Files
AIFSH-DHLive-ComfyUI/talkingface/preprocess.py
T
2024-08-06 10:28:01 +08:00

205 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import shutil
import numpy as np
import cv2
import os
import sys
import time
import argparse
from talkingface.run_utils import video_pts_process, concat_output_2binfile
from talkingface.mediapipe_utils import detect_face_mesh,detect_face
from talkingface.utils import main_keypoints_index,INDEX_LIPS
# 1、是否是mp4,宽高是否大于200,时长是否大于2s,可否成功转换为符合格式的mp4
# 2、面部关键点检测及是否可以构成循环视频
# 4、旋转矩阵、面部mask估计
# 5、验证文件完整性
dir_ = "data/asset/Actor"
def print_log(task_id, progress, status, Error, mode = 0):
'''
status: -1代表未开始, 0代表处理中, 1代表已完成, 2代表出错中断
progress: 0-1000, 进度千分比
'''
print("task_id: {}. progress: {:0>4d}. status: {}. mode: {}. Error: {}".format(task_id, progress, status, mode, Error))
sys.stdout.flush()
def check_step0(task_id, video_path):
try:
cap = cv2.VideoCapture(video_path)
vid_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) # 宽度
vid_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) # 高度
frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)
fps = cap.get(cv2.CAP_PROP_FPS)
cap.release()
if vid_width < 200 or vid_height < 200:
print_log(task_id, 0, 2, "video width/height < 200")
return 0
if frames < 2*fps:
print_log(task_id, 0, 2, "video duration < 2s")
return 0
os.makedirs(os.path.join(dir_, task_id), exist_ok=True)
front_video_path = os.path.join("data", "front.mp4")
scale = max(vid_width / 720., vid_height / 1280.)
if scale > 1:
new_width = int(vid_width / scale + 0.1)//2 * 2
new_height = int(vid_height / scale + 0.1)//2 * 2
ffmpeg_cmd = "ffmpeg -i {} -r 25 -ss 00:00:00 -t 00:02:00 -vf scale={}:{} -an -loglevel quiet -y {}".format(
video_path,new_width,new_height,front_video_path)
else:
ffmpeg_cmd = "ffmpeg -i {} -r 25 -ss 00:00:00 -t 00:02:00 -an -loglevel quiet -y {}".format(
video_path, front_video_path)
os.system(ffmpeg_cmd)
if not os.path.isfile(front_video_path):
return 0
return 1
except:
print_log(task_id, 0, 2, "video cant be opened")
return 0
def check_step1(task_id):
front_video_path = os.path.join("data", "front.mp4")
back_video_path = os.path.join("data", "back.mp4")
video_out_path = os.path.join(dir_, task_id, "video.mp4")
face_info_path = os.path.join(dir_, task_id, "video_info.bin")
preview_path = os.path.join(dir_, task_id, "preview.jpg")
if ExtractFromVideo(task_id, front_video_path) != 1:
shutil.rmtree(os.path.join(dir_, task_id))
return 0
ffmpeg_cmd = "ffmpeg -i {} -vf reverse -loglevel quiet -y {}".format(front_video_path, back_video_path)
os.system(ffmpeg_cmd)
ffmpeg_cmd = "ffmpeg -f concat -i {} -loglevel quiet -y {}".format("data/video_concat.txt", video_out_path)
os.system(ffmpeg_cmd)
ffmpeg_cmd = "ffmpeg -i {} -vf crop=w='min(iw\,ih)':h='min(iw\,ih)',scale=256:256,setsar=1 -vframes 1 {}".format(front_video_path, preview_path)
# ffmpeg_cmd = "ffmpeg -i {} -vf scale=256:-1 -loglevel quiet -y {}".format(front_video_path, preview_path)
os.system(ffmpeg_cmd)
if os.path.isfile(front_video_path):
os.remove(front_video_path)
if os.path.isfile(back_video_path):
os.remove(back_video_path)
if os.path.isfile(video_out_path) and os.path.isfile(face_info_path):
return 1
else:
return 0
# def check_step2(task_id, ):
# mat_list, pts_normalized_list, face_mask_pts = video_pts_process(pts_array_origin)
def ExtractFromVideo(task_id, front_video_path):
cap = cv2.VideoCapture(front_video_path)
if not cap.isOpened():
print_log(task_id, 0, 2, "front_video cant be opened by opencv")
return -1
vid_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) # 宽度
vid_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) # 高度
totalFrames = cap.get(cv2.CAP_PROP_FRAME_COUNT) # 总帧数
totalFrames = int(totalFrames)
pts_3d = np.zeros([totalFrames, 478, 3])
frame_index = 0
face_rect_list = []
start_time = time.time()
while cap.isOpened():
ret, frame = cap.read() # 按帧读取视频
# #到视频结尾时终止
if ret is False:
break
rect_2d = detect_face([frame])
rect = rect_2d[0]
tag_ = 1 if np.sum(rect) > 0 else 0
if frame_index == 0 and tag_ != 1:
print_log(task_id, 0, 2, "no face detected in first frame")
cap.release() # 释放视频对象
return 0
elif tag_ == 0: # 有时候人脸检测会失败,就用上一帧的结果替代这一帧的结果
rect = face_rect_list[-1]
face_rect_list.append(rect)
x_min = rect[0] * vid_width
y_min = rect[2] * vid_height
x_max = rect[1] * vid_width
y_max = rect[3] * vid_height
seq_w, seq_h = x_max - x_min, y_max - y_min
x_mid, y_mid = (x_min + x_max) / 2, (y_min + y_max) / 2
x_min = int(max(0, x_mid - seq_w * 0.65))
y_min = int(max(0, y_mid - seq_h * 0.4))
x_max = int(min(vid_width, x_mid + seq_w * 0.65))
y_max = int(min(vid_height, y_mid + seq_h * 0.8))
frame_face = frame[y_min:y_max, x_min:x_max]
frame_kps = detect_face_mesh([frame_face])[0]
if np.sum(frame_kps) == 0:
print_log(task_id, 0, 2, "Frame num {} keypoint error".format(frame_index))
cap.release() # 释放视频对象
return 0
pts_3d[frame_index] = frame_kps + np.array([x_min, y_min, 0])
frame_index += 1
if time.time() - start_time > 0.5:
progress = int(1000 * frame_index / totalFrames * 0.99)
print_log(task_id, progress, 0, "handling...")
start_time = time.time()
cap.release() # 释放视频对象
if type(pts_3d) is np.ndarray and len(pts_3d) == totalFrames:
pts_3d_main = pts_3d[:, main_keypoints_index]
mat_list, pts_normalized_list, face_pts_mean_personal, face_mask_pts_normalized = video_pts_process(pts_3d_main)
output = concat_output_2binfile(mat_list, pts_3d, face_pts_mean_personal, face_mask_pts_normalized)
# print(output.shape)
pts_normalized_list = np.array(pts_normalized_list)[:, INDEX_LIPS]
# 找出此模特正面人脸的嘴巴区域范围
x_max, x_min = np.max(pts_normalized_list[:, :, 0]), np.min(pts_normalized_list[:, :, 0])
y_max, y_min = np.max(pts_normalized_list[:, :, 1]), np.min(pts_normalized_list[:, :, 1])
y_min = y_min + (y_max - y_min) / 10.
first_line = np.zeros([406])
first_line[:4] = np.array([x_min,x_max,y_min,y_max])
# print(first_line)
#
# pts_2d_main = pts_3d[:, main_keypoints_index, :2].reshape(len(pts_3d), -1)
# smooth_array_ = np.array(mat_list).reshape(-1, 16)*100
#
# output = np.concatenate([smooth_array_, pts_2d_main], axis=1).astype(np.float32)
output = np.concatenate([first_line.reshape(1,-1), output], axis=0).astype(np.float32)
# print(smooth_array_.shape, pts_2d_main.shape, first_line.shape, output.shape)
face_info_path = os.path.join(dir_, task_id, "video_info.bin")
# np.savetxt(face_info_path, output, fmt='%.1f')
# print(222)
output.tofile(face_info_path)
return 1
else:
print_log(task_id, 0, 2, "keypoint cant be saved")
return 0
def check_step0_audio(task_id, video_path):
dir_ = "data/asset/Audio"
wav_path = os.path.join(dir_, task_id + ".wav")
ffmpeg_cmd = "ffmpeg -i {} -ac 1 -ar 16000 -loglevel quiet -y {}".format(
video_path, wav_path)
os.system(ffmpeg_cmd)
if not os.path.isfile(wav_path):
print_log(task_id, 0, 2, "audio convert failed", 2)
return 0
return 1
def new_task(task_id, task_mode, video_path):
# print(task_id, task_mode, video_path)
if task_mode == "0": # "actor"
print_log(task_id, 0, 0, "handling...")
if check_step0(task_id, video_path):
print_log(task_id, 0, 0, "handling...")
if check_step1(task_id):
print_log(task_id, 1000, 1, "process finished, click to confirm")
if task_mode == "2": # "audio"
print_log(task_id, 0, 0, "handling...", 2)
if check_step0_audio(task_id, video_path):
print_log(task_id, 1000, 1, "process finished, click to confirm", 2)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Inference code to preprocess videos')
parser.add_argument('--task_id', type=str, help='task_id')
parser.add_argument('--task_mode', type=str, help='task_mode')
parser.add_argument('--video_path', type=str, help='Filepath of video that contains faces to use')
args = parser.parse_args()
new_task(args.task_id, args.task_mode, args.video_path)