Post
Postech 24-WURF 연구노트(1/22~2/4) | Gihun Son

Postech 24-WURF 연구노트(1/22~2/4)

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

1/22:

무조건 “ffmpeg”를 사용할 때에는 아래 옵션 ‘copy’를 사용해야 한다.

  • ffmpeg로 clip을 딸 때는 인코딩이 되지 않는다는 정보를 봐서 안되는 줄 알았는데, 되고 있었다. 화질이 정말 많이 안좋아지고, 속도도 느렸다. (아래 옵션을 주니, clip을 만드는데 1분도 안걸렸다..)

지금 코드들에 ffmpeg를 쓰는 코드들을 전부 바꿔주어야 한다.!!!!

1
2
3
4
5
6
~~ffmpeg.input(raw_vid_path, ss=clip_info['start_sec']).output(
        out_path, 
        t=clip_info['duration'],
        vcodec='copy',  # H.264 video codec
        acodec='copy',      # AAC audio codec
        ).run()~~

moviepy를 사용

1
2
3
4
5
from moviepy.editor import VideoFileClip, concatenate_videoclips

def make_clip_video(path,save_path, start_t, end_t):
    clip_video = VideoFileClip(path).subclip(start_t, end_t)
    clip_video.write_videofile(save_path)
  • 위 ffmpeg를 사용하여 해상도를 유지할 때, 싱크가 맞지 않는 문제가 발생했다. 따라서 다른 api를 사용하여 동영상 편집을 해야 한다.

실험(Dataset Collection Pipeline) (korean)

  • 현재 frame별 score 정보를 통해 filtering할 수 있도록 코드를 작성하였는데, 유효할지는 모르겠다.

1. TalkNet만을 사용하여 Filtering

  • 1.1.Negative Cutting: negative상황이 3초를 넘기는 모든 순간을 cutting하고 padding하여 나타내는 방법(positive가 20미만은 것은 negative로 변경)
  • 1.2. Positive Cutting: positive가 35미만인 것을 negative로 전환하고, 처음 negative와 마지막 negative를 제외시키는 방법

2. WhisperX로 clip을 만든 후 Filtering

  • 1.1. Negative Cutting: negative상황이 3초를 넘기는 모든 순간을 cutting하고 padding하여 나타내는 방법(positive가 20미만은 것은 negative로 변경)
  • 1.2. Positive Cutting: positive가 35미만인 것을 negative로 전환하고, 처음 negative와 마지막 negative를 제외시키는 방법

실험의 터미널 순서

  1. full_video에 TalkNet(Ncut)파일 실행
  2. WhisperX video에 TalkNet(Ncut)파일 실행

다시 새롭게 TalkNet Process를 구성했다.

실험 결과는 이후에 살펴보아야 한다.

1/23:

TalkNet을 통과하더라도, 옆 얼굴로 말하는 사람을 filtering하지는 못했다. 정면을 detection하는 모듈로 fitering이 필요할 것 같다.(ex, landmark detector)

