Post
Postech 24-WURF 연구 노트(1/18~1/21) | Gihun Son

Postech 24-WURF 연구 노트(1/18~1/21)

“연구를 진행하며 정리한 공부 내용과 생각들”

“Data collecting pipeline”

1. youtube_script_api사용하여, script정보로 json파일 생성

  • youtube script에서 정보를 처리하는 코드 작성
    • 유튜브 채널의 id를 이용해서, 채널 내의 모든 동영상에 대한 id를 알 수 있고, 동영상 id를 통해 script정보를 가져온다.
    • 유튜브 채널이 아닌, 재생목록을 통해서도 가능하다.

Untitled

  • json파일 Ex)

Untitled 1

  • 현재 각 언어별 json파일 생성 완료

2. Video Clip download

  • 실패했던 코딩
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
# -*- coding: utf-8 -*-

import os, sys
import argparse
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"  # Arrange GPU devices starting from 0
os.environ["CUDA_VISIBLE_DEVICES"]= "3"  # Set the GPU 2 to use
import torch
import numpy as np
import cv2
from skimage.transform import estimate_transform, warp, resize, rescale
import scipy.io
import collections
from tqdm import tqdm
from datasets.data_utils import landmarks_interpolate
from src.spectre import SPECTRE
from config import cfg as spectre_cfg
from src.utils.util import tensor2video
import torchvision

import json
from collections import OrderedDict
from googleapiclient.discovery import build
from youtube_transcript_api import YouTubeTranscriptApi
from external.Visual_Speech_Recognition_for_Multiple_Languages.tracker.face_tracker import FaceTracker
from external.Visual_Speech_Recognition_for_Multiple_Languages.tracker.utils import get_landmarks
from yt_dlp import YoutubeDL

import ffmpeg

def extract_frames(video_path, detect_landmarks=True):
    #video_path: "samples/LRS3/PbgB2TaYhio_00007.mp4"
    #videofolder: "samples/LRS3/PbgB2TaYhio_00007"
    videofolder = os.path.splitext(video_path)[0]
    #print(videofolder,"9949494949")
    os.makedirs(videofolder, exist_ok=True)
    #cv2.VideoCapture는 성공하면 True를, 실패하면 False를 반환
    #video의 frame들을 읽어온다.
    vidcap = cv2.VideoCapture(video_path)#video를 open

    
    if detect_landmarks is True:
        face_tracker = FaceTracker()

    imagepath_list = []
    count = 0

    face_info = collections.defaultdict(list)

    #input video의 fps를 알게 해준다.
    #demo에서 사용하는 vido의 경우 25fps를 갖는다. video의 길이가 1초 조금 넘기 때문에 총 35frame인 것을 알 수 있다.
    fps = vidcap.get(cv2.CAP_PROP_FPS)

    #success1, image1 = vidcap.read()
    #print("0000000000",success1,"000000",image1.shape)
    
    #tqdm은 진행상황을 보여줄 수 있는 라이브러리, pbar는 bar형태로 보여준다.
    with tqdm(total=int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))) as pbar:
        while True:
            #.read()를 통해 각 frame에 대한 정보를 획득한다.
            #.read()성공 여부와 각 frame을 반환한다. 여기서 frame은 HxWxC의 image로 반환된다.
            success, image = vidcap.read()
            if not success:
                break

            if detect_landmarks is True:
                #face_detector를 통해 15개의 값을 얻는다. bbox인데, 정확하게 어떤 기준으로 추출되는 것인지는 이해하지 못했다.
                detected_faces = face_tracker.face_detector(image, rgb=False)
                # -- face alignment
                #68개의 landmark 좌표(1,68,2), 각 landmark에 대한 score(1,68)
                landmarks, scores = face_tracker.landmark_detector(image, detected_faces, rgb=False)
                #print("landmark:",landmarks.shape,scores.shape)
                

                ###############################################################################################
                #옆면을 보고 있어도 landmarks값은 68개로 고정된다. mask로 가려지거나 옆면을 보는 경우에 어떻게 filtering할지 생각필요
                #if None in landmarks:
                    #print("???????????????????????????????????????????????")
                #####################################################################

                face_info['bbox'].append(detected_faces)
                face_info['landmarks'].append(landmarks)
                face_info['landmarks_scores'].append(scores)

            #현재 frame을 image파일 형태로 저장한다.
            imagepath = os.path.join(videofolder, f'{count:06d}.jpg')
            cv2.imwrite(imagepath, image)  # save frame as JPEG file
            count += 1 #count를 통해 image의 수, 즉 frame 수를 알 수 있을 것이다.
            #frame의 저장 경로를 list형태로 저장한다. 총 35frame video이므로, 35개의 경로가 저장될 것이다.
            #print('imagepath:',imagepath)
            imagepath_list.append(imagepath)
            pbar.update(1)
            pbar.set_description("Preprocessing frame %d" % count)

    #landmarks에는 각 frame(35개)별 landmark들을 list형태로 저장한 변수이다.
    landmarks = get_landmarks(face_info)
    #print('landmarks length:',len(landmarks))
    print('video frames are stored in {}'.format(videofolder))
    return imagepath_list, landmarks, videofolder, fps

