SCAIL-2 / SCAIL-Pose /NLFPoseExtract /v1_process_pose.py
fffiloni's picture
Migrated files batch 1
09462dc verified
Raw
History Blame Contribute Delete
12.6 kB
import os
import sys
# 动态添加项目根目录到 sys.path,这样就不需要 export PYTHONPATH
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(current_dir) # SCAIL_Pose 目录
if project_root not in sys.path:
sys.path.insert(0, project_root)
import cv2
import torch
import pickle
import torchvision
import shutil
import glob
import random
from tqdm import tqdm
import decord
from decord import VideoReader, cpu, gpu
from torchvision.transforms import ToPILImage
from PIL import Image
import numpy as np
import argparse
from NLFPoseExtract.nlf_render import render_nlf_as_images, collect_smpl_poses, shift_dwpose_according_to_nlf, p3d_single_p2d
from NLFPoseExtract.nlf_draw import intrinsic_matrix_from_field_of_view, process_data_to_COCO_format, p3d_to_p2d
from DWPoseProcess.dwpose import DWposeDetector
from concurrent.futures import ProcessPoolExecutor, as_completed
import multiprocessing
import traceback
from NLFPoseExtract.extract_nlfpose_batch import process_video_nlf
from NLFPoseExtract.reshape_utils_3d import reshapePool3d
try:
import moviepy.editor as mpy
except:
import moviepy as mpy
from torchvision.transforms.functional import center_crop, resize
from torchvision.transforms import InterpolationMode
import torchvision.transforms as TT
import copy
from NLFPoseExtract.align3d import solve_new_camera_params_central, solve_new_camera_params_down
def recollect_nlf(data):
new_data = []
for item in data:
new_item = item.copy()
if len(item['bboxes']) > 0:
new_item['bboxes'] = item['bboxes'][:1]
new_item['nlfpose'] = item['nlfpose'][:1]
new_data.append(new_item)
return new_data
def recollect_dwposes(poses):
new_poses = []
for pose in poses:
new_pose = pose.copy()
for i in range(1):
bodies = pose["bodies"]
faces = pose["faces"][i:i+1]
hands = pose["hands"][2*i:2*i+2]
candidate = bodies["candidate"][i:i+1] # candidate是所有点的坐标和置信度
subset = bodies["subset"][i:i+1] # subset是认为的有效点
new_pose = {
"bodies": {
"candidate": candidate,
"subset": subset
},
"faces": faces,
"hands": hands
}
new_poses.append(new_pose)
return new_poses
def resize_for_rectangle_crop(arr, image_size, reshape_mode='random'):
if arr.shape[3] / arr.shape[2] > image_size[1] / image_size[0]:
arr = resize(arr, size=[image_size[0], int(arr.shape[3] * image_size[0] / arr.shape[2])], interpolation=InterpolationMode.BICUBIC)
else:
arr = resize(arr, size=[int(arr.shape[2] * image_size[1] / arr.shape[3]), image_size[1]], interpolation=InterpolationMode.BICUBIC)
h, w = arr.shape[2], arr.shape[3]
delta_h = h - image_size[0]
delta_w = w - image_size[1]
if reshape_mode == 'random' or reshape_mode == 'none':
top = np.random.randint(0, delta_h + 1)
left = np.random.randint(0, delta_w + 1)
elif reshape_mode == 'center':
top, left = delta_h // 2, delta_w // 2
else:
raise NotImplementedError
arr = TT.functional.crop(
arr, top=top, left=left, height=image_size[0], width=image_size[1]
)
return arr
def scale_faces(poses, pose_2d_ref):
# 输入:两个list of dict,poses[0]['faces'].shape: 1, 68, 2 , poses_ref[0]['faces'].shape: 1, 68, 2
# 根据脸部的中心点,对poses中的脸部关键点进行缩放
# 也即:计算ref里面脸部中心点(idx: 30)到其他脸部关键点的中心距离, 计算poses里面脸部中心点到其他脸部关键点的中心距离,得到scale_n
# 对scale_n 取一下0.8-1.5的上下界,然后应用在poses上
# 注意:需要inplace改变poses
ref = pose_2d_ref[0]
pose_0 = poses[0]
face_0 = pose_0['faces'] # shape: (1, 68, 2)
face_ref = ref['faces']
# 提取 numpy 数组
face_0 = np.array(face_0[0]) # (68, 2)
face_ref = np.array(face_ref[0])
# 中心点(鼻尖或面部中心)
center_idx = 30
center_0 = face_0[center_idx]
center_ref = face_ref[center_idx]
# 计算到中心点的距离
dist = np.linalg.norm(face_0 - center_0, axis=1)
dist_ref = np.linalg.norm(face_ref - center_ref, axis=1)
# 避免中心点自身的 0 距离影响
dist = np.delete(dist, center_idx)
dist_ref = np.delete(dist_ref, center_idx)
mean_dist = np.mean(dist)
mean_dist_ref = np.mean(dist_ref)
if mean_dist < 1e-6:
scale_n = 1.0
else:
scale_n = mean_dist_ref / mean_dist
# 限制在 [0.8, 1.5]
scale_n = np.clip(scale_n, 0.8, 1.5)
for i, pose in enumerate(poses):
face = pose['faces']
# 提取 numpy 数组
face = np.array(face[0]) # (68, 2)
center = face[center_idx]
scaled_face = (face - center) * scale_n + center
poses[i]['faces'][0] = scaled_face
body = pose['bodies']
candidate = body['candidate']
candidate_np = np.array(candidate[0]) # (14, 2)
body_center = candidate_np[0]
scaled_candidate = (candidate_np - body_center) * scale_n + body_center
poses[i]['bodies']['candidate'][0] = scaled_candidate
# inplace 修改
pose['faces'][0] = scaled_face
return scale_n
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Process video with NLF pose estimation')
parser.add_argument('--subdir', type=str, default="../examples/001", help='Path to the subdirectory to process')
parser.add_argument('--model_path', type=str, default='pretrained_weights/nlf_l_multi_0.3.2.torchscript',
help='Path to NLF model')
parser.add_argument('--use_align', action='store_true', help='Whether to use 2D keypoints from reference image for alignment')
parser.add_argument('--resolution', type=int, nargs=2, default=[512, 896],
metavar=('HEIGHT', 'WIDTH'),
help='Target resolution as [height, width], currently only [512, 896] are supported')
args = parser.parse_args()
subdir = args.subdir
model_nlf = torch.jit.load(args.model_path).cuda().eval()
decord.bridge.set_bridge("torch")
# 设置路径
mp4_path = os.path.join(subdir, 'driving.mp4')
if not os.path.exists(mp4_path):
raise FileNotFoundError(f"No video file found in {subdir}")
if args.use_align:
out_path_aligned = os.path.join(subdir, 'rendered_aligned.mp4')
else:
out_path_aligned = os.path.join(subdir, 'rendered.mp4')
ref_image_path = os.path.join(subdir, 'ref_image.jpg')
if not os.path.exists(ref_image_path):
ref_image_path = os.path.join(subdir, 'ref_image.png')
if not os.path.exists(ref_image_path):
ref_image_path = os.path.join(subdir, 'ref.jpg')
if not os.path.exists(ref_image_path):
raise FileNotFoundError(f"No reference image found in {subdir}")
print(f"Processing: {subdir}")
print(f"Video: {mp4_path}")
print(f"Reference: {ref_image_path}")
print(f"Resolution: {args.resolution}")
# 读取视频
vr = VideoReader(mp4_path)
vr_frames = vr.get_batch(list(range(len(vr)))) # T H W C
sampling_image_size = args.resolution
if vr_frames.shape[1] < vr_frames.shape[2]:
target_H, target_W = sampling_image_size
else:
target_W, target_H = sampling_image_size
vr_frames = resize_for_rectangle_crop(vr_frames.permute(0, 3, 1, 2), [target_H, target_W], reshape_mode='center').permute(0, 2, 3, 1) # T H W C ->T C H W -> T H W C
# 读取参考图片
img_ref = cv2.imread(ref_image_path)
img_ref = cv2.cvtColor(img_ref, cv2.COLOR_BGR2RGB)
vr_frames_ref = torch.from_numpy(img_ref).unsqueeze(0)
vr_frames_ref = resize_for_rectangle_crop(vr_frames_ref.permute(0, 3, 1, 2), [target_H, target_W], reshape_mode='center').permute(0, 2, 3, 1) # 1 H W C ->1 C H W -> 1 H W C
# 初始化检测器
detector = DWposeDetector(use_batch=False).to(0)
# 处理Driving视频
print("Processing driving video...")
detector_return_list = []
pil_frames = []
for i in tqdm(range(len(vr_frames)), desc="Detecting poses in video"):
pil_frame = Image.fromarray(vr_frames[i].numpy())
pil_frames.append(pil_frame)
detector_result = detector(pil_frame)
detector_return_list.append(detector_result)
W, H = pil_frames[0].size
poses, scores, det_results = zip(*detector_return_list)
print("Running NLF on driving video...")
nlf_results = process_video_nlf(model_nlf, vr_frames, det_results)
# 处理ref图片
print("Processing reference image...")
detector_return_list_ref = []
pil_frames_ref = []
for i in range(len(vr_frames_ref)):
pil_frame = Image.fromarray(vr_frames_ref[i].numpy())
pil_frames_ref.append(pil_frame)
detector_result = detector(pil_frame)
detector_return_list_ref.append(detector_result)
poses_ref, scores_ref, det_results_ref = zip(*detector_return_list_ref)
print("Running NLF on reference image...")
nlf_results_ref = process_video_nlf(model_nlf, vr_frames_ref, det_results_ref)
# 进行对齐和渲染
print("Aligning and rendering...")
ori_camera_pose = intrinsic_matrix_from_field_of_view([target_H, target_W])
ori_focal = ori_camera_pose[0, 0]
pose_3d_first_driving_frame = nlf_results[0]['nlfpose'][0][0].cpu().numpy() # 3D点 frame-idx bbox-idx detect-idx
pose_3d_coco_first_driving_frame = process_data_to_COCO_format(pose_3d_first_driving_frame)
if args.use_align:
poses_2d_ref = poses_ref[0]['bodies']['candidate'][0][:14]
poses_2d_ref[:, 0] = poses_2d_ref[:, 0] * target_W
poses_2d_ref[:, 1] = poses_2d_ref[:, 1] * target_H
poses_2d_subset = poses_ref[0]['bodies']['subset'][0][:14]
pose_3d_coco_first_driving_frame = pose_3d_coco_first_driving_frame[:14]
valid_indices = []
valid_upper_indices = []
valid_lower_indices = []
upper_body_indices = [0, 2, 3, 5, 6]
lower_body_indices = [9, 10, 12, 13]
excluded_indices = [3, 4, 6, 7] # 去除手
for i in range(len(poses_2d_subset)):
if poses_2d_subset[i] != -1.0 and np.sum(pose_3d_coco_first_driving_frame[i]) != 0:
if i in upper_body_indices:
valid_upper_indices.append(i)
if i in lower_body_indices:
valid_lower_indices.append(i)
if len(valid_lower_indices) >= 4:
print("Align feet")
valid_indices = [1] + valid_lower_indices
else:
print("Align body")
valid_indices = [1] + valid_upper_indices
pose_2d_ref = poses_2d_ref[valid_indices]
pose_3d_coco_first_driving_frame = pose_3d_coco_first_driving_frame[valid_indices]
if len(valid_lower_indices) >= 4:
new_camera_intrinsics, scale_m, scale_s = solve_new_camera_params_down(pose_3d_coco_first_driving_frame, ori_focal, [target_H, target_W], pose_2d_ref)
else:
new_camera_intrinsics, scale_m, scale_s = solve_new_camera_params_central(pose_3d_coco_first_driving_frame, ori_focal, [target_H, target_W], pose_2d_ref)
# m 代表缩放了多少
scale_face = scale_faces(list(poses), list(poses_ref)) # poses[0]['faces'].shape: 1, 68, 2 , poses_ref[0]['faces'].shape: 1, 68, 2
print(f"Scale - m: {scale_m}, face: {scale_face}")
nlf_results = recollect_nlf(nlf_results)
poses = recollect_dwposes(list(poses))
shift_dwpose_according_to_nlf(collect_smpl_poses(nlf_results), poses, ori_camera_pose, new_camera_intrinsics, target_H, target_W, scale_x=scale_m, scale_y=scale_m*scale_s)
print("Rendering final video...")
frames_np = render_nlf_as_images(nlf_results, poses, reshape_pool=None, intrinsic_matrix=new_camera_intrinsics)
else:
nlf_results = recollect_nlf(nlf_results)
print("Rendering final video...")
frames_np = render_nlf_as_images(nlf_results, poses, reshape_pool=None, intrinsic_matrix=ori_camera_pose)
mpy.ImageSequenceClip(frames_np, fps=16).write_videofile(out_path_aligned)
print(f"Done! Output saved to: {out_path_aligned}")