Untitled

  • Youtube script정보가 정확하지 않다는 것을 확인한 후 “WhisperX”를 통해 자막을 생성하고 video clip을 구한 후에, TalkNet으로 Filtering하는 방식을 생각했었다. 아래 코드와 같다.
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
	# Initialization
	Processed_dir='./cropped_Korean_full_Ncut/'
	video_dir='./Korean_full_Ncut/'
	video_list=os.listdir(video_dir)
	os.makedirs(Processed_dir)
	for video_name in video_list:
		args.pyaviPath = os.path.join(video_dir, 'pyavi')
		args.pyframesPath = os.path.join(video_dir, 'pyframes')
		args.pyworkPath = os.path.join(video_dir, 'pywork')
		args.pycropPath = os.path.join(video_dir, 'pycrop')

		os.makedirs(video_dir, exist_ok = True)
		os.makedirs(args.pyaviPath, exist_ok = True) # The path for the input video, input audio, output video
		os.makedirs(args.pyframesPath, exist_ok = True) # Save all the video frames
		os.makedirs(args.pyworkPath, exist_ok = True) # Save the results in this process by the pckl method
		os.makedirs(args.pycropPath, exist_ok = True) # Save the detected face clips (audio+video) in this process
		args.videoPath = os.path.join(video_dir, video_name )
		args.videoFilePath = os.path.join(args.pyaviPath, 'video.avi')
		print("??????????????",args.videoFilePath)
		#print("00000000000000000000000",args.videoPath)
		#print("1111111111111111111111111111",args.videoFilePath)
		# If duration did not set, extract the whole video, otherwise extract the video from 'args.start' to 'args.start + args.duration'
		if args.duration == 0:
			command = ("ffmpeg -y -i %s -qscale:v 2 -threads %d -async 1 -r 25 %s -loglevel panic" % \
				(args.videoPath, args.nDataLoaderThread, args.videoFilePath))
		else:
			command = ("ffmpeg -y -i %s -qscale:v 2 -threads %d -ss %.3f -to %.3f -async 1 -r 25 %s -loglevel panic" % \
				(args.videoPath, args.nDataLoaderThread, args.start, args.start + args.duration, args.videoFilePath))
		subprocess.call(command, shell=True, stdout=None)
		sys.stderr.write(time.strftime("%Y-%m-%d %H:%M:%S") + " Extract the video and save in %s \r\n" %(args.videoFilePath))
		
		# Extract audio
		args.audioFilePath = os.path.join(args.pyaviPath, 'audio.wav')
		command = ("ffmpeg -y -i %s -qscale:a 0 -ac 1 -vn -threads %d -ar 16000 %s -loglevel panic" % \
			(args.videoFilePath, args.nDataLoaderThread, args.audioFilePath))
		subprocess.call(command, shell=True, stdout=None)
		sys.stderr.write(time.strftime("%Y-%m-%d %H:%M:%S") + " Extract the audio and save in %s \r\n" %(args.audioFilePath))

		# Extract the video frames
		command = ("ffmpeg -y -i %s -qscale:v 2 -threads %d -f image2 %s -loglevel panic" % \
			(args.videoFilePath, args.nDataLoaderThread, os.path.join(args.pyframesPath, '%06d.jpg'))) 
		subprocess.call(command, shell=True, stdout=None)
		sys.stderr.write(time.strftime("%Y-%m-%d %H:%M:%S") + " Extract the frames and save in %s \r\n" %(args.pyframesPath))

		# Scene detection for the video frames
		scene = scene_detect(args)
		sys.stderr.write(time.strftime("%Y-%m-%d %H:%M:%S") + " Scene detection and save in %s \r\n" %(args.pyworkPath))	

		# Face detection for the video frames
		faces = inference_video(args)
		sys.stderr.write(time.strftime("%Y-%m-%d %H:%M:%S") + " Face detection and save in %s \r\n" %(args.pyworkPath))

		# Face tracking
		allTracks, vidTracks = [], []
		for shot in scene:
			if shot[1].frame_num - shot[0].frame_num >= args.minTrack: # Discard the shot frames less than minTrack frames
				allTracks.extend(track_shot(args, faces[shot[0].frame_num:shot[1].frame_num])) # 'frames' to present this tracks' timestep, 'bbox' presents the location of the faces
		sys.stderr.write(time.strftime("%Y-%m-%d %H:%M:%S") + " Face track and detected %d tracks \r\n" %len(allTracks))

		# Face clips cropping
		for ii, track in tqdm.tqdm(enumerate(allTracks), total = len(allTracks)):
			vidTracks.append(crop_video(args, track, os.path.join(args.pycropPath, '%05d'%ii)))
		savePath = os.path.join(args.pyworkPath, 'tracks.pckl')
		with open(savePath, 'wb') as fil:
			pickle.dump(vidTracks, fil)
		sys.stderr.write(time.strftime("%Y-%m-%d %H:%M:%S") + " Face Crop and saved in %s tracks \r\n" %args.pycropPath)
		fil = open(savePath, 'rb')
		vidTracks = pickle.load(fil)

		# Active Speaker Detection by TalkNet
		files = glob.glob("%s/*.avi"%args.pycropPath)
		files.sort()
		scores = evaluate_network(files, args)
		#print("11111111111111111111111111111",scores) #영상에 등장하는 사람들의 speaking score를 말해준다. (score가 가장 높은 사람들에 대해서만 crop하면 좋을 것 같다.), 또는 가장 높은 점수가 threshold이하일 때(사람이 안나온다는 뜻)
		#print("222222222222222222222222222",len(scores))
		#scores는 모든 clip의 score가 들어있는 list
		for num in range(len(scores)):
			#scores[num]은 num번째에 해당하는 video클립의 frame별 score를 나타낸다.
			print("score:",sum(scores[num]))
			print("len: ",len(scores[num]))
			###threshold가 0 미만일 때는 아예 고려하지 않는다.
			if sum(scores[num])<0:
				if num>99:
					clip_path=args.pycropPath+'/00'+str(num)+'.avi'
					os.remove(clip_path)
				elif num>9:
					clip_path=args.pycropPath+'/000'+str(num)+'.avi'
					os.remove(clip_path)
				else:
					clip_path=args.pycropPath+'/0000'+str(num)+'.avi'
					os.remove(clip_path)
				continue

			minus_frame_list=[]
			plus_frame_list=[]

			for i in range(len(scores[num])):
				if scores[num][i]<=0:
					print('-',end='')
					minus_frame_list.append(i)
				else:
					print('+',end='')
					plus_frame_list.append(i)

			plus_frame_bindings=find_continuous_sequences(plus_frame_list)
			minus_frame_bindings=find_continuous_sequences(minus_frame_list)
			##################################################
			#여기까지 코딩됨 짧은 +++는 ---로 변환 하고
			# 그 후에 ----  길이에 따라 remove
			for plus_bind in plus_frame_bindings:
				if len(plus_bind)<20:
					plus_frame_bindings.remove(plus_bind)
					minus_frame_bindings.append(plus_bind)

			plus_frame_list=[]
			minus_frame_list=[]
			for p_b in plus_frame_bindings:
				for p in p_b:
					plus_frame_list.append(p)
			plus_frame_list.sort()
			for m_b in minus_frame_bindings:
				for m in m_b:
					minus_frame_list.append(m)
			minus_frame_list.sort()

			plus_frame_bindings=find_continuous_sequences(plus_frame_list)
			minus_frame_bindings=find_continuous_sequences(minus_frame_list)

			########################################################
					#모두 -----거나 ++++++일 때의 대처 코드	
			if len(minus_frame_list)==0:
				if num>99:
					clip_path=args.pycropPath+'/00'+str(num)+'.avi'
					clip = VideoFileClip(clip_path)
					clip_length_frames = int(clip.duration * clip.fps)
					if clip_length_frames < 70: #대략 2초의 영상이 frame 70
						clip.close()
						os.remove(clip_path)
					else:
						clip.close()
						shutil.move(clip_path, Processed_dir+'00'+str(num)+'.avi')
						os.rename(Processed_dir+'00'+str(num)+'.avi',Processed_dir+str(num)+'_'+video_name)
				elif num>9:
					clip_path=args.pycropPath+'/000'+str(num)+'.avi'
					clip = VideoFileClip(clip_path)
					clip_length_frames = int(clip.duration * clip.fps)
					if clip_length_frames < 70: #대략 2초의 영상이 frame 70
						clip.close()
						os.remove(clip_path)
					else:
						clip.close()
						shutil.move(clip_path, Processed_dir+'000'+str(num)+'.avi')
						os.rename(Processed_dir+'000'+str(num)+'.avi',Processed_dir+str(num)+'_'+video_name)
				else:
					clip_path=args.pycropPath+'/0000'+str(num)+'.avi'
					clip = VideoFileClip(clip_path)
					clip_length_frames = int(clip.duration * clip.fps)
					if clip_length_frames < 70: #대략 2초의 영상이 fps 70
						clip.close()
						os.remove(clip_path)
					#print("00",clip_path,clip_out_path)
					else:
						clip.close()
						shutil.move(clip_path, Processed_dir+'0000'+str(num)+'.avi')
						os.rename(Processed_dir+'0000'+str(num)+'.avi',Processed_dir+str(num)+'_'+video_name)
				continue
			elif len(plus_frame_list)==0:
				if num>99:
					clip_path=args.pycropPath+'/00'+str(num)+'.avi'
					os.remove(clip_path)
				elif num>9:
					clip_path=args.pycropPath+'/000'+str(num)+'.avi'
					os.remove(clip_path)
				else:
					clip_path=args.pycropPath+'/0000'+str(num)+'.avi'
					os.remove(clip_path)
				continue

			sorted_rm_frame_list=[]
			if len(plus_frame_bindings)>1:
				if plus_frame_bindings[0][0]!=0:
					for pn in range(plus_frame_bindings[0][0]):
						sorted_rm_frame_list.append(pn)
				elif plus_frame_bindings[-1][-1]!=(len(scores[num])-1):
					for pn in range(plus_frame_bindings[-1][-1],len(scores[num])):
						sorted_rm_frame_list.append(pn)
			elif len(plus_frame_bindings)==1:
				if plus_frame_bindings[0][0]!=0:
					for pn in range(plus_frame_bindings[0][0]):
						sorted_rm_frame_list.append(pn)
				elif plus_frame_bindings[0][-1]!=(len(scores[num])-1):
					for pn in range(plus_frame_bindings[0][-1],len(scores[num])):
						sorted_rm_frame_list.append(pn)
			elif len(plus_frame_bindings)==0:
				if num>9:
					clip_path=args.pycropPath+'/000'+str(num)+'.avi'
					os.remove(clip_path)
				else:
					clip_path=args.pycropPath+'/0000'+str(num)+'.avi'
					os.remove(clip_path)
				continue
			
			if num>99:
				clip_path=args.pycropPath+'/00'+str(num)+'.avi'
				clip_out_path=args.pycropPath+'/processed_00'+str(num)+'.avi'
				#print("11",clip_path,clip_out_path)
				is_same=remove_frames(clip_path,clip_out_path,sorted_rm_frame_list)
				if is_same==2:
					os.remove(clip_out_path)
					continue
				shutil.move(clip_out_path, Processed_dir+'processed_00'+str(num)+'.avi')
				os.rename(Processed_dir+'processed_00'+str(num)+'.avi',Processed_dir+str(num)+'_'+video_name)
			elif num>9:
				clip_path=args.pycropPath+'/000'+str(num)+'.avi'
				clip_out_path=args.pycropPath+'/processed_000'+str(num)+'.avi'
				#print("11",clip_path,clip_out_path)
				is_same=remove_frames(clip_path,clip_out_path,sorted_rm_frame_list)
				if is_same==2:
					os.remove(clip_out_path)
					continue
				shutil.move(clip_out_path, Processed_dir+'processed_000'+str(num)+'.avi')
				os.rename(Processed_dir+'processed_000'+str(num)+'.avi',Processed_dir+str(num)+'_'+video_name)
			else:
				clip_path=args.pycropPath+'/0000'+str(num)+'.avi'
				clip_out_path=args.pycropPath+'/processed_0000'+str(num)+'.avi'
				#print("00",clip_path,clip_out_path)
				is_same=remove_frames(clip_path,clip_out_path,sorted_rm_frame_list)
				if is_same==2:
					os.remove(clip_out_path)
					continue
				shutil.move(clip_out_path, Processed_dir+'processed_0000'+str(num)+'.avi')
				os.rename(Processed_dir+'processed_0000'+str(num)+'.avi',Processed_dir+str(num)+'_'+video_name)

		shutil.rmtree(args.pyaviPath)
		shutil.rmtree(args.pyframesPath)
		shutil.rmtree(args.pyworkPath)
		shutil.rmtree(args.pycropPath)
	#break#뺴줘야 한다.

  • 급하게 데이터셋을 준비해야 한다는 생각 때문에, 급하게 코딩을 시작했고 오류가 발생할 때마다 깔끔하게 정리하는 것이 아니라, 그때 그때 임의적으로 코드를 수정해서 코드가 정말 복잡하다.. hard coding은 정말 좋지 않은 것 같다. 다음부터는 급하더라도 최대한 처음부터 깔끔하게 정리하면서 코딩을 해야 겠다. 위 코드는 추후에 깔끔하게 정리할 예정이다.

  • 우선 결론적으로 말하자면, 위 코드가 깔끔하지는 않은 것 같다. TalkNet은 동영상의 frame별로, talking state에 대한 “score”를 계산한다. 이를 이용해서 단순하게 ‘sum(scores)’값이 threshold를 넘는다고 clip을 데이터로 변환하는 것이 아니라, scores list안의 각 score값이 양수인지 음수인지에 따라 다르게 처리하였다. 그런데, 이렇게 frame단위로 처리하다보니 의도치 않게 툭툭 끊기는 부분이 있었고, 기준을 내가 정하다보니 깔끔하게 나오지 않는 data들도 꽤 있었다. 그래서 더 좋은 방법이 없는지 고민해보았다.

