# !/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 sys import pickle import gc 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 tqdm import tqdm 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") def process_video_v2(ref_image_pil, uploaded_audio, width, height, length, seed, context_frames, context_overlap, cfg, steps, sample_rate, fps, pipe, save_video, pose_dir, audio_file_prefix,visualizer,video_images): origin_h = height origin_w = width # 处理输入图片的尺寸 panding_img=img_padding(height, width, ref_image_pil) # 不管输出图片是何种尺寸,为保证图片质量,将输入图片转为为正方形,横裁切,竖填充,长宽为输出尺寸最大 infer_image_pil=Image.fromarray(cv2.cvtColor(panding_img,cv2.COLOR_BGR2RGB)) #将高宽改成最大图幅,方便裁切 height = max(height,width) width = max(height, width) if visualizer and isinstance(video_images,torch.Tensor): logging.info("***** start infer video to npy files for drive pose ! ***** ") video_len, _, _, _ = video_images.size() if video_len < 50: 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 = [img_padding(height,width,i) for i in tensor_list] pose_dir = os.path.join(tensorrt_lite, audio_file_prefix) if not os.path.exists(pose_dir): os.makedirs(pose_dir) else: timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") pose_dir = os.path.join(tensorrt_lite, f"{audio_file_prefix}_{timestamp}") os.makedirs(pose_dir) visualizer.move_to_cuda() # 首帧手势对齐输入图片,keypoint数据左眼,肩膀及手肘, _, first_key, first_box_xy = visualizer(np.asarray(input_frames_cv2[0]),None) _, input_key, input_box_xy = visualizer(np.asarray(panding_img),None) first_length,first_left_eye_y=estimate_ratio(first_key,first_box_xy) input_length,input_left_eye_y = estimate_ratio(input_key,input_box_xy) #print(first_length,first_left_eye_y,input_length,input_left_eye_y) #160.0 [236.0] 158.0 [151.0] 眼睛高度为绝对值 if first_length and first_left_eye_y and input_length and input_left_eye_y: if abs(input_length / height - first_length / height) > 0.005: # 比例不同须基于输入图片对齐 logging.info( "Starting the first frame gesture alignment based on the input image *** 基于输入图片,开始首帧手势缩放对齐 !") input_left_eye_y_=input_left_eye_y[0] first_left_eye_y_=first_left_eye_y[0] input_frames_cv2=[align_img(input_length, first_length, height, width, i, input_left_eye_y_, first_left_eye_y_) for i in input_frames_cv2] else:# 人体比例接近,但是高度不对,也需要对齐 logging.info( "Starting the first frame shift based on the input image *** 基于输入图片,开始首帧手势平移对齐 !") if abs(input_left_eye_y[0] / height - first_left_eye_y[ 0] / height) > 0.005: input_frames_cv2 = [affine_img(input_left_eye_y, first_left_eye_y, i) for i in input_frames_cv2] xxx=input_frames_cv2 for i,xx in enumerate(xxx): cv2.imwrite(f"{i}.png",xx) if i >2: break index = 0 for i in tqdm(input_frames_cv2): pose_img,_,_=visualizer(np.asarray(i),[5]) np.save(os.path.join(pose_dir, f"{index}"), pose_img) index+=1 USE_Default = False visualizer.enable_model_cpu_offload() gc.collect() torch.cuda.empty_cache() else: if pose_dir == "none": logging.info("when use echo v2,need choice a pose dir,using default pose for testing !") pose_dir = os.path.join(cur_path, "echomimic_v2/assets/halfbody_demo/pose/01") USE_Default = True else: logging.info( "Use NPY files for custom videos, which must be located in directory comfyui/input/tensorrt_lite") pose_dir = os.path.join(tensorrt_lite, pose_dir) USE_Default = False if seed is not None and seed > -1: generator = torch.manual_seed(seed) else: generator = torch.manual_seed(random.randint(100, 1000000)) #final_fps = fps start_idx = 0 audio_clip = AudioFileClip(uploaded_audio) L = min(int(audio_clip.duration * fps),length,len(os.listdir(pose_dir))) # if above will cause error #L=min(length,L) #length is definitely print(f"***** infer length is {L}") pose_list = [] for index in range(start_idx, start_idx + L): tgt_musk_path = os.path.join(pose_dir, "{}.npy".format(index)) if USE_Default: detected_pose = np.load(tgt_musk_path, allow_pickle=True).tolist() imh_new, imw_new, rb, re, cb, ce = detected_pose['draw_pose_params'] #print(imh_new, imw_new, rb, re, cb, ce) 官方示例蒙版的尺寸是768*768 im = draw_pose_select_v2(detected_pose, imh_new, imw_new, ref_w=800) #缩放比例为1,im也是768 ref_w!=768 im = np.transpose(np.array(im), (1, 2, 0)) tgt_musk = np.zeros((imw_new, imh_new, 3)).astype('uint8') tgt_musk[rb:re, cb:ce, :] = im else: tgt_musk = np.load(tgt_musk_path, allow_pickle=True) tgt_musk = center_resize_pad(tgt_musk, width, height) # 缩放裁剪遮罩,防止遮罩非正方形 tgt_musk_pil = Image.fromarray(np.array(tgt_musk)).convert('RGB') pose_list.append( torch.Tensor(np.array(tgt_musk_pil)).to(dtype=weight_dtype, device=device).permute(2, 0, 1) / 255.0) poses_tensor = torch.stack(pose_list, dim=1).unsqueeze(0) print(f"poses_tensor:{poses_tensor.shape}") # audio_clip = AudioFileClip(audio_clip) # # audio_clip = audio_clip.set_duration(L / fps) video = pipe( infer_image_pil, uploaded_audio, poses_tensor[:, :, :L, ...], width, height, L, steps, cfg, generator=generator, audio_sample_rate=sample_rate, context_frames=context_frames, fps=fps, context_overlap=context_overlap, start_idx=start_idx, ).videos final_length = min(video.shape[2],poses_tensor.shape[2], 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(face_img, uploaded_audio, width, height, length, seed, facemask_dilation_ratio, facecrop_dilation_ratio, context_frames, context_overlap, cfg, steps, sample_rate, fps, pipe, face_detector, save_video, pose_dir, video_images, audio_file_prefix, visualizer=None,): if seed is not None and seed > -1: generator = torch.manual_seed(seed) else: generator = torch.manual_seed(random.randint(100, 1000000)) #### face musk prepare face_mask = np.zeros((face_img.shape[0], face_img.shape[1])).astype('uint8') det_bboxes, probs = face_detector.detect(face_img) select_bbox = select_face(det_bboxes, probs) if select_bbox is None: face_mask[:, :] = 255 else: xyxy = select_bbox[:4].astype(float) # 面部处理出来是浮点数,无法实现整形 xyxy = np.round(xyxy).astype("int") rb, re, cb, ce = xyxy[1], xyxy[3], xyxy[0], xyxy[2] #56 227 132 268 r_pad = int((re - rb) * facemask_dilation_ratio) # ratio:0.1 遮罩膨胀系数 17*2 c_pad = int((ce - cb) * facemask_dilation_ratio) # ratio:0.1 遮罩膨胀系数 14*2 face_mask[rb - r_pad: re + r_pad, cb - c_pad: ce + c_pad] = 255 #### face crop #### if facecrop_dilation_ratio<1.0: if facecrop_dilation_ratio==0: facecrop_dilation_ratio=1 r_pad_crop = int((re - rb) * facecrop_dilation_ratio) # ratio 0.5 r_pad_crop:85,c_pad_crop:68 c_pad_crop = int((ce - cb) * facecrop_dilation_ratio) # ratio 1.0 r_pad_crop:171,c_pad_crop:136 crop_rect = [max(0, cb - c_pad_crop), max(0, rb - r_pad_crop), min(ce + c_pad_crop, face_img.shape[1]), min(re + r_pad_crop, face_img.shape[0])] if width == height: # 输出图片指定尺寸,如果是非方形,则会变形 face_img_i, ori_face_rect_i = crop_and_pad(face_img, crop_rect) face_mask_m, ori_mask_rect_m = crop_and_pad(face_mask, crop_rect) # (0, 7, 384, 391) face_img = cv2.resize(face_img_i, (width, height)) face_mask = cv2.resize(face_mask_m, (width, height)) else: face_img,face_mask=crop_and_pad_rectangle(face_img,face_mask,crop_rect) face_img= cv2tensor(face_img).permute(0, 2, 3, 1)#[1, 3, 357, 245] =>[[1,357, 245,3]] face_mask = cv2tensor(face_mask).permute(0, 2, 3, 1) face_img=tensor_upscale(face_img, width, height) face_img=tensor2cv(face_img) face_mask = tensor_upscale(face_mask, width, height) face_mask=cv2.cvtColor(tensor2cv(face_mask), cv2.COLOR_BGR2GRAY)#二值化 ret, face_mask = cv2.threshold(face_mask, 0, 255, cv2.THRESH_BINARY) else: #when ratio=1 no crop print("when facecrop_ratio=1.0,The maximum image size will be obtained, but there may be edge deformation.** 选择最大裁切为1.0时,边缘可能会出现形变!") ref_image_pil = Image.fromarray(face_img[:, :, [2, 1, 0]]) if visualizer: if pose_dir == "none": # motion sync if isinstance(video_images,torch.Tensor): print("**** Use video pose drive video! ****") pose_dir_path,video_len = motion_sync_main(visualizer, width, height, video_images, face_img,facecrop_dilation_ratio, audio_file_prefix) else: raise ("**** You need link video_images for drive video ****") #pose_dir = os.path.join(cur_path, "assets", "test_pose_demo_pose") # default else: print("**** Use pkl drive video! ****") pose_dir_path = os.path.join(tensorrt_lite, pose_dir) files_and_directories = os.listdir(pose_dir_path) # 过滤出文件,排除子目录 files = [f for f in files_and_directories if os.path.isfile(os.path.join(pose_dir_path, f))] video_len=len(files) if length>video_len: print(f"**** video length {video_len} is less than length,use {video_len} as {length} ****") length=video_len pose_list = [] for index in range(len(os.listdir(pose_dir_path))): tgt_musk_path = os.path.join(pose_dir_path, f"{index}.pkl") with open(tgt_musk_path, "rb") as f: tgt_kpts = pickle.load(f) tgt_musk = visualizer.draw_landmarks((width, height), tgt_kpts,facecrop_dilation_ratio) tgt_musk_pil = Image.fromarray(np.array(tgt_musk).astype(np.uint8)).convert('RGB') pose_list.append( torch.Tensor(np.array(tgt_musk_pil)).to(dtype=weight_dtype, device="cuda").permute(2, 0, 1) / 255.0) face_mask_tensor = torch.stack(pose_list, dim=1).unsqueeze(0) else: print("**** Use audio drive video! ****") face_mask_tensor = torch.Tensor(face_mask).to(dtype=weight_dtype, device="cuda").unsqueeze(0).unsqueeze( 0).unsqueeze(0) / 255.0 video = pipe( ref_image_pil, uploaded_audio, face_mask_tensor, width, height, length, steps, cfg, generator=generator, audio_sample_rate=sample_rate, context_frames=context_frames, fps=fps, context_overlap=context_overlap ).videos final_length = min(video.shape[2], face_mask_tensor.shape[2], 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): pt_path = hf_hub_download( repo_id=repo_id, subfolder=subfolder, filename=pt_name, local_dir = file_dir, ) else: pt_path=get_instance_path(file_path) return pt_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): pt_path = hf_hub_download( repo_id=repo_id, filename=pt_name, local_dir=file_dir, ) else: pt_path=get_instance_path(file_path) return pt_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 instance_path(path, repo): if repo == "": if path == "none": repo = "none" else: model_path = get_local_path(folder_paths.base_path, path) repo = get_instance_path(model_path) return repo 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 get_local_path(comfy_file_path, model_path): path = os.path.join(comfy_file_path, "models", "diffusers", model_path) model_path = os.path.normpath(path) if sys.platform == 'win32': model_path = model_path.replace('\\', "/") return model_path def get_instance_path(path): instance_path = os.path.normpath(path) if sys.platform == 'win32': instance_path = instance_path.replace('\\', "/") return instance_path 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): 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: left_eye_y.append(y) if name == "left_shoulder": if conf > 0.3: left_shoulder_y.append(y) if name == "left_elbow": if conf > 0.3: left_elbow_y.append(y) 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]+box_xy[0]] #眼部的实际高度要加上box的边界 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 # 82.0 [50.0] 76.0 [41.0] f f in in base_image=np.zeros((height, width,3), np.uint8) if input_length / height < first_length / height: # 输入图的人物占比要小,pose图需要缩小对齐,0.926 ratio input_frames_cv2_first=cv2.resize(input_frames_cv2_first, (int(height * ratio), int(height * ratio)), interpolation=cv2.INTER_AREA) #缩小 reduced_image,pad_size=center_paste(base_image, input_frames_cv2_first) #中心粘贴 move_ = -int(first_left_eye_y * ratio+pad_size[0] - input_left_eye_y) if first_left_eye_y * ratio +pad_size[0] >= \ input_left_eye_y else \ int(input_left_eye_y)- int(first_left_eye_y * ratio+pad_size[0]) #对齐眼睛 translation_matrix = np.float32([[1, 0, 0], [0, 1, move_]]) #y轴位移 shifted_image = cv2.warpAffine(reduced_image, translation_matrix, (width, height)) else: # pose图里人物的比例小于输入图,pose要放大 input_frames_cv2_first=cv2.resize(input_frames_cv2_first, (int(height / ratio), int(height / ratio)), interpolation=cv2.INTER_AREA) #放大 crpo_image=center_crop(input_frames_cv2_first, height, width) #中心裁切 h, w = input_frames_cv2_first.shape[:2] shift_y=(h-height)//2 move_ = -int(first_left_eye_y / ratio-shift_y - input_left_eye_y) if first_left_eye_y / ratio-shift_y >= \ input_left_eye_y else \ int(input_left_eye_y) - int(first_left_eye_y / ratio-shift_y) translation_matrix = np.float32([[1, 0, 0], [0, 1, move_]]) shifted_image = cv2.warpAffine(crpo_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