Spaces:
Sleeping
Sleeping
Upload 12 files
Browse files- .gitattributes +1 -0
- preprocessing/20words_mean_face.npy +3 -0
- preprocessing/30word - Copy.csv +30 -0
- preprocessing/30word.csv +30 -0
- preprocessing/README.md +23 -0
- preprocessing/anhtrasn.json +30 -0
- preprocessing/crop_mouth_from_video.py +283 -0
- preprocessing/extract_audio_from_video.py +56 -0
- preprocessing/shape_predictor_68_face_landmarks.dat +3 -0
- preprocessing/transform.py +61 -0
- preprocessing/utils.py +149 -0
- preprocessing/vietnamese_detected_face_30_words.csv +0 -0
- preprocessing/vietnamese_detected_face_30_words_have_snr.csv +0 -0
.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
|
|