tesseract를 사용하자.

GitHub - thiagoalessio/tesseract-ocr-for-php: A wrapper to work with Tesseract OCR inside PHP.

  • tesseract는 ‘image’내의 ‘text image’를 text로 변환해준다. 다행히도 우리가 사용하는 데이터들에는 video내의 ‘text image’가 자막으로 존재한다. 따라서 자막이 같은 것끼리 묶으면 하나의 깔끔한 video clip을 얻을 수 있는 것이다.
  • 왜 이렇게 하는지 목적을 적지 않은 것 같다.

우리의 목표는 한 문장 단위로 끊기는 깔끔한 Dataset을 만드는 것이다.

  • youtube script 또는 whisperx를 사용하면 대략적인 한문장의 위치를 contact할 수 있고, 취득된 video clip들을 보면 한문장 정도의 speech를 한다. 하지만 문제는 정확하지 않은 정보를 사용하기 때문에, 말하는 도중에 끊기는 경우가 많다. 따라서 이를 해결하고자 “tesseract”를 사용하고자 한다.
  • 코드는 아래와 같다.
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
from PIL import Image
import pytesseract
import os
from moviepy.editor import VideoFileClip, concatenate_videoclips
import numpy as np
import shutil

def extract_frames(input_path, output_folder):
    # Load the video clip
    clip = VideoFileClip(input_path)

    # Create the output folder if it doesn't exist
    os.makedirs(output_folder, exist_ok=True)

    # Iterate through each frame and save as an image
    for i, frame in enumerate(clip.iter_frames(fps=clip.fps, dtype='uint8')):
        frame_path = os.path.join(output_folder, f"frame_{i:04d}.png")
        Image.fromarray(frame).save(frame_path)

    # Close the clip to free up resources
    clip.close()

