File size: 12,260 Bytes
9e14838
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
import os
from os.path import join
import argparse
import glob
import subprocess
import cv2
from tqdm import tqdm
import numpy as np
import logging
import torch
from test_tools.common import detect_all, grab_all_frames
from test_tools.faster_crop_align_xray import FasterCropAlignXRay
from test_tools.warp_for_xray import (
    estimiate_batch_transform,
    transform_landmarks,
    std_points_256,
)
from test_tools.ct.operations import find_longest, multiple_tracking
from test_tools.utils import get_crop_box
import datetime
# from FaceForensics.face_detection_save import get_boundingbox

os.environ['CUDA_LAUNCH_BLOCKING'] = "1"
os.environ["CUDA_VISIBLE_DEVICES"] = "0"

device=torch.device('cuda')
#Date
now = datetime.datetime.now()
logger = logging.getLogger("main") #Logger 선언
stream_handler = logging.StreamHandler() # Logger output 방법 선언
formatter = logging.Formatter('[%(asctime)s][%(levelname)s|%(filename)s:%(lineno)s] >> %(message)s')
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
logger.setLevel(logging.DEBUG)

crop_align_func = FasterCropAlignXRay(256)
max_frame= 10000

### video path를 받으면 crop된 face를 저장하는 함수 ###
def crop_face_from_video(video_path,cache_path,crop_path,clip_size):
    # mp4 파일이 아니면 return
    if 'mp4' not in video_path : return
    # video name
    video_name = video_path.split('/')[-1].replace('.mp4','')
    # 만약 crop image path에 crop된 이미지가 110개 이상이면 return

    if os.path.exists(crop_path):
        if len(os.listdir(crop_path))>clip_limit:
            logger.info(f'{video_name} already exists')
            return
        
    ##########################################
    # detect_res : list, 전체 frame, whole frame
    # detect_res [] : list, len = 사람 수로 예상 the number of detected face in a frame
    # detect_res [] [] : tuple, length = 3 
    # detect_res [] [] 의 각 요소는 각각 box, lm5 : landmark (5,2) , score 
    ##########################################
    # all_lm68 : list, 전체 frame, whole frame
    # all_lm68 : list, len = 사람 수로 예상, the number of detected face in a frame
    # all_lm68 : np.array : landmark 68개 (68,2)
    ##########################################
    # frames : each frame's np.array
    
    
    # cache_file : cache file path
    # landmark와 box를 저장하는 cache file
    cache_file = f"{cache_path}.pth"
   
    if os.path.exists(cache_file):
        # cache file이 존재하면 load하고 frame만 불러옴
        detect_res, all_lm68 = torch.load(cache_file)
        frames = grab_all_frames(video_path, max_size=max_frame, cvt=True)
        logger.info("detection result loaded from cache")
    else:
        # cache file이 존재하지 않으면 detect_all 함수를 통해 detect_res, all_lm68, frames를 불러옴
        # detection_all 함수는 retina_face를 이용해서 box와 landmark를 찾는 함수
        detect_res, all_lm68, frames = detect_all(
                    video_path, return_frames=True, max_size=10000
                )
        torch.save((detect_res, all_lm68), cache_file)
    try:
        shape = frames[0].shape[:2]
    except IndexError: # if there is no frame in the video, error list에 저장
        f = open("./indexerror.txt", 'a')
        f.write("{}\n".format(video_path))
        f.close()
        return
    
    # 모든 detect_res
    all_detect_res = []

    assert len(all_lm68) == len(detect_res)
    # in each frame, save the detected face's bounding box, landmark(5, 68), score as a tuple and save it in a list
    for faces, faces_lm68 in zip(detect_res, all_lm68):
            new_faces = []
            for (box, lm5, score), face_lm68 in zip(faces, faces_lm68):
                new_face = (box, lm5, face_lm68, score)
                new_faces.append(new_face)
            all_detect_res.append(new_faces)
    detect_res = all_detect_res    
    # SORT tracking
    # tracks : list, len = 사람 수로 예상, the number of detected face in a frame
    # tracks [] : list, len = 프레임 수, the number of frames
    # tracks [] [] : tuple, length = 4, 각각 box, lm5 : landmark (5,2) , lm68 : landmark (68,2), score
    tracks = multiple_tracking(detect_res)
    # tuples : list, len = 사람 수로 예상, the number of detected face in a frame
    # tuples [] : tuple, length = 2, 각각 0, 프레임 수 the number of frames
    tuples = [(0, len(detect_res))] * len(tracks)
    # if there is no face detected, find the longest face in the video
    if len(tracks) == 0:
        tuples, tracks = find_longest(detect_res)
    data_storage = {}
    frame_boxes = {}
    super_clips = []
    frame_res = {}
    super_clips_start_end = []
    # super_clips : tracking된 face들을 의미하는 것으로 보임
    for track_i, ((start, end), track) in enumerate(zip(tuples, tracks)): # each track(=face)
        
        # if detect_res's length is not equal to track's length, raise error 
        assert len(detect_res[start:end]) == len(track)
        
        super_clips.append(len(track))
        super_clips_start_end.append((start, end))
        for face, frame_idx, j in zip(track, range(start, end), range(len(track))): # frame에서 각각의 face
            box,lm5,lm68 = face[:3] # box, lm5, lm68
            big_box = get_crop_box(shape, box, scale=0.5) # get crop box

            top_left = big_box[:2][None, :] # top left point

            new_lm5 = lm5 - top_left 
            new_lm68 = lm68 - top_left

            new_box = (box.reshape(2, 2) - top_left).reshape(-1)

            info = (new_box, new_lm5, new_lm68, big_box) # face info


            x1, y1, x2, y2 = big_box
            cropped = frames[frame_idx][y1:y2, x1:x2]
            # cropped = cv2.resize(cropped, (512, 512))
            # face들을 tracking한 박스들로 crop함
            # landmark들도 box에 맞게 변환
            # data_storage에 저장 i는 face id, j는 frame을 의미
            base_key = f"{track_i}_{j}_" # i : face, j : frame
            data_storage[base_key + "img"] = cropped
            data_storage[base_key + "ldm"] = info
            data_storage[base_key + "idx"] = frame_idx
            frame_boxes[frame_idx] = np.rint(box).astype(np.int64)
    # 총 crop된 face들과 그 face들의 frame 수를 알려줌
    logger.info(f"{crop_path}  :  sampling clips from super clips {super_clips}")
    clips_for_video = []
    clip_size = clip_size
    pad_length = clip_size - 1
    
    # 각 face id 별로 clip을 만듦
    # 아래의 영어 표기로는 8clip을 의미하지만 정확하겐 clip size 만큼 함
    for super_clip_idx, super_clip_size in enumerate(super_clips): # cut the super clip into clips, overlap 7frames, 8frames per clip 
        inner_index = list(range(super_clip_size))
        
        if super_clip_size < clip_size: # if there is not enough frames to make a clip, pad the frames   
            # to do : how to operate the padding
            # 정확하게 이 코드가 어떻게 동작하는지 모르겠지만 
            # 대략적으로 frame들을 clipsize로 나눌때 부족하면 
            # clip size만큼의 frame이 되도록 padding을 함
            if super_clip_size < clip_size//2 : continue 
            post_module = inner_index[1:-1][::-1] + inner_index

            l_post = len(post_module)
            post_module = post_module * (pad_length // l_post + 1)
            post_module = post_module[:pad_length]
            assert len(post_module) == pad_length

            pre_module = inner_index + inner_index[1:-1][::-1]
            l_pre = len(post_module)
            pre_module = pre_module * (pad_length // l_pre + 1)
            pre_module = pre_module[-pad_length:]
            assert len(pre_module) == pad_length

            inner_index = pre_module + inner_index + post_module

        super_clip_size = len(inner_index)

        frame_range = [
            inner_index[i : i + clip_size] for i in range(super_clip_size) if i + clip_size <= super_clip_size
        ]
        for indices in frame_range:
            clip = [(super_clip_idx, t) for t in indices]
            clips_for_video.append(clip)
            
    # landmarks, images = crop_align_func(landmarks, images) # i : face, j : frame
    processed_clips = 0  # Track number of processed clips
    for clip in clips_for_video: 
        # Check if we've reached the clip limit
        if processed_clips >= clip_limit:
            logger.info(f"Reached clip limit of {clip_limit}, stopping processing")
            break
            
        # 각 자른 clip에 대해서 진행
        images = [data_storage[f"{i}_{j}_img"] for i, j in clip] # call cropped face images from data_storage, i : face, j : frame
        landmarks = [data_storage[f"{i}_{j}_ldm"] for i, j in clip] # call landmarks from data_storage, i : face, j : frame
        # landmark를 기준으로 crop align func을 진행 
        # 해당 함수가 clip에 있는 얼굴들의 landmark 평균을 기준으로 박스를 설정하고
        # 박스를 기준으로 crop align을 진행
        # 다르게 말하면, landmark 평균을 기준으로 박스의 geometry를 설정하고
        # 박스의 geometry는 고정한체로 얼굴이 움직이는 걸 찍었다 생각하면 됨
        # 다시 또 말하면, 카메라를 고정하고 사람이 움직이는 것을 찍은것처럼 
        # PPT 참조
        landmarks, images = crop_align_func(landmarks, images) # align the face images by landmarks in the clip
        i, j = clip[-1]
        k = super_clips[i]%clip_size 
        
        ##########################################################################
        # 코드 변경시 이 함수에서는 이부분만 변경할 것을 권고 !!!!!!!!!!!!!!!!!!!!!!!!
        # 특히, cv2.imwrite함수만 변경할 것을 추천
        ##########################################################################
        if (j+1)%clip_size==0: # if last frame number of the clip is multiple of clip_size, save all images in the clip
            # it means save face alignments in 8 frames in the video so that they don't overlap
            for f, (i,j) in enumerate(clip) : 
                cv2.imwrite(join(crop_path, f'{i:02}_{j:04}.png'), cv2.cvtColor(images[f], cv2.COLOR_BGR2RGB))   
        if j == super_clips[i]-1: # if the clip have last frame image, save all images in the clip
            if k!=0 : # if the clip is not multiple of clip_size, save the last k images in the clip
                # k is the number of frames that are not overlapped
                for l in range(clip_size-k,clip_size):
                    ci,cj = clip[l]
                    cv2.imwrite(join(crop_path, f'{ci:02}_{cj:04}.png'), cv2.cvtColor(images[l], cv2.COLOR_BGR2RGB)) # clip means face alignment images in 8 frames, non-overlap
        
        processed_clips += 1  # Increment processed clip counter
        ##########################################################################


if __name__ == '__main__':
    p = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    p.add_argument('--video_path','-i', type=str, default='/videos.mp4', help='path to input video')
    p.add_argument('--save_path','-s', type=str, default='/data/crop_face', help='path to save cropped faces')
    p.add_argument('--cachepath', '-c', type=str, default='/data/cache', help='path to cache detection results')
    p.add_argument('--clipsize','-l',type=int,default=32, help='number of frames in a clip')
    args = p.parse_args()
    video_path = args.video_path
    save_path = args.save_path
    cache_path = args.cachepath
    clip_size = args.clipsize
    
    crop_face_from_video(video_path, cache_path, crop_path, clip_size)
            
    
    
    
###################### reference ###################### 

# this code reference from FTCN Official Code in git hub
# link is  https://github.com/yinglinzheng/FTCN

# - Zheng, Y., Bao, J., Chen, D., Zeng, M., & Wen, F. (2021). Exploring Temporal Coherence for More General Video Face Forgery Detection. In Proceedings of the IEEE/CVF International Conference on Computer Vision (pp. 15044–15054).