def crop_face(frame, landmarks, scale=1.0):
    image_size = 224
    left = np.min(landmarks[:, 0])
    right = np.max(landmarks[:, 0])
    top = np.min(landmarks[:, 1])
    bottom = np.max(landmarks[:, 1])

    h, w, _ = frame.shape
    old_size = (right - left + bottom - top) / 2
    center = np.array([right - (right - left) / 2.0, bottom - (bottom - top) / 2.0])

    size = int(old_size * scale)

    src_pts = np.array([[center[0] - size / 2, center[1] - size / 2], [center[0] - size / 2, center[1] + size / 2],
                        [center[0] + size / 2, center[1] - size / 2]])
    DST_PTS = np.array([[0, 0], [0, image_size - 1], [image_size - 1, 0]])
    tform = estimate_transform('similarity', src_pts, DST_PTS)
    #print("tform: ",tform)
    return tform

def createDirectory(directory):
    try:
        if not os.path.exists(directory):
            os.makedirs(directory)
    except OSError:
        print("Error: Failed to create the directory.")

json_path1 = './French.json'  # json file path
raw_vid_root1 = './French'  # download raw video path

with open(json_path1) as f:
    data_dict1=json.load(f)
is_None=0
for video_id, clips in data_dict1.items():
    url="https://www.youtube.com/watch?v=" + video_id
    #your_instance = YourClass(url="https://www.youtube.com/watch?v=" + video_id, video_full_path=raw_vid_root1+'/'+video_id+'.mp4')
    with YoutubeDL({'outtmpl':f'{raw_vid_root1}/{video_id}',
                    'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best'}) as ydl:
        ydl.download(url)
        
    #download(raw_vid_root2+'/'+video_id+'.mp4',video_id)
    #your_instance.download_video_high_quality(progress_call_back)
    for clip_name, data in clips['clips'].items():
        #####################################################
        if '[' in data['text'] or ']' in data['text']: #음악 또는 효과음은 대괄호로 표시되기 때문
            continue
        ##################################################
        raw_vid_path = os.path.join(raw_vid_root1, video_id + ".mp4")
        out_path = os.path.join('./French', video_id)+'/'+clip_name+'.mp4'
        createDirectory(os.path.join('./French', video_id))
        ffmpeg.input(raw_vid_path, ss=data['start_sec']).output(out_path, t=data['duration']).run()
        #clip_path=process_ffmpeg1(video_id,raw_vid_path,clip_name, data['start_sec'],data['end_sec'])
        #print("111111111",clip_path)
        image_paths, landmarks, videofolder, fps = extract_frames(out_path, detect_landmarks=True)
        landmarks = landmarks_interpolate(landmarks)
        original_video_length = len(image_paths)
        i=0
        images_list = []
        for i_path in image_paths:
            frame = cv2.imread(i_path)
            frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
            try:
                kpt=landmarks[i]
            except:
                os.remove(i_path)
                is_None=1
                break
            tform = crop_face(frame,kpt,scale=1.6)
            cropped_image = warp(frame, tform.inverse, output_shape=(224, 224))
            #print(cropped_image.shape)
            cropped_image_tensor = torch.from_numpy(cropped_image).unsqueeze(0)
            images_list.append(cropped_image_tensor)
            #print(images_list)
            print(i_path)
            os.remove(i_path)
        if is_None==1:
            is_None=0
            os.remove('./French/'+video_id+'/'+clip_name+".mp4")
            continue
        vid_orig = tensor2video(torch.cat(images_list, dim=0))
        #print("oooooooooooooooooooooo",vid_orig.shape)
        torchvision.io.write_video('./French/'+video_id+'/'+clip_name+"_crop.mp4", vid_orig, fps=fps)
        os.remove('./French/'+video_id+'/'+clip_name+".mp4")
        os.rmdir('./French/'+video_id+'/'+clip_name)

위 코드는 만들었던 json파일의 정보를 통해, 구간 별 video clip을 다운로드하고, “spectre”모델의 cropped process를 사용해서 cropping까지 하려고 작성한 코드이다.

Untitled 2

그러나 위 결과처럼 제대로 tracking이 되지 않았다.

Untitled 3

Untitled 4

정확하게 말하면 tracking은 잘 되었지만, 화면 전환에 대해 제대로 된 대응이 되지 못했다. 그래서 화면 전환이 되면, 이전에 나오던 사람의 face region bbox에 대해 cropping하는 것이다. 또한 위의 오른쪽 사진처럼 두 사람이 나오는 상황에서 말하는 사람이 아닌, 말하지 않는 사람에 대해 cropping할 때도 있었다.. 이 결과를 미리 확인해보지 못했다.

https://github.com/TaoRuijie/TalkNet-ASD

위 TalkNet은 말하는 사람을 detection하여 data filtering에 사용하려고 했던 모델이다. 그런데, 내가 face region이 crop된다는 사실을 미처 생각하지 못하고, 이전 코드에서 발생하는 오류(youtube_dl, cropping face 등)들을 해결하느라 하루를 소비했다..

  • 따라서 먼저, json파일에 따라 video clip들을 다운로드하였다.