def make_clip(input_path, output_folder, s_frame, e_frame):
    video_clip = VideoFileClip(input_path)
    fps = video_clip.fps
    frames_in_3_seconds = int(3 * fps)
    
    if frames_in_3_seconds > (e_frame - s_frame):
        return -100
    
    subclip = video_clip.subclip(s_frame / fps, e_frame / fps)
    subclip.write_videofile(os.path.join(output_folder, f"{video_id}_{clip_count}.mp4"), codec="libx264", audio_codec="aac")
    return 0

video_folder='./languages_fullvideo/Korean_full'
video_list=os.listdir(video_folder)
save_path='./Korean_full_split'
#video는 .mp4를 포함하는 동영상 이름(ex. fdija14d.mp4)

for video in video_list:
    video_id = video.split('.')[0]
    video_path = os.path.join(video_folder, video)
    frame_path = os.path.join(save_path, video_id, video_id+'_F')

    extract_frames(video_path, frame_path)

    frame_list = os.listdir(frame_path)
    clip_text = []
    current_text = []
    clip_frame_list = []
    start_frame = 0
    end_frame = 0
    clip_count = 0

    for f_n in range(len(frame_list)):
        current_frame = Image.open(os.path.join(frame_path, frame_list[f_n]))
        width, height = current_frame.size
        lower_height = height // 2
        cropped_image = current_frame.crop((0, lower_height, width, height))

        if f_n == 0:
            clip_text = pytesseract.image_to_string(cropped_image)
            start_frame = 0
            continue

        current_text = pytesseract.image_to_string(cropped_image)
        current_text = current_text.replace(" ", "")

        if len(clip_text) < 5:
            if len(current_text) < 5:
                clip_text = current_text
                start_frame = f_n + 1
                continue
            elif len(current_text) >= 5:
                clip_text = current_text
                start_frame = f_n
        elif len(clip_text) >= 5:
            if len(current_text) < 5:
                is_save = make_clip(video_path, os.path.join(save_path, video_id, f"{video_id}_{clip_count}.mp4"), start_frame, end_frame)
                clip_text = current_text
                if is_save == -100:
                    continue
                elif is_save == 0:
                    clip_count += 1
                    start_frame = f_n
                    end_frame = f_n
                continue

        if current_text[-2] == clip_text[-2] and current_text[0] == clip_text[0] and current_text[3] == clip_text[3]:
            end_frame += 1
        else:
            is_save = make_clip(video_path, os.path.join(save_path, video_id, f"{video_id}_{clip_count}.mp4"), start_frame, end_frame)
            clip_text = current_text
            if is_save == -100:
                continue
            elif is_save == 0:
                print("save clip!!! clip_name: ",video_id+'_'+str(clip_count)+'.mp4')
                clip_count += 1
                start_frame = f_n
                end_frame = f_n
    shutil.rmtree(frame_path)
  • 간단하게 짜보았는데, 간단하게 말하면, video를 frame단위로 끊은 후 각 frame의 text정보를 ‘tesseract’ api를 사용하여 추출한다. 그리고 자막은 frame이 변화하더라도 하나의 speech가 끝날 때 까지는 동일하게 유지되기 때문에, 동일한 자막(text)를 갖는 frame들을 하나의 video clip으로 만든다.
  • 이 때 frame을 통해 만드는 video clip의 길이가 3초 이상 되는 것들만을 사용한다.
  • 또한 text가 5개 이상인 것만을 사용한다.

