aiface commited on
Commit
327b68f
1 Parent(s): 907b7f3

Upload 12 files

Browse files
.gitattributes CHANGED
@@ -32,3 +32,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
32
  *.zip filter=lfs diff=lfs merge=lfs -text
33
  *.zst filter=lfs diff=lfs merge=lfs -text
34
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
32
  *.zip filter=lfs diff=lfs merge=lfs -text
33
  *.zst filter=lfs diff=lfs merge=lfs -text
34
  *tfevents* filter=lfs diff=lfs merge=lfs -text
35
+ preprocessing/shape_predictor_68_face_landmarks.dat filter=lfs diff=lfs merge=lfs -text
preprocessing/20words_mean_face.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dbf68b2044171e1160716df7c53e8bbfaa0ee8c61fb41171d04cb6092bb81422
3
+ size 1168
preprocessing/30word - Copy.csv ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ thông,1916,,,,thông,tin,của,và,các,có,trong,là,ngày,đã,đầu,theo,công,tư,quý
2
+ tin,1740,,,,1916,1740,1687,1640,1566,1513,1512,1344,1330,1284,1202,1197,1165,1148,1119
3
+ của,1687,,,,những,thành,cho,vị,tế,về,phố,tháng,động,sản,với,được,chính,số,dõi
4
+ và,1640,,,,1076,1065,1050,971,937,925,919,903,902,883,873,869,797,760,750
5
+ các,1566,,,,,,,,,,,,,,,,,,
6
+ có,1513,,,,,,,,,,,,,,,,,,
7
+ trong,1512,,,,,,,,,,,,,,,,,,
8
+ là,1344,,,,,,,,,,,,,,,,,,
9
+ ngày,1330,,,,,,,,,,,,,,,,,,
10
+ đã,1284,,,,,,,,,,,,,,,,,,
11
+ đầu,1202,,,,,,,,,,,,,,,,,,
12
+ theo,1197,,,,,,,,,,,,,,,,,,
13
+ công,1165,,,,,,,,,,,,,,,,,,
14
+ tư,1148,,,,,,,,,,,,,,,,,,
15
+ quý,1119,,,,,,,,,,,,,,,,,,
16
+ những,1076,,,,,,,,,,,,,,,,,,
17
+ thành,1065,,,,,,,,,,,,,,,,,,
18
+ cho,1050,,,,,,,,,,,,,,,,,,
19
+ vị,971,,,,,,,,,,,,,,,,,,
20
+ tế,937,,,,,,,,,,,,,,,,,,
21
+ về,925,,,,,,,,,,,,,,,,,,
22
+ phố,919,,,,,,,,,,,,,,,,,,
23
+ tháng,903,,,,,,,,,,,,,,,,,,
24
+ động,902,,,,,,,,,,,,,,,,,,
25
+ sản,883,,,,,,,,,,,,,,,,,,
26
+ với,873,,,,,,,,,,,,,,,,,,
27
+ được,869,,,,,,,,,,,,,,,,,,
28
+ chính,797,,,,,,,,,,,,,,,,,,
29
+ số,760,,,,,,,,,,,,,,,,,,
30
+ dõi,750,,,,,,,,,,,,,,,,,,
preprocessing/30word.csv ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ thông,1916
2
+ tin,1740
3
+ của,1687
4
+ và,1640
5
+ các,1566
6
+ có,1513
7
+ trong,1512
8
+ là,1344
9
+ ngày,1330
10
+ đã,1284
11
+ đầu,1202
12
+ theo,1197
13
+ công,1165
14
+ tư,1148
15
+ quý,1119
16
+ những,1076
17
+ thành,1065
18
+ cho,1050
19
+ vị,971
20
+ tế,937
21
+ về,925
22
+ phố,919
23
+ tháng,903
24
+ động,902
25
+ sản,883
26
+ với,873
27
+ được,869
28
+ chính,797
29
+ số,760
30
+ dõi,750
preprocessing/README.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### Pre-processing
2
+
3
+ * To get mouth ROIs
4
+
5
+ Run mouth cropping script to save grayscale mouth ROIs. We assume you save cropped mouths to *`$TCN_LIPREADING_ROOT/datasets/visual_data/`*. You can choose `--testset-only` to produce testing set.
6
+
7
+ ```Shell
8
+ python crop_mouth_from_video.py --video-direc <LRW-DIREC> \
9
+ --landmark-direc <LANDMARK-DIREC> \
10
+ --save-direc <MOUTH-ROIS-DIRECTORY> \
11
+ --convert-gray \
12
+ --testset-only
13
+ ```
14
+
15
+ * To get audio waveforms
16
+
17
+ Run format conversion script to extract audio waveforms (.npz) from raw videos. We assume you save audio waveforms to *`$TCN_LIPREADING_ROOT/datasets/audio_data/`*. You can choose `--testset-only` to produce testing set.
18
+
19
+ ```Shell
20
+ python extract_audio_from_video.py --video-direc <LRW-DIREC> \
21
+ --save-direc <AUDIO-WAVEFORMS-DIRECTORY> \
22
+ --testset-only
23
+ ```
preprocessing/anhtrasn.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "transcript": "Xin kính chào quý vị Kính mời quý vị",
3
+ "words": [
4
+ {
5
+ "end_time": 7.6,
6
+ "start_time": 0.0,
7
+ "word": "Xin"
8
+ },
9
+ {
10
+ "end_time": 7.8,
11
+ "start_time": 7.6,
12
+ "word": "kính"
13
+ },
14
+ {
15
+ "end_time": 8.0,
16
+ "start_time": 7.8,
17
+ "word": "chào"
18
+ },
19
+ {
20
+ "end_time": 8.0,
21
+ "start_time": 8.0,
22
+ "word": "quý"
23
+ },
24
+ {
25
+ "end_time": 8.2,
26
+ "start_time": 8.0,
27
+ "word": "vị"
28
+ }
29
+ ]
30
+ }
preprocessing/crop_mouth_from_video.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #! /usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2020 Imperial College London (Pingchuan Ma)
5
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
6
+
7
+ """ Crop Mouth ROIs from videos for lipreading"""
8
+
9
+ # from msilib.schema import File
10
+ from ast import Pass
11
+ import os
12
+ import cv2 # OpenCV 라이브러리
13
+ import glob # 리눅스식 경로 표기법을 사용하여 원하는 폴더/파일 리스트 얻음
14
+ import argparse # 명령행 인자를 파싱해주는 모듈
15
+ import numpy as np
16
+ from collections import deque # collections 모듈에 있는 데크 불러오기 # 데크: 스택과 큐를 합친 자료구조
17
+
18
+ from utils import * # utils.py 모듈에 있는 모든 함수 불러오기
19
+ from transform import * # transform.py 모듈에 있는 모든 함수 불러오기
20
+
21
+ import dlib # face landmark 찾는 라이브러리
22
+ import face_alignment # face landmark 찾는 라이브러리
23
+ from PIL import Image
24
+
25
+
26
+ # 인자값을 받아서 처리하는 함수
27
+ def load_args(default_config=None):
28
+ # 인자값을 받아서 처리하는 함수
29
+ parser = argparse.ArgumentParser(description='Lipreading Pre-processing')
30
+
31
+ # 입력받을 인자값 등록
32
+ # -- utils
33
+ parser.add_argument('--video-direc', default=None, help='raw video directory')
34
+ parser.add_argument('--video-format', default='.mp4', help='raw video format')
35
+ parser.add_argument('--landmark-direc', default=None, help='landmark directory')
36
+ parser.add_argument('--filename-path', default='./vietnamese_detected_face_30.csv', help='list of detected video and its subject ID')
37
+ parser.add_argument('--save-direc', default=None, help='the directory of saving mouth ROIs')
38
+ # -- mean face utils
39
+ parser.add_argument('--mean-face', default='./20words_mean_face.npy', help='mean face pathname')
40
+ # -- mouthROIs utils
41
+ parser.add_argument('--crop-width', default=96, type=int, help='the width of mouth ROIs')
42
+ parser.add_argument('--crop-height', default=96, type=int, help='the height of mouth ROIs')
43
+ parser.add_argument('--start-idx', default=48, type=int, help='the start of landmark index')
44
+ parser.add_argument('--stop-idx', default=68, type=int, help='the end of landmark index')
45
+ parser.add_argument('--window-margin', default=12, type=int, help='window margin for smoothed_landmarks')
46
+ # -- convert to gray scale
47
+ parser.add_argument('--convert-gray', default=False, action='store_true', help='convert2grayscale')
48
+ # -- test set only
49
+ parser.add_argument('--testset-only', default=False, action='store_true', help='process testing set only')
50
+
51
+ # 입력받은 인자값을 args에 저장 (type: namespace)
52
+ args = parser.parse_args()
53
+ return args
54
+
55
+ args = load_args() # args 파싱 및 로드
56
+
57
+ # -- mean face utils
58
+ STD_SIZE = (256, 256)
59
+ mean_face_landmarks = np.load(args.mean_face) # 20words_mean_face.npy
60
+ stablePntsIDs = [33, 36, 39, 42, 45]
61
+
62
+
63
+ # 영상에서 랜드마크 받아서 입술 잘라내기
64
+ def crop_patch( video_pathname, landmarks):
65
+
66
+ """Crop mouth patch
67
+ :param str video_pathname: pathname for the video_dieo # 영상 위치
68
+ :param list landmarks: interpolated landmarks # 보간된 랜드마크
69
+ """
70
+
71
+ frame_idx = 0 # 프레임 인덱스 번호 0 으로 초기화
72
+ frame_gen = read_video(video_pathname) # 비디오 불러오기
73
+
74
+ # 무한 반복
75
+ while True:
76
+ try:
77
+ frame = frame_gen.__next__() ## -- BGR # 이미지 프레임 하나씩 불러오기
78
+ except StopIteration: # 더 이상 next 요소가 없으면 StopIterraion Exception 발생
79
+ break # while 빠져나가기
80
+ if frame_idx == 0: # 프레임 인덱스 번호가 0일 경우
81
+ q_frame, q_landmarks = deque(), deque() # 데크 생성
82
+ sequence = []
83
+
84
+ q_landmarks.append(landmarks[frame_idx]) # 프레임 인덱스 번호에 맞는 랜드마크 정보 추가
85
+ q_frame.append(frame) # 프레임 정보 추가
86
+ if len(q_frame) == args.window_margin:
87
+ smoothed_landmarks = np.mean(q_landmarks, axis=0) # 각 그룹의 같은 원소끼리 평균
88
+ cur_landmarks = q_landmarks.popleft() # 데크 제일 왼쪽 값 꺼내기
89
+ cur_frame = q_frame.popleft() # 데크 제일 왼쪽 값 꺼내기
90
+ # -- affine transformation # 아핀 변환
91
+ trans_frame, trans = warp_img( smoothed_landmarks[stablePntsIDs, :],
92
+ mean_face_landmarks[stablePntsIDs, :],
93
+ cur_frame,
94
+ STD_SIZE)
95
+ trans_landmarks = trans(cur_landmarks)
96
+ # -- crop mouth patch # 입술 잘라내기
97
+ sequence.append( cut_patch( trans_frame,
98
+ trans_landmarks[args.start_idx:args.stop_idx],
99
+ args.crop_height//2,
100
+ args.crop_width//2,))
101
+ if frame_idx == len(landmarks)-1:
102
+ while q_frame:
103
+ cur_frame = q_frame.popleft() # 데크 제일 왼쪽 값 꺼내기
104
+ # -- transform frame # 프레임 변환
105
+ trans_frame = apply_transform( trans, cur_frame, STD_SIZE)
106
+ # -- transform landmarks # 랜드마크 변환
107
+ trans_landmarks = trans(q_landmarks.popleft())
108
+ # -- crop mouth patch # 입술 잘라내기
109
+ sequence.append( cut_patch( trans_frame,
110
+ trans_landmarks[args.start_idx:args.stop_idx],
111
+ args.crop_height//2,
112
+ args.crop_width//2,))
113
+ return np.array(sequence) # 입술 numpy 반환
114
+ frame_idx += 1 # 프레임 인덱스 번호 증가
115
+ return None
116
+
117
+
118
+ # 랜드마크 보간
119
+ def landmarks_interpolate(landmarks):
120
+
121
+ """Interpolate landmarks
122
+ param list landmarks: landmarks detected in raw videos # 원본 영상 데이터에서 검출한 랜드마크
123
+ """
124
+
125
+ valid_frames_idx = [idx for idx, _ in enumerate(landmarks) if _ is not None] # 랜드마크 번호 list 생성
126
+
127
+ # 랜드마크 번호 list 가 비어있다면
128
+ if not valid_frames_idx:
129
+ return None
130
+
131
+ # 1부터 (랜드마크 번호 list 개수-1)만큼 for 문 반복
132
+ for idx in range(1, len(valid_frames_idx)):
133
+ if valid_frames_idx[idx] - valid_frames_idx[idx-1] == 1: # 현재 랜드마크 번호 - 이전 랜드마크 번호 == 1 일 경우
134
+ continue # 코드 실행 건너뛰기
135
+ else: # 아니라면
136
+ landmarks = linear_interpolate(landmarks, valid_frames_idx[idx-1], valid_frames_idx[idx]) # 랜드마크 업데이트(보간)
137
+
138
+ valid_frames_idx = [idx for idx, _ in enumerate(landmarks) if _ is not None] # 랜드마크 번호 list 생성
139
+ # -- Corner case: keep frames at the beginning or at the end failed to be detected. # 시작 또는 끝 프레임을 보관하지 못함
140
+ if valid_frames_idx:
141
+ landmarks[:valid_frames_idx[0]] = [landmarks[valid_frames_idx[0]]] * valid_frames_idx[0] # 랜드마크 첫번째 프레임 정보 저장
142
+ landmarks[valid_frames_idx[-1]:] = [landmarks[valid_frames_idx[-1]]] * (len(landmarks) - valid_frames_idx[-1]) # 랜드마크 마지막 프레임 정보 저장
143
+
144
+ valid_frames_idx = [idx for idx, _ in enumerate(landmarks) if _ is not None] # 랜드마크 번호 list 생성
145
+ # 랜드마크 번호 list 개수 == 보간한 랜드마크 개수 확인, 아니면 AssertionError 메시지를 띄움
146
+ assert len(valid_frames_idx) == len(landmarks), "not every frame has landmark" # 원하는 조건의 변수값을 보증하기 위해 사용
147
+
148
+ return landmarks # 랜드마크 반환
149
+
150
+
151
+ def get_yield(output_video):
152
+ for frame in output_video:
153
+ yield frame
154
+
155
+
156
+ lines = open(args.filename_path).read().splitlines() # 문자열을 '\n' 기준으로 쪼갠 후 list 생성
157
+ lines = list(filter(lambda x: 'test' == x.split('/')[-2], lines)) if args.testset_only else lines # args.testset_only 값이 있다면 test 폴더 속 파일명만 불러와서 list 생성, 아니라면 원래 lines 그대로 값 유지
158
+
159
+ # lines 개수만큼 반복문 실행
160
+ for filename_idx, line in enumerate(lines):
161
+
162
+ # 파일명, 사람id
163
+ filename, person_id = line.split(',')
164
+ print('idx: {} \tProcessing.\t{}'.format(filename_idx, filename)) # 파일 인덱스번호, 파일명 출력
165
+
166
+ video_pathname = os.path.join(args.video_direc, filename+args.video_format) # 영상디렉토리 + 파일명.비디오포맷/
167
+ landmarks_pathname = os.path.join(args.landmark_direc, filename+'.npz') # 저장디렉토리 + 랜드마크 파일명.npz
168
+ dst_pathname = os.path.join( args.save_direc, filename+'.npz') # 저장디렉토리 + 결과영상 파일명.npz
169
+
170
+ # 파일이 있는지 확인, 없으면 AssertionError 메시지를 띄움
171
+ assert os.path.isfile(video_pathname), "File does not exist. Path input: {}".format(video_pathname) # 원하는 조건의 변수값을 보증하기 위해 사용
172
+
173
+ # video 에 대한 face landmark npz 파일이 없고 영상 확장자 avi 인 경우 dlib 으로 직접 npz 파일 생성
174
+ if not os.path.exists(landmarks_pathname) and video_pathname.split('.')[-1] == 'mp4':
175
+
176
+ # dlib 사용해서 face landmark 찾기
177
+ def get_face_landmark(img):
178
+ detector_hog = dlib.get_frontal_face_detector()
179
+ dlib_rects = detector_hog(img, 1)
180
+ model_path = os.path.dirname(os.path.abspath(__file__)) + '/shape_predictor_68_face_landmarks.dat'
181
+ landmark_predictor = dlib.shape_predictor(model_path)
182
+
183
+ # dlib 으로 face landmark 찾기
184
+ list_landmarks = []
185
+ for dlib_rect in dlib_rects:
186
+ points = landmark_predictor(img, dlib_rect)
187
+ list_points = list(map(lambda p: (p.x, p.y), points.parts()))
188
+ list_landmarks.append(list_points)
189
+
190
+ input_width, input_height = img.shape
191
+ output_width, output_height = (256, 256)
192
+ width_rate = input_width / output_width
193
+ height_rate = input_height / output_height
194
+ img_rate = [(width_rate, height_rate)]*68
195
+ face_rate = np.array(img_rate)
196
+ eye_rate = np.array(img_rate[36:48])
197
+
198
+ # face landmark list 가 비어있지 않은 경우
199
+ if list_landmarks:
200
+ for dlib_rect, landmark in zip(dlib_rects, list_landmarks):
201
+ face_landmark = np.array(landmark) # face landmark
202
+ eye_landmark = np.array(landmark[36:48]) # eye landmark
203
+
204
+ return face_landmark, eye_landmark
205
+ # face landmark list 가 비어있는 경우
206
+ else:
207
+ landmark = [(0.0, 0.0)] * 68
208
+ face_landmark = np.array(landmark) # face landmark
209
+ eye_landmark = np.array(landmark[36:48]) # eye landmark
210
+ return face_landmark, eye_landmark
211
+
212
+
213
+ target_frames = 29 # 원하는 프레임 개수
214
+ video = videoToArray(video_pathname, is_gray=args.convert_gray) # 영상 정보 앞에 영상 프레임 개수를 추가한 numpy
215
+ output_video = frameAdjust(video, target_frames) # frame sampling (프레임 개수 맞추기)
216
+
217
+ multi_sub_landmarks = []
218
+ person_landmarks = []
219
+ frame_landmarks = []
220
+ for frame_idx, frame in enumerate(get_yield(output_video)):
221
+ print(f'\n ------------frame {frame_idx}------------ ')
222
+
223
+ facial_landmarks, eye_landmarks = get_face_landmark(frame) # dlib 사용해서 face landmark 찾기
224
+
225
+ person_landmarks = {
226
+ 'id': 0,
227
+ 'most_recent_fitting_scores': np.array([2.0,2.0,2.0]),
228
+ 'facial_landmarks': facial_landmarks,
229
+ 'roll': 7,
230
+ 'yaw': 3.5,
231
+ 'eye_landmarks': eye_landmarks,
232
+ 'fitting_scores_updated': True,
233
+ 'pitch': -0.05
234
+ }
235
+ frame_landmarks.append(person_landmarks)
236
+ multi_sub_landmarks.append(np.array(frame_landmarks.copy(), dtype=object))
237
+
238
+ multi_sub_landmarks = np.array(multi_sub_landmarks) # list to numpy
239
+ save2npz(landmarks_pathname, data=multi_sub_landmarks) # face landmark npz 저장
240
+ print('\n ------------ save npz ------------ \n')
241
+
242
+ # video 에 대한 face landmark npz 파일이 있는 경우
243
+ else:
244
+
245
+ # 파일이 있는지 확인, 없으면 AssertionError 메시지를 띄움
246
+ assert os.path.isfile(landmarks_pathname), "File does not exist. Path input: {}".format(landmarks_pathname) # 원하는 조건의 변수값을 보증하기 위해 사용
247
+
248
+ # 파일이 존재할 경우
249
+ if os.path.exists(dst_pathname):
250
+ continue # 코드 실행 건너뛰기
251
+
252
+ multi_sub_landmarks = np.load( landmarks_pathname, allow_pickle=True)['data'] # numpy 파일 열기
253
+ landmarks = [None] * len( multi_sub_landmarks) # 랜드마크 변수 초기화
254
+ for frame_idx in range(len(landmarks)):
255
+ try:
256
+ landmarks[frame_idx] = multi_sub_landmarks[frame_idx][int(person_id)]['facial_landmarks'].astype(np.float64) # 프레임 인덱스 번호에서 사람id의 얼굴 랜드마크 정보 가져오기
257
+ except IndexError: # 해당 인덱스 번호에 깂이 없으면 IndexError 발생
258
+ continue # 코드 실행 건너뛰기
259
+
260
+ # face landmark 가 [(0,0)]*68 이 아니면 랜드마크 보간 후 npz 파일 생성
261
+ landmarks_empty_list = []
262
+ landmarks_empty = [(0, 0)]*68
263
+ landmarks_empty = np.array(landmarks_empty, dtype=object)
264
+ for i in range(len(landmarks_empty)):
265
+ landmarks_empty_list.append(landmarks_empty.copy())
266
+ condition = landmarks != landmarks_empty_list
267
+ if condition:
268
+ # -- pre-process landmarks: interpolate frames not being detected.
269
+ preprocessed_landmarks = landmarks_interpolate(landmarks) # 랜드마크 보간
270
+ # 변수가 비어있지 않다면
271
+ if not preprocessed_landmarks:
272
+ continue # 코드 실행 건너뛰기
273
+
274
+ # -- crop
275
+ sequence = crop_patch(video_pathname, preprocessed_landmarks) # 영상에서 랜드마크 받아서 입술 잘라내기
276
+ # sequence가 비어있는지 확인, 비어있으면 AssertionError 메시지를 띄움
277
+ assert sequence is not None, "cannot crop from {}.".format(filename) # 원하는 조건의 변수값을 보증하기 위해 사용
278
+
279
+ # -- save
280
+ data = convert_bgr2gray(sequence) if args.convert_gray else sequence[...,::-1] # gray 변환
281
+ save2npz(dst_pathname, data=data) # 데이터를 npz 형식으로 저장
282
+
283
+ print('Done.')
preprocessing/extract_audio_from_video.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #! /usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Copyright 2020 Imperial College London (Pingchuan Ma)
5
+ # Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
6
+
7
+
8
+ """Transforms mp4 audio to npz. Code has strong assumptions on the dataset organization!"""
9
+
10
+ import os
11
+ import librosa # 음원 데이터 분석 라이브러리
12
+ import argparse # 명령행 인자를 파싱해주는 모듈
13
+
14
+ from utils import * # utils.py 모듈에 있는 모든 함수(read_txt_lines(), save2npz(), read_video()) 불러오기
15
+
16
+
17
+ # 인자값을 받아서 처리하는 함수
18
+ def load_args(default_config=None):
19
+ # 인자값을 받을 수 있는 인스턴스 생성
20
+ parser = argparse.ArgumentParser(description='Extract Audio Waveforms')
21
+
22
+ # 입력받을 인자값 등록
23
+ # -- utils
24
+ parser.add_argument('--video-direc', default=None, help='raw video directory')
25
+ parser.add_argument('--filename-path', default='./vietnamese_detected_face_30.csv', help='list of detected video and its subject ID')
26
+ parser.add_argument('--save-direc', default=None, help='the directory of saving audio waveforms (.npz)')
27
+ # -- test set only
28
+ parser.add_argument('--testset-only', default=False, action='store_true', help='process testing set only')
29
+
30
+ # 입력받은 인자값을 args에 저장 (type: namespace)
31
+ args = parser.parse_args()
32
+ return args
33
+
34
+ args = load_args() # args 파싱 및 로드
35
+
36
+ lines = open(args.filename_path).read().splitlines() # 문자열을 '\m' 기준으로 쪼갠 후 list 생성
37
+ lines = list(filter(lambda x: 'test' == x.split('/')[-2], lines)) if args.testset_only else lines # args.testset_only 값이 있다면 test 폴더 속 파일명만 불러와서 list 생성, 아니라면 원래 lines 그대로 값 유지
38
+
39
+ # lines 개수만큼 반복문 실행
40
+ for filename_idx, line in enumerate(lines):
41
+
42
+ # 파일명, 사람id
43
+ filename, person_id = line.split(',')
44
+ print('idx: {} \tProcessing.\t{}'.format(filename_idx, filename)) # 파일 인덱스번호, 파일명 출력
45
+
46
+ video_pathname = os.path.join(args.video_direc, filename+'.mp4') # 영상디렉토리 + 파일명.mp4
47
+ dst_pathname = os.path.join( args.save_direc, filename+'.npz') # 저장디렉토리 + 파일명.npz
48
+
49
+ # 파일이 있는지 확인, 없으면 AssertionError 메시지를 띄움
50
+ assert os.path.isfile(video_pathname), "File does not exist. Path input: {}".format(video_pathname) # 원하는 조건의 변수값을 보증하기 위해 사용
51
+
52
+ # wav 파일 읽는 라이브러리: librosa
53
+ # librosa 로 데이터를 읽으면 데이터 범위가 [-1,1]로 정규화됨
54
+ # librosa 입력에서 sr=None 으로 지정하지 않고 임의의 sample_rate를 설정하면 load할 때 resampling 수행함
55
+ data = librosa.load(video_pathname, sr=16000)[0][-19456:]
56
+ save2npz(dst_pathname, data=data) # librosa 로 읽은 데이터를 npz 형식으로 저장
preprocessing/shape_predictor_68_face_landmarks.dat ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fbdc2cb80eb9aa7a758672cbfdda32ba6300efe9b6e6c7a299ff7e736b11b92f
3
+ size 99693937
preprocessing/transform.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2 # OpenCV 라이브러리
2
+ import numpy as np
3
+ from skimage import transform as tf # 이미지 변환 모듈
4
+
5
+ # -- Landmark interpolation:
6
+ def linear_interpolate(landmarks, start_idx, stop_idx):
7
+ start_landmarks = landmarks[start_idx] # 랜드마크 시작
8
+ stop_landmarks = landmarks[stop_idx] # 랜드마크 끝
9
+ delta = stop_landmarks - start_landmarks # 랜드마크 값 차이
10
+ for idx in range(1, stop_idx-start_idx):
11
+ landmarks[start_idx+idx] = start_landmarks + idx/float(stop_idx-start_idx) * delta # 랜드마크 업데이트(보간)
12
+ return landmarks
13
+
14
+ # -- Face Transformation
15
+ # src: 입력 영상, dst: 출력/결과 영상
16
+ def warp_img(src, dst, img, std_size):
17
+ tform = tf.estimate_transform('similarity', src, dst) # find the transformation matrix # 변환 행렬 구하기
18
+ warped = tf.warp(img, inverse_map=tform.inverse, output_shape=std_size) # wrap the frame image # 주어진 좌표 변환에 따라 프레임 이미지 왜곡
19
+ warped = warped * 255 # note output from wrap is double image (value range [0,1])
20
+ warped = warped.astype('uint8') # numpy 데이터 타입 uint8 으로 변경
21
+ return warped, tform
22
+
23
+ def apply_transform(transform, img, std_size):
24
+ warped = tf.warp(img, inverse_map=transform.inverse, output_shape=std_size) # wrap the frame image # 주어진 좌표 변환에 따라 프레임 이미지 왜곡
25
+ warped = warped * 255 # note output from wrap is double image (value range [0,1])
26
+ warped = warped.astype('uint8') # numpy 데이터 타입 uint8 으로 변경
27
+ return warped
28
+
29
+ # -- Crop
30
+ def cut_patch(img, landmarks, height, width, threshold=5):
31
+
32
+ center_x, center_y = np.mean(landmarks, axis=0) # 각 그룹의 같은 원소끼리 평균
33
+
34
+ # 좌표 처리
35
+ if center_y - height < 0:
36
+ center_y = height
37
+ if center_y - height < 0 - threshold:
38
+ raise Exception('too much bias in height')
39
+ if center_x - width < 0:
40
+ center_x = width
41
+ if center_x - width < 0 - threshold:
42
+ raise Exception('too much bias in width')
43
+
44
+ if center_y + height > img.shape[0]:
45
+ center_y = img.shape[0] - height
46
+ if center_y + height > img.shape[0] + threshold:
47
+ raise Exception('too much bias in height')
48
+ if center_x + width > img.shape[1]:
49
+ center_x = img.shape[1] - width
50
+ if center_x + width > img.shape[1] + threshold:
51
+ raise Exception('too much bias in width')
52
+
53
+ # 배열 복사
54
+ cutted_img = np.copy(img[ int(round(center_y) - round(height)): int(round(center_y) + round(height)),
55
+ int(round(center_x) - round(width)): int(round(center_x) + round(width))])
56
+ return cutted_img
57
+
58
+ # -- RGB to GRAY
59
+ def convert_bgr2gray(data):
60
+ # np.stack(배열_1, 배열_2, axis=0): 지정한 axis를 완전히 새로운 axis로 생각
61
+ return np.stack([cv2.cvtColor(_, cv2.COLOR_BGR2GRAY) for _ in data], axis=0) # gray 변환
preprocessing/utils.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #coding=utf-8
2
+ import os
3
+ import cv2 # OpenCV 라이브러리
4
+ import numpy as np
5
+ from PIL import Image
6
+
7
+
8
+ # -- IO utils
9
+ # 텍스트 라인 불러오기
10
+ def read_txt_lines(filepath):
11
+ # 파일이 있는지 확인, 없으면 AssertionError 메시지를 띄움
12
+ assert os.path.isfile( filepath ), "Error when trying to read txt file, path does not exist: {}".format(filepath) # 원하는 조건의 변수값을 보증하기 위해 사용
13
+
14
+ # 파일 불러오기
15
+ with open( filepath ) as myfile:
16
+ content = myfile.read().splitlines() # 문자열을 '\n' 기준으로 쪼갠 후 list 생성
17
+ return content
18
+
19
+
20
+ # npz 저장
21
+ def save2npz(filename, data=None):
22
+ # 데이터가 비어있는지 확인, 없으면 AssertionError 메시지를 띄움
23
+ assert data is not None, "data is {}".format(data)
24
+
25
+ # 파일 없을 경우
26
+ if not os.path.exists(os.path.dirname(filename)):
27
+ os.makedirs(os.path.dirname(filename)) # 디렉토리 생성
28
+ np.savez_compressed(filename, data=data) # 압축되지 않은 .npz 파일 형식 으로 여러 배열 저장
29
+ def save2npz(filename, data=None):
30
+ """save2npz.
31
+ :param filename: str, the fileanme where the data will be saved.
32
+ :param data: ndarray, arrays to save to the file.
33
+ """
34
+ assert data is not None, "data is {}".format(data)
35
+ if not os.path.exists(os.path.dirname(filename)):
36
+ os.makedirs(os.path.dirname(filename))
37
+ np.savez_compressed(filename, data=data)
38
+
39
+ # 비디오 불러오기
40
+ def read_video(filename):
41
+ cap = cv2.VideoCapture(filename) # 영상 객체(파일) 가져오기
42
+
43
+ while(cap.isOpened()): # 영상 파일(카메라)이 정상적으로 열렸는지(초기화되었는지) 여부
44
+ # ret: 정상적으로 읽어왔는가?
45
+ # frame: 한 장의 이미지(frame) 가져오기
46
+ ret, frame = cap.read() # BGR
47
+ if ret: # 프레임 정보를 정상적으로 읽지 못하면
48
+ yield frame # 프레임을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보
49
+ else: # 프레임 정보를 정상적으로 읽지 못하면
50
+ break # while 빠져나가기
51
+ cap.release() # 영상 파일(카메라) 사용 종료
52
+
53
+
54
+
55
+ # Video 정보 가져오기
56
+ def get_video_info(infilename, is_print=False):
57
+ cap = cv2.VideoCapture(infilename)
58
+ if not cap.isOpened():
59
+ print("could not open : ", infilename)
60
+ cap.release()
61
+ exit(0)
62
+
63
+ length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
64
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
65
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
66
+ fps = cap.get(cv2.CAP_PROP_FPS)
67
+ cap.release()
68
+
69
+ if is_print:
70
+ print('length : ', length)
71
+ print('width : ', width)
72
+ print('height : ', height)
73
+ print('fps : ', fps)
74
+
75
+ video_info = {
76
+ 'length': length,
77
+ 'width': width,
78
+ 'height': height,
79
+ 'fps': fps,
80
+ }
81
+
82
+ return video_info
83
+
84
+ # Video -> Numpy
85
+ # 참고 깃허브 코드: https://github.com/khazit/Lip2Word/blob/master/lipReader.py#L22
86
+ def videoToArray(video_pathname, is_gray=True) :
87
+
88
+ cap = cv2.VideoCapture(video_pathname) # 영상 객체(파일) 가져오기
89
+
90
+ # 영상 파일(카메라)이 정상적으로 열리지 않은 경우
91
+ if not cap.isOpened():
92
+ print("could not open : ", video_pathname)
93
+ cap.release() # 영상 파일(카메라) 사용 종료
94
+ exit(0) # 빠져나가기
95
+
96
+ n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 영상 프레임 개수
97
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 영상 너비
98
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 영상 높이
99
+ fps = cap.get(cv2.CAP_PROP_FPS) # 영상 FPS(Frames Per Second)
100
+
101
+ if is_gray:
102
+ video = np.zeros((n_frames, height, width)) # gray
103
+ else:
104
+ n_channels=3
105
+ video = np.zeros((n_frames, height, width, n_channels)) # color
106
+
107
+ video = video.astype(np.uint8)
108
+
109
+ i = 0
110
+ while True :
111
+ success, frame = cap.read()
112
+ if not success :
113
+ break
114
+ else :
115
+ # gray scale 적용
116
+ if is_gray:
117
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
118
+
119
+ video[i] = frame
120
+ i += 1
121
+
122
+ cap.release() # 영상 파일(카메라) 사용 종료
123
+
124
+ return video # 영상 정보 앞에 영상 프레임 개수를 추가한 numpy 반환
125
+
126
+
127
+ # Frame Sampling (프레임 개수 맞추기)
128
+ # 참고 깃허브 코드: https://github.com/khazit/Lip2Word/blob/master/lipReader.py#L62
129
+ def frameAdjust(video, target_frames=29):
130
+ n_frames = video.shape[0] # 영상 프레임 개수
131
+
132
+ if target_frames == n_frames :
133
+ return video # 영상 그대로 반환
134
+ else :
135
+ # 영상 프레임 개수 > 원하는 프레임 개수
136
+ if n_frames > target_frames :
137
+ idx = np.linspace(0, n_frames-1, target_frames) # 숫자 시퀀스 생성 # 구간 시작점, 구간 끝점, 구간 내 숫자 개수
138
+ idx = np.around(idx, 0).astype(np.int32) # 반올림하고 dtype 을 정수로 변경
139
+ return video[idx] # 원하는 프레임 개수로 sampling 한 영상
140
+ # 영상 프레임 개수 < 원하는 프레임 개수
141
+ else :
142
+ output_video = np.zeros((target_frames, *video.shape[1:])).astype(np.uint8) # 원하는 프레임 개수에 맞춰서 0으로 초기화한 numpy 생성
143
+ output_video[:n_frames] = video # 영상 프레임 개수까지 그대로 영상 정보 저장
144
+
145
+ # 원하는 프레임 개수만큼 마지막 프레임 복제
146
+ for i in range(target_frames-n_frames+1) :
147
+ output_video[i+n_frames-1] = output_video[n_frames-1]
148
+
149
+ return output_video # 원하는 프레임 개수로 sampling 한 영상
preprocessing/vietnamese_detected_face_30_words.csv ADDED
The diff for this file is too large to render. See raw diff
 
preprocessing/vietnamese_detected_face_30_words_have_snr.csv ADDED
The diff for this file is too large to render. See raw diff