# !/usr/bin/env python # -*- coding: UTF-8 -*- import datetime import logging import os import torch from PIL import Image import numpy as np import cv2 import pickle import gc import shutil from huggingface_hub import hf_hub_download try: from moviepy.editor import VideoFileClip, AudioFileClip except: try: from moviepy import VideoFileClip, AudioFileClip except: from moviepy import * import random from .src.utils.mp_utils import LMKExtractor from .src.utils.motion_utils import motion_sync from .src.utils.util import save_videos_grid, crop_and_pad,crop_and_pad_rectangle,center_crop from .echomimic_v2.src.utils.dwpose_util import draw_pose_select_v2 from comfy.utils import common_upscale,ProgressBar import folder_paths from multiprocessing.pool import ThreadPool weight_dtype = torch.float16 cur_path = os.path.dirname(os.path.abspath(__file__)) device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu" tensorrt_lite= os.path.join(folder_paths.get_input_directory(),"tensorrt_lite") MAX_SIZE = 768 def process_video_v2(infer_image_pil,ref_image_pil, uploaded_audio,face_locator_tensor, W_change, H_change, start_idx, seed, context_frames, context_overlap, cfg, steps, sample_rate, fps, pipe,mask_len,origin_h,origin_w, save_video, audio_file_prefix,L,whisper_chunks): # audio_clip = AudioFileClip(audio_clip) # # audio_clip = audio_clip.set_duration(L / fps) if seed is not None and seed > -1: generator = torch.manual_seed(seed) else: generator = torch.manual_seed(random.randint(100, 1000000)) video = pipe( infer_image_pil, #uploaded_audio, face_locator_tensor, width=W_change, height=H_change, video_length=L, num_inference_steps=steps, guidance_scale=cfg, generator=generator, audio_sample_rate=sample_rate, context_frames=context_frames, fps=fps, context_overlap=context_overlap, start_idx=start_idx, whisper_chunks=whisper_chunks, ).videos final_length = min(video.shape[2],mask_len, L) video_sig = video[:, :, :final_length, :, :] output_file = os.path.join(folder_paths.output_directory, f"{audio_file_prefix}_echo.mp4") print(f"**** final_length is : {final_length} ****") if origin_h!=origin_w: ouput_list = save_videos_grid(video_sig, output_file, n_rows=1, fps=fps, save_video=save_video,size=(origin_w,origin_h),ref_image_pil=ref_image_pil) else: ouput_list = save_videos_grid(video_sig, output_file, n_rows=1, fps=fps, save_video=save_video) if save_video: output_video_path = os.path.join(folder_paths.output_directory, f"{audio_file_prefix}_audio.mp4") video_clip = VideoFileClip(output_file) audio_clip = AudioFileClip(uploaded_audio) final_clip = video_clip.set_audio(audio_clip) final_clip.write_videofile( output_video_path, codec="libx264", audio_codec="aac") print(f"**** saving{output_file} at {output_video_path} ****") video_clip.reader.close() audio_clip.close() final_clip.reader.close() return ouput_list def process_video(ref_image_pil, uploaded_audio, width, height, length, seed, face_locator_tensor,context_frames, context_overlap, cfg, steps, sample_rate, fps, pipe,save_video,mask_len, audio_file_prefix,whisper_chunks): if seed is not None and seed > -1: generator = torch.manual_seed(seed) else: generator = torch.manual_seed(random.randint(100, 1000000)) video = pipe( ref_image_pil, #uploaded_audio, c_face_locator_tensor=face_locator_tensor, width=width, height=height, video_length=length, num_inference_steps=steps, guidance_scale=cfg, generator=generator, audio_sample_rate=sample_rate, context_frames=context_frames, fps=fps, context_overlap=context_overlap, whisper_chunks=whisper_chunks, ).videos final_length = min(video.shape[2], mask_len, length) output_file = os.path.join(folder_paths.output_directory, f"{audio_file_prefix}_echo.mp4") print(f"**** final_length is : {final_length} ****") ouput_list = save_videos_grid(video, output_file, n_rows=1, fps=fps, save_video=save_video) if save_video: output_video_path = os.path.join(folder_paths.output_directory, f"{audio_file_prefix}_audio.mp4") video_clip = VideoFileClip(output_file) audio_clip = AudioFileClip(uploaded_audio) final_clip = video_clip.set_audio(audio_clip) final_clip.write_videofile( output_video_path, codec="libx264", audio_codec="aac") print(f"**** saving{output_file} at {output_video_path} ****") video_clip.reader.close() audio_clip.close() final_clip.reader.close() return ouput_list def motion_sync_main(vis, width, height, video_images, face_img,facecrop_dilation_ratio, audio_file_prefix): lmk_extractor = LMKExtractor() ref_det = lmk_extractor(face_img) #driver_video = os.path.join(folder_paths.input_directory, driver_video) # if audio_form_video: # audio_path = os.path.join(folder_paths.input_directory, f"{audio_file_prefix}_audio.wav") # video_clip = VideoFileClip(driver_video) # audio_clip = video_clip.audio # audio_clip.write_audiofile(audio_path) # video_clip.close() # audio_clip.close() video_len,_,_,_=video_images.size() if video_len<25: raise "input video has not much frames for driver,change your input video!" else: tensor_list = list(torch.chunk(video_images, chunks=video_len)) input_frames_cv2=[tensor2cv(tensor_upscale(i, width, height)) for i in tensor_list] # print(ref_det) sequence_driver_det = [] if input_frames_cv2: try: print("**** Starting process video ****") for frame in input_frames_cv2: result = lmk_extractor(frame) assert result is not None, "bad video, face not detected" sequence_driver_det.append(result) except: print("face detection failed") else: raise "input video error,change your input video!" print("**** motion sync lenght " f"{len(sequence_driver_det)} ****") if vis: if facecrop_dilation_ratio==0: facecrop_dilation_ratio=1 pose_frames_driver = [vis.draw_landmarks((width, height), i["lmks"],facecrop_dilation_ratio, normed=True) for i in sequence_driver_det] poses_add_driver = [(i * 0.5 + j * 0.5).clip(0, 255).astype(np.uint8) for i, j in zip(input_frames_cv2, pose_frames_driver)] #print(f"**** poses_add_driver is done in len : {len(poses_add_driver)} ****") save_dir = os.path.join(tensorrt_lite, audio_file_prefix) if not os.path.exists(save_dir): os.makedirs(save_dir) sequence_det_ms = motion_sync(sequence_driver_det, ref_det) for i in range(len(sequence_det_ms)): with open('{}/{}.pkl'.format(save_dir, i), 'wb') as file: pickle.dump(sequence_det_ms[i], file) print(f"**** motion_sync {save_dir} is done ****") else: #即便有文件夹,还是重新生成,避免出错 timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") save_dir=os.path.join(tensorrt_lite,f"{audio_file_prefix}_{timestamp}") os.makedirs(save_dir) sequence_det_ms = motion_sync(sequence_driver_det, ref_det) for i in range(len(sequence_det_ms)): with open('{}/{}.pkl'.format(save_dir, i), 'wb') as file: pickle.dump(sequence_det_ms[i], file) print(f"**** motion_sync {save_dir} is done ****") return save_dir,video_len def select_face(det_bboxes, probs): ## max face from faces that the prob is above 0.8 ## box: xyxy if det_bboxes is None or probs is None: return None filtered_bboxes = [] for bbox_i in range(len(det_bboxes)): if probs[bbox_i] > 0.8: filtered_bboxes.append(det_bboxes[bbox_i]) if len(filtered_bboxes) == 0: return None sorted_bboxes = sorted(filtered_bboxes, key=lambda x: (x[3] - x[1]) * (x[2] - x[0]), reverse=True) return sorted_bboxes[0] def find_directories(base_path): directories = [] for root, dirs, files in os.walk(base_path): for name in dirs: directories.append(name) return directories def download_weights(file_dir,repo_id,subfolder="",pt_name=""): if subfolder: file_path = os.path.join(file_dir,subfolder, pt_name) sub_dir=os.path.join(file_dir,subfolder) if not os.path.exists(sub_dir): os.makedirs(sub_dir) if not os.path.exists(file_path): file_path = hf_hub_download( repo_id=repo_id, subfolder=subfolder, filename=pt_name, local_dir = file_dir, ) return file_path else: file_path = os.path.join(file_dir, pt_name) if not os.path.exists(file_dir): os.makedirs(file_dir) if not os.path.exists(file_path): file_path = hf_hub_download( repo_id=repo_id, filename=pt_name, local_dir=file_dir, ) return file_path def pil2narry(img): img = torch.from_numpy(np.array(img).astype(np.float32) / 255.0).unsqueeze(0) return img def narry_list(list_in): for i in range(len(list_in)): value = list_in[i] modified_value = pil2narry(value) list_in[i] = modified_value return list_in def get_video_img(tensor): if tensor == None: return None outputs = [] for x in tensor: x = tensor_to_pil(x) outputs.append(x) yield outputs def gen_img_form_video(tensor): pil = [] for x in tensor: pil[x] = tensor_to_pil(x) yield pil def phi_list(list_in): for i in range(len(list_in)): value = list_in[i] list_in[i] = value return list_in def tensor_to_pil(tensor): image_np = tensor.squeeze().mul(255).clamp(0, 255).byte().numpy() image = Image.fromarray(image_np, mode='RGB') return image def nomarl_upscale(img_tensor, width, height): samples = img_tensor.movedim(-1, 1) img = common_upscale(samples, width, height, "nearest-exact", "center") samples = img.movedim(1, -1) img_pil = tensor_to_pil(samples) return img_pil def tensor_upscale(img_tensor, width, height): samples = img_tensor.movedim(-1, 1) img = common_upscale(samples, width, height, "nearest-exact", "center") samples = img.movedim(1, -1) return samples def tensor2cv(tensor_image): if len(tensor_image.shape)==4:# b hwc to hwc tensor_image=tensor_image.squeeze(0) if tensor_image.is_cuda: tensor_image = tensor_image.cpu() tensor_image=tensor_image.numpy() #反归一化 maxValue=tensor_image.max() tensor_image=tensor_image*255/maxValue img_cv2=np.uint8(tensor_image)#32 to uint8 img_cv2=cv2.cvtColor(img_cv2,cv2.COLOR_RGB2BGR) return img_cv2 def cvargb2tensor(img): assert type(img) == np.ndarray, 'the img type is {}, but ndarry expected'.format(type(img)) img = torch.from_numpy(img.transpose((2, 0, 1))) return img.float().div(255).unsqueeze(0) # 255也可以改为256 def cv2tensor(img): assert type(img) == np.ndarray, 'the img type is {}, but ndarry expected'.format(type(img)) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = torch.from_numpy(img.transpose((2, 0, 1))) return img.float().div(255).unsqueeze(0) # 255也可以改为256 def images_generator(img_list: list,): #get img size sizes = {} for image_ in img_list: if isinstance(image_,Image.Image): count = sizes.get(image_.size, 0) sizes[image_.size] = count + 1 elif isinstance(image_,np.ndarray): count = sizes.get(image_.shape[:2][::-1], 0) sizes[image_.shape[:2][::-1]] = count + 1 else: raise "unsupport image list,must be pil or cv2!!!" size = max(sizes.items(), key=lambda x: x[1])[0] yield size[0], size[1] # any to tensor def load_image(img_in): if isinstance(img_in, Image.Image): img_in=img_in.convert("RGB") i = np.array(img_in, dtype=np.float32) i = torch.from_numpy(i).div_(255) if i.shape[0] != size[1] or i.shape[1] != size[0]: i = torch.from_numpy(i).movedim(-1, 0).unsqueeze(0) i = common_upscale(i, size[0], size[1], "lanczos", "center") i = i.squeeze(0).movedim(0, -1).numpy() return i elif isinstance(img_in,np.ndarray): i=cv2.cvtColor(img_in,cv2.COLOR_BGR2RGB).astype(np.float32) i = torch.from_numpy(i).div_(255) #print(i.shape) return i else: raise "unsupport image list,must be pil,cv2 or tensor!!!" total_images = len(img_list) processed_images = 0 pbar = ProgressBar(total_images) images = map(load_image, img_list) try: prev_image = next(images) while True: next_image = next(images) yield prev_image processed_images += 1 pbar.update_absolute(processed_images, total_images) prev_image = next_image except StopIteration: pass if prev_image is not None: yield prev_image def load_images(img_list: list,): gen = images_generator(img_list) (width, height) = next(gen) images = torch.from_numpy(np.fromiter(gen, np.dtype((np.float32, (height, width, 3))))) if len(images) == 0: raise FileNotFoundError(f"No images could be loaded .") return images def tensor2pil(tensor): image_np = tensor.squeeze().mul(255).clamp(0, 255).byte().numpy() image = Image.fromarray(image_np, mode='RGB') return image def cf_tensor2cv(tensor,width, height): d1, _, _, _ = tensor.size() if d1 > 1: tensor_list = list(torch.chunk(tensor, chunks=d1)) tensor = [tensor_list][0] cr_tensor=tensor_upscale(tensor,width, height) cv_img=tensor2cv(cr_tensor) return cv_img def center_resize_pad(img, new_width, new_height):#为简化,new已是正方形 h, w = img.shape[:2] if w == h: if w == new_width: return img else: return cv2.resize(img, (new_width, new_height)) else: #蒙版也有可能不是正方形 if h > w: # 竖直图左右填充 s = max(h, w) f = np.zeros((s, s, 3), np.uint8) ax, ay = (s - img.shape[1]) // 2, (s - img.shape[0]) // 2 f[ay:img.shape[0] + ay, ax:ax + img.shape[1]] = img else: f = center_crop(img, h, h) return cv2.resize(f, (new_width, new_height)) def img_padding(height,width,ref_image_pil): output_max = max(height, width) img = tensor2cv(ref_image_pil) h, w = img.shape[:2] if h==w: return cv2.resize(img, (output_max,output_max), interpolation=cv2.INTER_AREA) else: if h > w: #竖直图左右填充 s = max(h, w) f = np.zeros((s, s, 3), np.uint8) ax, ay = (s - w) // 2, (s - h) // 2 f[ay:h + ay, ax:ax + w] = img else: f=center_crop(img, h, h) return cv2.resize(f, (output_max,output_max), interpolation=cv2.INTER_AREA) def estimate_ratio(keypoint: list,box_xy,length=None): x1, y1, x2, y2=box_xy bbox_width, bbox_height = x2 - x1, y2 - y1 left_eye_y = [] left_shoulder_y = [] left_elbow_y = [] for i, (name, (x, y, conf)) in enumerate(keypoint[0].items()): if name == "left_eye": if conf > 0.3: y_coord =int(y * bbox_height / 256) + y1 #获取坐标点在原图的绝对值 left_eye_y.append(y_coord) if name == "left_shoulder": if conf > 0.3: y_coord = int(y * bbox_height / 256) + y1 left_shoulder_y.append(y_coord) if name == "left_elbow": if conf > 0.3: y_coord = int(y * bbox_height / 256) + y1 left_elbow_y.append(y_coord) if left_eye_y and left_elbow_y: length=left_elbow_y[0] - left_eye_y[0] elif left_eye_y and left_shoulder_y and not left_elbow_y : length = left_shoulder_y[0] - left_eye_y[0] else: pass if left_eye_y: left_eye_y=[left_eye_y[0]] return length,left_eye_y def align_img(input_length, first_length, height, width, input_frames_cv2_first, input_left_eye_y, first_left_eye_y): ratio = input_length / first_length #603 [201] 679 [220] input_frames_cv2_first = cv2.resize(input_frames_cv2_first, (int(height * ratio), int(height * ratio)), interpolation=cv2.INTER_AREA) # 缩小 base_image=np.zeros((height, width,3), np.uint8) if input_length / height < first_length / height: # 输入图的人物占比要小,pose图需要缩小0.88对齐,#603 [201] 679 [220] reduced_image,pad_size=center_paste(base_image, input_frames_cv2_first) #中心粘贴,pad为黑边尺寸 eye_y=int(first_left_eye_y*ratio+pad_size[1]) #图片缩放后的绝对值 else: # pose图里人物的比例小于输入图,pose要放大 reduced_image=center_crop(input_frames_cv2_first, height, width) #中心裁切 h, w = input_frames_cv2_first.shape[:2] shift_y=(h-height)//2 #图片放大后裁切掉的边界 eye_y= int(first_left_eye_y*ratio-shift_y) move_ = -int(eye_y - input_left_eye_y) if eye_y>= int(input_left_eye_y) else int(input_left_eye_y- eye_y) #对齐眼睛 translation_matrix = np.float32([[1, 0, 0], [0, 1, move_]]) shifted_image = cv2.warpAffine(reduced_image, translation_matrix, (width, height)) return shifted_image def center_paste(img_b,img_f): b_h,b_w=img_b.shape[:2] f_h, f_w = img_f.shape[:2] x = (b_w - f_w) // 2 y = (b_h - f_h) // 2 # 确保坐标不会是负数 x = max(0, x) y = max(0, y) img_b[y:y + f_h, x:x + f_w] = img_f return img_b,(x,y) def affine_img(input_left_eye_y, first_left_eye_y, img): height,width=img.shape[:2] move_ = int(input_left_eye_y[0] - first_left_eye_y[0]) translation_matrix = np.float32([[1, 0, 0], [0, 1, move_]]) # y轴位移 shifted_image = cv2.warpAffine(img, translation_matrix, (width, height)) return shifted_image def convert_fps(src_path, tgt_path, tgt_fps=24, tgt_sr=16000): clip = VideoFileClip(src_path) new_clip = clip.set_fps(tgt_fps) if tgt_fps is not None: audio = new_clip.audio audio = audio.set_fps(tgt_sr) new_clip = new_clip.set_audio(audio) if '.mov' in tgt_path: tgt_path = tgt_path.replace('.mov', '.mp4') new_clip.write_videofile(tgt_path, codec='libx264', audio_codec='aac') def get_video_pose(visualizer, frames, sample_stride: int = 1, max_frame=None): # read input video #vr = decord.VideoReader(video_path, ctx=decord.cpu(0)) #sample_stride *= max(1, int(vr.get_avg_fps() / 24)) #frames = vr.get_batch(list(range(0, len(vr), sample_stride))).asnumpy() #frames=vr.get_batch(list(range(0, len(vr), sample_stride))).asnumpy() # print(frames[0]) if max_frame is not None: frames = frames[:max_frame] height, width, _ = frames[0].shape print("start get video pose list") detected_poses = [visualizer(frm) for frm in frames] visualizer.release_memory() return detected_poses, height, width, frames def resize_and_pad(img, max_size): img_new = np.zeros((max_size, max_size, 3)).astype('uint8') imh, imw = img.shape[0], img.shape[1] half = max_size // 2 if imh > imw: imh_new = max_size imw_new = int(round(imw / imh * imh_new)) half_w = imw_new // 2 rb, re = 0, max_size cb = half - half_w ce = cb + imw_new else: imw_new = max_size imh_new = int(round(imh / imw * imw_new)) half_h = imh_new // 2 cb, ce = 0, max_size rb = half - half_h re = rb + imh_new img_resize = cv2.resize(img, (imw_new, imh_new)) img_new[rb:re, cb:ce, :] = img_resize return img_new def resize_and_pad_param(imh, imw, max_size): half = max_size // 2 if imh > imw: imh_new = max_size imw_new = int(round(imw / imh * imh_new)) half_w = imw_new // 2 rb, re = 0, max_size cb = half - half_w ce = cb + imw_new else: imw_new = max_size imh_new = int(round(imh / imw * imw_new)) imh_new = max_size half_h = imh_new // 2 cb, ce = 0, max_size rb = half - half_h re = rb + imh_new return imh_new, imw_new, rb, re, cb, ce def get_pose_params(detected_poses, max_size): print('get_pose_params...') # pose rescale height = 768 width=768 w_min_all, w_max_all, h_min_all, h_max_all = [], [], [], [] mid_all = [] for num, detected_pose in enumerate(detected_poses): detected_poses[num]['num'] = num candidate_body = detected_pose['bodies']['candidate'] score_body = detected_pose['bodies']['score'] candidate_face = detected_pose['faces'] score_face = detected_pose['faces_score'] candidate_hand = detected_pose['hands'] score_hand = detected_pose['hands_score'] # face if candidate_face.shape[0] > 1: index = 0 candidate_face = candidate_face[index] score_face = score_face[index] detected_poses[num]['faces'] = candidate_face.reshape(1, candidate_face.shape[0], candidate_face.shape[1]) detected_poses[num]['faces_score'] = score_face.reshape(1, score_face.shape[0]) else: candidate_face = candidate_face[0] score_face = score_face[0] # body if score_body.shape[0] > 1: tmp_score = [] for k in range(0, score_body.shape[0]): tmp_score.append(score_body[k].mean()) index = np.argmax(tmp_score) candidate_body = candidate_body[index * 18:(index + 1) * 18, :] score_body = score_body[index] score_hand = score_hand[(index * 2):(index * 2 + 2), :] candidate_hand = candidate_hand[(index * 2):(index * 2 + 2), :, :] else: score_body = score_body[0] all_pose = np.concatenate((candidate_body, candidate_face)) all_score = np.concatenate((score_body, score_face)) all_pose = all_pose[all_score > 0.8] body_pose = np.concatenate((candidate_body,)) mid_ = body_pose[1, 0] face_pose = candidate_face hand_pose = candidate_hand h_min, h_max = np.min(face_pose[:, 1]), np.max(body_pose[:7, 1]) h_ = h_max - h_min mid_w = mid_ w_min = mid_w - h_ // 2 w_max = mid_w + h_ // 2 w_min_all.append(w_min) w_max_all.append(w_max) h_min_all.append(h_min) h_max_all.append(h_max) mid_all.append(mid_w) w_min = np.min(w_min_all) w_max = np.max(w_max_all) h_min = np.min(h_min_all) h_max = np.max(h_max_all) mid = np.mean(mid_all) margin_ratio = 0.25 h_margin = (h_max - h_min) * margin_ratio h_min = max(h_min - h_margin * 0.8, 0) h_max = min(h_max + h_margin * 0.1, 1) h_new = h_max - h_min h_min_real = int(h_min * height) h_max_real = int(h_max * height) mid_real = int(mid * width) height_new = h_max_real - h_min_real + 1 width_new = height_new w_min_real = mid_real - width_new // 2 if w_min_real < 0: w_min_real = 0 width_new = mid_real * 2 w_max_real = w_min_real + width_new w_min = w_min_real / width w_max = w_max_real / width imh_new, imw_new, rb, re, cb, ce = resize_and_pad_param(height_new, width_new, max_size) res = {'draw_pose_params': [imh_new, imw_new, rb, re, cb, ce], 'pose_params': [w_min, w_max, h_min, h_max], 'video_params': [h_min_real, h_max_real, w_min_real, w_max_real], } return res def save_pose_params_item(input_items): detected_pose, pose_params, draw_pose_params, save_dir = input_items w_min, w_max, h_min, h_max = pose_params num = detected_pose['num'] candidate_body = detected_pose['bodies']['candidate'] candidate_face = detected_pose['faces'][0] candidate_hand = detected_pose['hands'] candidate_body[:, 0] = (candidate_body[:, 0] - w_min) / (w_max - w_min) candidate_body[:, 1] = (candidate_body[:, 1] - h_min) / (h_max - h_min) candidate_face[:, 0] = (candidate_face[:, 0] - w_min) / (w_max - w_min) candidate_face[:, 1] = (candidate_face[:, 1] - h_min) / (h_max - h_min) candidate_hand[:, :, 0] = (candidate_hand[:, :, 0] - w_min) / (w_max - w_min) candidate_hand[:, :, 1] = (candidate_hand[:, :, 1] - h_min) / (h_max - h_min) detected_pose['bodies']['candidate'] = candidate_body detected_pose['faces'] = candidate_face.reshape(1, candidate_face.shape[0], candidate_face.shape[1]) detected_pose['hands'] = candidate_hand detected_pose['draw_pose_params'] = draw_pose_params np.save(save_dir + '/' + str(num) + '.npy', detected_pose) def save_pose_params(detected_poses, pose_params, draw_pose_params, save_dir): #save_dir = ori_video_path.replace('video', 'pose/') if not os.path.exists(save_dir): os.makedirs(save_dir) input_list = [] for i, detected_pose in enumerate(detected_poses): input_list.append([detected_pose, pose_params, draw_pose_params, save_dir]) pool = ThreadPool(8) pool.map(save_pose_params_item, input_list) pool.close() pool.join() return save_dir def get_img_pose(visualizer, img_path: str, sample_stride: int = 1, max_frame=None): # read input img frame = cv2.imread(img_path) height, width, _ = frame.shape short_size = min(height, width) resize_ratio = max(MAX_SIZE / short_size, 1.0) frame = cv2.resize(frame, (int(resize_ratio * width), int(resize_ratio * height))) height, width, _ = frame.shape detected_poses = [visualizer(frame)] visualizer.release_memory() return detected_poses, height, width, frame def save_aligned_img(ori_frame, video_params, max_size): h_min_real, h_max_real, w_min_real, w_max_real = video_params img = ori_frame[h_min_real:h_max_real, w_min_real:w_max_real, :] img_aligened = resize_and_pad(img, max_size=max_size) print('aligned img shape:', img_aligened.shape) save_dir = './assets/refimg_aligned' os.makedirs(save_dir, exist_ok=True) save_path = os.path.join(save_dir, 'aligned.png') cv2.imwrite(save_path, img_aligened) return save_path