현재까지 생각 중인 Data Collection Pipeline

  1. “tesseract”사용하여, sentence단위로 video clip collection

    +) 앞 frame과의 text비교를 전체로 해도 될 것 같다.(현재는 3개만 뽑아서 하고 있음)

    자막은 frame이 바뀌어도 전혀 바뀌지 않기 때문에, 괜찮을 것 같다는 생각을 했다. 또한 배경 글씨가 영향을 줄 것을 고려하여, frame의 전체에서 text detection을 하는 것이 아니라, 자막이 있는 부분만을 crop하여 사용하기 때문에 괜찮을 것 같다.

    ⇒ 실험적으로 판단해보자

  2. TalkNet을 통해 filtering한다. 이미 sentence단위로 filtering이 되었을 것이기 때문에, score가 (-)인 부분을 앞뒤로만 cutting하면 될 것 같다.
    • ‘sum(score)’가 0미만인 clip들은 아예 사용하지 않는 것이 깔끔하다.
  3. 얼굴이 정면을 보고 있지 않은 경우들이 꽤 있는 것 같다. landmark detection등을 사용하여 마지막으로 filtering이 필요할 것 같다.

언어마다 억양이 다르다?

우리나라 언어만 보더라도, 경상도 사람들이 일상적인 대화를 나누는 것을 보아도 화를 내는 것처럼 보인다고 한다. 독일어도 마찬가지도 다른 나라 사람들이 보기에 언어가 매우 화나보이는 경우가 많다고 한다. 이처럼 각 언어마다 억양과 소리의 높낮이에 따라 얼굴에 나타나는 expression이 다르고, 이를 학습할 수 있지 않을까? 라는 생각을 했다.

  • whisperX에서 tokenizer를 통해 언어를 판단하고, 그 언어에 pre-train된 wav2vec를 사용하는데, 이를 사용하여 더 정확하게 각 언어의 feature정보를 뽑을 수 있지 않을까?

Untitled 1

is there a way to check similarity between two full sentences in python?

1/24:

서로 다른 언어로 더빙되어 있는 영상

“This is Sparta” in 21 different languages

  • 위 영상들을 보면 같은 장면에 여러 나라들의 말로 더빙한 장면들이 이어져 있다. 연기에 따라 다른 점도 있겠지만, 기본적으로 같은 감정을 유지하면서 언어를 다르게 표현한 것이 목표였을 것이다. 그런데 언어에 따라 소리만 들어본다면 그 감정의 깊이가 다르게 느껴졌다.

How To Say “HAPPY NEW YEAR” in 60 Different Languages

Goal: Learning multilingual 3D talking head

  • We want 3D talking head to generate accurate lip movements on the multi-lingual speech
  • To do so,
    • Collect new dataset
    • Design a model or auxillary loss for the improvements
    • Design an evaluation metric for evaluating multilingual capability

(1) Collecting dataset

  • Collecting 2D video datasets
    • 언어 선정 필요
  • But, do we need pseudo 3D dataset?
    • [SPECTRE, CVPRW23] not god enough

      image

  • 일단 2D video dataset 모으는데 집중하기!

(2) Design a model or auxillary loss for improving multilingual capacity

  • Goal: use both VOCASET (GT 3D), and our collected dataset (2D only)
    • Semi-supervised learning으로 VOCASET과 우리 데이터셋이 적당히 녹여들도록 loss를 설계해줘야함
  • [GT 있는경우] Reconstruction loss
    • 기존 연구들
      • Faceformer, CVPR 2022

        Untitled

      • Codetalker, CVPR 2023

        Untitled 1

  • [GT 없는경우, 우리가 모은 데이터셋]
    • Use multilingual SyncNet as auxillary loss
    • Use emotion recognition model as perceptual loss
      • SPECTRE와 비슷
  • Multilingual을 위한 추가 시도
  • [Challenge] loss design이 까다로움…
    • 기존 VOCASET과 우리가 모은 데이터셋간의 loss 조합을 좀 생각해야함
    • 과연 2D video에서 오는 signal이 충분한가?
    • 데이터를 섞어야 하나, 혹은 학습 순서를 조정

(3) Design an evaluation metric for evaluating multilingual capacity

  • Human evaluation 필요 ⇒ 그 나라 사람들한테 결과에 대해서 평가를 받아야함
  • Automatic evaluation 제안 필요
    • AVSR 모델을 사용하여 WER 측정 할수 있을듯
    • https://github.com/facebookresearch/muavic