Untitled 5

(빠르게 코딩한다고 코드가 지저분한 부분이 많다. 우선 데이터 취득을 다 한 후에 코드 정리를 전체적으로 깔끔하게 해야 한다.)

  • 아래는 다운 받은 동영상 예시이다.(video id별로 정렬하도록 코딩했다)

Untitled 6

3. TalkNet을 사용하여 Talking Face에 대한 cropping

  • 기존 demoTalkNet.py의 코드를 변형시켰다. 각 clip별로 돌아가도록 하였고, 모델의 특징은 clip에 등장하는 모든 사람의 face region을 crop한 이후, 말하고 있는지 안하고 있는지를 score로 나타낸다. 따라서 가장 주요하게 추가한 코드는 아래와 같다.

Untitled 7

각 사람의 score가 시간축으로 나타난다. 따라서 score가 높을수록 말하는 비중이 높은 것이다. clip에 등장하는 사람들 중 score가 가장 높을 뿐만 아니라, threshold(나는 30으로 설정)보다 높은 clip에 대해서만 취득을 한다. 만약 모든 score가 0미만이라면 그 video clip안에는 말하는 사람이 없다는 것이다.

Data Collecting Progress

German(우선 정지)

Video id: “UYwS6lcY-L0”까지 video clip 다운로드 완료(German data의 20%)

json파일 전체 길이: 1083756 line / 다운로드 완료 데이터: 170000 line

Spanish(우선 정지)

Video id: “2_Bot-EYiRU”까지 video clip 다운로드 완료(Spanish data의 35%)

json파일 전체 길이: 475534 line / 다운로드 완료 데이터: 160000 line

French

모든 Video clip 취득 완료

json파일 전체 길이: 122328 line

English

모든 Video clip 취득 완료

json파일 전체 길이: 109840line

Korean

Video clip전체 취득 완료

json파일 전체 길이: 17922 line

현재 “TalkNet”사용하여 Data Filtering진행 중

Chinese

취득 중(whisper X사용해야 함)

whisper x사용해서 video clip만드는중

1/20 TalkNet통과 예정

Japanese

Video clip전체 취득 완료

json파일 전체 길이: 5708 line

현재 “TalkNet”사용하여 Data Filtering진행 중

Hindi

Video clip전체 취득 완료

json파일 전체 길이: 1488 line

현재 “TalkNet”사용하여 Data Filtering진행 중

어족에 대한 자료&Reference

https://ko.wikipedia.org/wiki/어족

https://ko.wikipedia.org/wiki/인도유럽조어

어족에 따라 분류!

비슷한 발음을 갖는 언어를 언어학적으로 분류하여 학습시킨 후, 나중에 결과 분석에 사용하면 좋을 것 같다.

bbox 더넓게(CelebV-HQ 정도)

https://github.com/m-bain/whisperX (chinese에 대해)

  • 우선 많은 언어들에 대해서 해놓자

Data Collecting Progress

Untitled 8

  • 미처 확인을 못했다.. Script에 나와있는 시간을 보면 겹쳐있는데, duration을 기준으로 clip을 만들어서, 여러 데이터들이 겹쳐서 만들어졌다.. TalkNet결과에서 같은 영상들이 계속 나와서 확인을 해보니, Script에 문제가 있었다.. 꼼꼼하게 확인하지 못한 내 잘못이다.

Untitled 9

  • 따라서, 현재 ‘start_sec’부터 다음 clip의 ‘start_sec’까지 clip video를 만들고, 마지막 clip에서만 ‘duration’을 기준으로 clip을 만들 수 있도록 새롭게 코드를 짰다..
  • 데이터를 다시 생성해야 하고, 생성할 동안 어족 자료나 다른 언어에 대해서도 데이터 취득을 고민하고 있어야 할 것 같다.

German

Video id: “UYwS6lcY-L0”까지 video clip 다운로드 완료(German data의 20%)

json파일 전체 길이: 1083756 line / 다운로드 완료 데이터: 170000 line

Spanish

Video id: “2_Bot-EYiRU”까지 video clip 다운로드 완료(Spanish data의 35%)

json파일 전체 길이: 475534 line / 다운로드 완료 데이터: 160000 line

French

모든 Video clip 취득 완료

json파일 전체 길이: 122328 line

English

모든 Video clip 취득 완료

json파일 전체 길이: 109840line

Korean

Video clip전체 취득 완료

json파일 전체 길이: 17922 line

현재 “TalkNet”사용하여 Data Filtering진행 중

Chinese

취득 중(whisper X사용해야 함)

whisper x사용해서 video clip만드는중

1/20 TalkNet통과 예정

Japanese

Video clip전체 취득 완료

json파일 전체 길이: 5708 line

현재 “TalkNet”사용하여 Data Filtering진행 중

Hindi

Video clip전체 취득 완료

json파일 전체 길이: 1488 line

현재 “TalkNet”사용하여 Data Filtering진행 중

This post is licensed under CC BY 4.0 by the author.