TODO list

  • [1] 2D dataset collecting 완료해야함 ⇒ 언어 일단 몇가지만 정해서 진행
    • 영어 / 일어(200/50), 중국어(200/50), 프랑스어(200/50), 인도이란어파(200/50), 샘어파(200/50)
  • [2] 기존 3D talking head model에 auxillary loss로 붙어서 성능 개선되는 것 확인해야함
    • 여러가지 loss (syncloss, emotion loss, 기타 등등) 붙여서 성능 개선
  • [3] SyncNet을 우리가 모은 데이터셋으로 finetuning해서 [2]의 syncloss 교체하기
  • [4] Evaluation metric 구상하기
  • [5] 언어와 관련된 분석이 뭐가 있을지에 대해서 생각 필요
  • 1/25(목):
  • 1/26(금): 1시쯤
  • 1/27(토): 저녁먹기 전쯤 (데드라인)

  • Faceformer나 codetalker가 비교적 약한 언어
    • 영어(vocaset)+4개정도(한국어제외)
      • {힌디어(인도), 쿠르드어(이란), 사카어(이란)}, 프랑스어, 중국어, 일본어, 히브리어, 아랍어
  • Faceformer, CodeTalker ⇒ vocaset으로 학습하고, inference하고, lip vertex error(evaluation) 측정하기
    • 코드 분석하고 친숙해지기

    (어디서 뭘 불러와야 되고, 모듈을 어떻게 떼어내야 하는지 등 완벽하게 알 수 있을 정도로 숙지해야 한다)

  • 2D video dataset 선정된 언어에 대해서 준비시켜야함
    • 5-7개정도 생각중
  • Faceformer나 codetalker중에 한가지 모델 선정해서 아래 내용들 진행해봐야함
    • [VOCASET만 사용해서] Wav2Vec 대신에 Multilingual AVHubert 사용하기 ⇒ test시에 lip vertex error 상에 어떤변화/정성적으로도
      • 성능에 이상 없을 경우 multilingual AVhubert로 교체
    • [VOCASET과 우리가 모은 데이터 모두 사용] SyncNet loss 추가해보기

    (SynNet의 입력은 speech(input)과 3D mesh mouth region(output)이다.

    • Option1
      • Vocaset으로 학습된 모델에 our2D만 가지고 finetuning
      • Regularization을 걸기 (VOCASET으로 학습한 weight이 그대로 유지되도록)
        • ex)$L=L_{sync}+L_{emotion}+ P_{previous}-P_{current} ^2$ 파라미터가 SynNet에 의한 학습에만 영향받는 것을 피하기 위함(P는 학습된 파라미터들)
    • Option2
      • $\alpha \times$ Reconstruction loss + $\beta \times$SyncNet loss
        • $\alpha=1, \beta=0$ if VOCASET is input
        • $\alpha=0, \beta=1$ if OUR2D video is input
      • VOCASET으로 학습시- INPUT: audio, identity ⇒ OUTPUT: 3Dmesh
      • OUR2D로 학습시- INPUT: audio, identity ⇒ OUTPUT: 3Dmesh ⇒ SyncNet
      • https://github.com/filby89/spectre 에서 사용한 loss 참고해보기

    주의) emotion loss를 측정할 때에는, video를 통해 구한 spectre의 camer parameter를 사용하여, 정면을 보고 있는 3D mesh를 video에 올린 후에 Loss측정을 해야 한다.

    • [VOCASET과 우리가 모은 데이터 모두 사용] emotion loss 추가해보기
      • Option1
        • 위와 동일한 방식
      • Option2
        • $\alpha \times$ Reconstruction loss + $\beta \times$SyncNet loss
          • $\alpha=1, \beta=0$ if VOCASET is input
          • $\alpha=0, \beta=1$ if OUR2D video is input
        • VOCASET으로 학습시- INPUT: audio, identity ⇒ OUTPUT: 3Dmesh
        • OUR2D로 학습시- INPUT: audio, identity ⇒ OUTPUT: 3Dmesh ⇒ SyncNet
        • https://github.com/filby89/spectre 에서 사용한 loss 그대로 사용해보기
  • 평가하는 방법
    • VOCASET으로 lip vertex error 측정하기
    • 실제로 다양한 언어(test set)을 만들어놓고, 정성적으로 판단하는 정도

Wac2Vec→ Multilingual AVHubert

M Avhubert model architecture

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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
model AVHubertModel(
  (feature_extractor_audio): SubModel(
    (proj): Linear(in_features=104, out_features=1024, bias=True)
  )
  (feature_extractor_video): SubModel(
    (resnet): ResEncoder(
      (frontend3D): Sequential(
        (0): Conv3d(1, 64, kernel_size=(5, 7, 7), stride=(1, 2, 2), padding=(2, 3, 3), bias=False)
        (1): BatchNorm3d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (2): PReLU(num_parameters=64)
        (3): MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 2, 2), padding=(0, 1, 1), dilation=1, ceil_mode=False)
      )
      (trunk): ResNet(
        (layer1): Sequential(
          (0): BasicBlock(
            (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (relu1): PReLU(num_parameters=64)
            (relu2): PReLU(num_parameters=64)
            (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          )
          (1): BasicBlock(
            (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (relu1): PReLU(num_parameters=64)
            (relu2): PReLU(num_parameters=64)
            (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          )
        )
        (layer2): Sequential(
          (0): BasicBlock(
            (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
            (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (relu1): PReLU(num_parameters=128)
            (relu2): PReLU(num_parameters=128)
            (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (downsample): Sequential(
              (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
              (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            )
          )
          (1): BasicBlock(
            (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (relu1): PReLU(num_parameters=128)
            (relu2): PReLU(num_parameters=128)
            (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          )
        )
        (layer3): Sequential(
          (0): BasicBlock(
            (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
            (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (relu1): PReLU(num_parameters=256)
            (relu2): PReLU(num_parameters=256)
            (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (downsample): Sequential(
              (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
              (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            )
          )
          (1): BasicBlock(
            (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (relu1): PReLU(num_parameters=256)
            (relu2): PReLU(num_parameters=256)
            (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          )
        )
        (layer4): Sequential(
          (0): BasicBlock(
            (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
            (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (relu1): PReLU(num_parameters=512)
            (relu2): PReLU(num_parameters=512)
            (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (downsample): Sequential(
              (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
              (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            )
          )
          (1): BasicBlock(
            (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (relu1): PReLU(num_parameters=512)
            (relu2): PReLU(num_parameters=512)
            (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
            (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          )
        )
        (avgpool): AdaptiveAvgPool2d(output_size=1)
      )
    )
    (proj): Linear(in_features=512, out_features=1024, bias=True)
  )
  (post_extract_proj): Linear(in_features=2048, out_features=1024, bias=True)
  (dropout_input): Dropout(p=0.0, inplace=False)
  (dropout_features): Dropout(p=0.0, inplace=False)
  (encoder): TransformerEncoder(
    (pos_conv): Sequential(
      (0): Conv1d(1024, 1024, kernel_size=(128,), stride=(1,), padding=(64,), groups=16)
      (1): SamePad()
      (2): GELU(approximate=none)
    )
    (layers): ModuleList(
      (0): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (1): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (2): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (3): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (4): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (5): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (6): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (7): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (8): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (9): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (10): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (11): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (12): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (13): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (14): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (15): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (16): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (17): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (18): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (19): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (20): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (21): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (22): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
      (23): TransformerSentenceEncoderLayer(
        (self_attn): MultiheadAttention(
          (dropout_module): FairseqDropout()
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
        )
        (dropout1): Dropout(p=0.1, inplace=False)
        (dropout2): Dropout(p=0.0, inplace=False)
        (dropout3): Dropout(p=0.1, inplace=False)
        (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (fc1): Linear(in_features=1024, out_features=4096, bias=True)
        (fc2): Linear(in_features=4096, out_features=1024, bias=True)
        (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
    )
    (layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
  )
  (layer_norm): LayerNorm((2048,), eps=1e-05, elementwise_affine=True)
  (final_proj): Linear(in_features=1024, out_features=512, bias=True)
)
Loading data...
INFO:av_hubert.avhubert.hubert:HubertModel Config: AVHubertConfig(_name=None, label_rate='${task.label_rate}', input_modality='${task.input_modality}', extractor_mode='default', encoder_layers=24, encoder_embed_dim=1024, encoder_ffn_embed_dim=4096, encoder_attention_heads=16, activation_fn='gelu', dropout=0.1, attention_dropout=0.1, activation_dropout=0.0, encoder_layerdrop=0.0, dropout_input=0.0, dropout_features=0.0, final_dim=0, untie_final_proj=False, layer_norm_first=False, conv_feature_layers='[(512,10,5)] + [(512,3,2)] * 4 + [(512,2,2)] * 2', conv_bias=False, logit_temp=0.1, target_glu=False, feature_grad_mult=1.0, mask_length_audio=10, mask_prob_audio=0.65, mask_length_image=10, mask_prob_image=0.65, mask_selection='static', mask_other=0, no_mask_overlap=False, mask_min_space=1, mask_channel_length=10, mask_channel_prob=0.0, mask_channel_selection='static', mask_channel_other=0, no_mask_channel_overlap=False, mask_channel_min_space=1, conv_pos=128, conv_pos_groups=16, latent_temp=(2, 0.5, 0.999995), skip_masked=False, skip_nomask=False, resnet_relu_type='prelu', resnet_weights=None, sim_type='cosine', sub_encoder_layers=0, audio_feat_dim=104, modality_dropout=0, audio_dropout=0, modality_fuse='concat', selection_type='same_other_seq', masking_type='input', decoder_embed_dim=768, decoder_ffn_embed_dim=3072, decoder_layers=6, decoder_layerdrop=0.0, decoder_attention_heads=4, decoder_learned_pos=False, decoder_normalize_before=False, no_token_positional_embeddings=False, decoder_dropout=0.1, decoder_attention_dropout=0.1, decoder_activation_dropout=0.0, max_target_positions=2048, share_decoder_input_output_embed=False, no_scale_embedding=True)
  1. 데이터셋 경로
  2. mavhubert로 training후 Inference

생각정리

1. 만약 2D video만을 사용한다면?

  • VOCASET으로 VQ-VAE를 학습시키고, stage-2까지 학습시킨다.
  • 그 후, 2D video를 통해 perceptual loss를 통해 학습한다.
    • emotion recognition model사용 등
  • 정량적인 Loss예시
    • 2D video에서 DECA, Spectre등의 모델로 camera pose parameter를 취득
    • 그 후, camera parameter만을 talking head model(CodeTalker)결과에 적용하여 2D로 projection(projection이 결국 rendering이다)
    • VOCASET에 비해, 2D video는 identity가 많다. 따라서 Lip Landmark를 취득하고, 비교하는 것이 아닌, ‘relative landmark loss’를 사용한다. (talking head가 만들어내는 head shape은 단 한가지 일 것이다, 다른 모델들 처럼, 반면 2D video에 등장하는 사람들은 매우 많다) (relative landmark loss를 구하는 방법은 DECA, Spectre등 사용한 모델을 참고하도록 해보자)( Landmark는 media pipe를 사용)
  • SynNet사용하여 sound와 3D를 통한 feature비교를 통해 loss설정

1. 데이터셋 filtering 코드작성 및 Data filtering

  • Landmark, AE등을 이용하여 정면만 볼 수 있도록
  • TalkNet을 통과했지만 아직 다른사람의 목소리가 들어간 경우가 있음(다시 한번 filtering)

[Mediapipe face mesh]

https://github.com/google/mediapipe/blob/master/mediapipe/python/solutions/face_mesh_connections.py (vertex정보 있음)

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
import mediapipe as mp
import cv2
import os
import time
from scipy.spatial.transform import Rotation as R
import numpy as np
BaseOptions = mp.tasks.BaseOptions
FaceLandmarker = mp.tasks.vision.FaceLandmarker
FaceLandmarkerOptions = mp.tasks.vision.FaceLandmarkerOptions
VisionRunningMode = mp.tasks.vision.RunningMode

model_path = '/local_data_2/urp2_gihun/face_landmarker.task'

# Create a face landmarker instance with the video mode:
options = FaceLandmarkerOptions(
    base_options=BaseOptions(model_asset_path=model_path),
    running_mode=VisionRunningMode.VIDEO,
    output_face_blendshapes=True,
    output_facial_transformation_matrixes=True
    )

with FaceLandmarker.create_from_options(options) as landmarker:
    path = '/local_data_2/urp2_gihun'
    filePath = os.path.join(path, "0_0NimiJ-ZIK0_1.mp4")
    print(filePath)

    if os.path.isfile(filePath):
        cap = cv2.VideoCapture(filePath)
    else:
        print("파일이 존재하지 않습니다.")
    # 프레임을 정수형으로 형 변환
    frameWidth = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))	# 영상의 넓이(가로) 프레임
    frameHeight = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))	# 영상의 높이(세로) 프레임
    frame_size = (frameWidth, frameHeight)
    print('frame_size={}'.format(frame_size))
    
    while True:
        retval, frame = cap.read()
        if not(retval):	# 프레임정보를 정상적으로 읽지 못하면
            break  # while문을 빠져나가기
         
        mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame)
        current_time_seconds = time.time()
        timestamp_msec = int(current_time_seconds * 1e6)
        face_landmarker_result = landmarker.detect_for_video(mp_image, timestamp_msec)

        trans_matrix=face_landmarker_result.facial_transformation_matrixes
        print("llll",trans_matrix)
        rotation_matrix=trans_matrix[0][:3,:3]
        r= R.from_matrix(rotation_matrix)
        Euler=r.as_euler('zyx', degrees=True)
        print("Euler:", Euler)
        break

        
    cv2.destroyAllWindows()

Untitled

Untitled 1

Untitled 2

Filtering과정

  1. 시간 초에 따라 video clip을 생성
    • 20초가 넘어가면 4등분, 15초가 넘어가면 3등분, 2초 미만은 삭제
  2. 잘린 video Clip을 TalkNet에 통과(전체 오디오(—++++—-)에서 (-)를 잘라내고 전체 video 중 말하지 않는(score가 -)인 frame의 비중이 30%가 넘어가면 삭제)
  3. 2번 과정까지 삭제되지 않은 video clip을 대상으로 Head Rotation측정

    [array([[ 9.51009631e-01, 1.74176589e-01, -2.55428523e-01, 1.03735483e+00], [-1.64764345e-01, 9.84628499e-01, 5.79677932e-02, -5.85783601e-01], [ 2.61598647e-01, -1.30424257e-02, 9.65089202e-01, -3.13258114e+01], [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])]

    • 위 행렬은 Mediapipe에서 뽑아낸 transformation matrix이다.
    • 위 행렬의 [:3,:3]이 rotation을 의미

    Untitled 3

    ## +) 입의 occlusion도 filtering도 해야 한다.

    • 마스크로 인해 입 전체가 가려진 경우에는 TalkNet으로 filtering이 되었지만, 마이크로 가리거나, 다른 요소로 인해 잘 보이지 않는 경우는 완전히 걸러지지는 못했다.
    • 원래는 Mediapipe에서 visibility와 presence를 제공한다고 되어있고, 실제로 출력되어서 이것을 이용하려고 했는데, 0.0만이 출력된다.(해당 오류를 해결하기 위해 같은 issue를 3시간동안 보며 수정해보았지만, 되지 않는다. 개발자들이 근본적인 해결을 하지 못한듯 하다)
    • 따라서 다른 간단한 모델을 사용해서, mouth region에 대한 occlusion을 해결해보려고 한다.
      • 적절한 모델을 찾을 필요성이 있다.
      • https://github.com/1adrianb/face-alignment
      • 위 모델의 api에서 landmark에 대한 confidence값을 output으로 받아서 mouth landmark의 confidence값을 이용한다. (68 landmark) confidence값이 정확하지 않다.

    Untitled 4

    filtering하는 것은 z축의 angle(head forward방향)을 기준으로 (x,y는 z보다는 threshold를 적게)

    (아주 잠깐 고개가 일정 threshold 이상 돌아갈 수도 있기 때문에, 일정 frame이상 threshold를 넘으면 사용하지 않는 pipeline으로 구성)

정리

  1. 20%이상 landmark 취득이 안되면 해당 clip삭제(고개가 50도 이상 돌아가면 잘 취득되지 않는 것을 확인)
  2. 1번 조건에 의해 filtering된 영상들 중 y angle 절대값의 평균이 40도 이상인 clip들 삭제 (x,z에 대해서도 30도로 그냥 조건을 주자)

2. Emoca에 구현되어 있는 Relative Lip Landmark Loss 구현하기

Untitled 5

+) AVHubert에 Me spectrogram적용하여 학습시켜보기(시도)

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