SCAIL-2 / SCAIL-Pose /NLFPoseExtract /nlf_render.py
fffiloni's picture
Migrated files batch 1
09462dc verified
Raw
History Blame Contribute Delete
33.1 kB
import cv2
import numpy as np
import math
from PIL import Image
from render_3d.taichi_cylinder import render_whole
from NLFPoseExtract.nlf_draw import intrinsic_matrix_from_field_of_view, process_data_to_COCO_format, preview_nlf_2d, p3d_to_p2d
from concurrent.futures import ProcessPoolExecutor, as_completed
from pose_draw.draw_pose_utils import draw_pose_to_canvas_np, scale_image_hw_keep_size
import pose_draw.draw_utils as draw_utils
import torch.multiprocessing as mp
import os
os.environ['PYOPENGL_PLATFORM'] = 'osmesa'
import copy
import random
import torch
try:
import moviepy.editor as mpy
except Exception:
import moviepy as mpy
def p3d_single_p2d(points, intrinsic_matrix):
X, Y, Z = points[0], points[1], points[2]
u = (intrinsic_matrix[0, 0] * X / Z) + intrinsic_matrix[0, 2]
v = (intrinsic_matrix[1, 1] * Y / Z) + intrinsic_matrix[1, 2]
u_np = u.cpu().numpy()
v_np = v.cpu().numpy()
return np.array([u_np, v_np])
def scale_around_center(points, center, dim, scale=1.0):
return (points[:, dim] - center[dim]) * scale + center[dim]
def shift_dwpose_according_to_nlf(smpl_poses, aligned_poses, ori_intrinstics, modified_intrinstics, height, width, scale_x = 1.0, scale_y = 1.0):
########## warning: 会改变body; shift 之后 body是不准的 ##########
for i in range(len(smpl_poses)):
persons_joints_list = smpl_poses[i]
poses_list = aligned_poses[i]
# 对里面每一个人,取关节并进行变形;并且修改2d;如果3d不存在,把2d的手/脸也去掉
for person_idx, person_joints in enumerate(persons_joints_list):
face = poses_list["faces"][person_idx]
right_hand = poses_list["hands"][2 * person_idx]
left_hand = poses_list["hands"][2 * person_idx + 1]
candidate = poses_list["bodies"]["candidate"][person_idx]
# 注意,这里不是coco format
person_joint_15_2d_shift = p3d_single_p2d(person_joints[15], modified_intrinstics) - p3d_single_p2d(person_joints[15], ori_intrinstics) if person_joints[15, 2] > 0.01 else np.array([0.0, 0.0]) # face
person_joint_20_2d_shift = p3d_single_p2d(person_joints[20], modified_intrinstics) - p3d_single_p2d(person_joints[20], ori_intrinstics) if person_joints[20, 2] > 0.01 else np.array([0.0, 0.0]) # right hand
person_joint_21_2d_shift = p3d_single_p2d(person_joints[21], modified_intrinstics) - p3d_single_p2d(person_joints[21], ori_intrinstics) if person_joints[21, 2] > 0.01 else np.array([0.0, 0.0]) # left hand
face[:, 0] += person_joint_15_2d_shift[0] / width
face[:, 1] += person_joint_15_2d_shift[1] / height
right_hand[:, 0] += person_joint_20_2d_shift[0] / width
right_hand[:, 1] += person_joint_20_2d_shift[1] / height
left_hand[:, 0] += person_joint_21_2d_shift[0] / width
left_hand[:, 1] += person_joint_21_2d_shift[1] / height
candidate[:, 0] += person_joint_15_2d_shift[0] / width
candidate[:, 1] += person_joint_15_2d_shift[1] / height
scales = [scale_x, scale_y]
# apply camera scale around wrist (hand[0]).
for dim in [0,1]:
right_hand[:, dim] = scale_around_center(right_hand, right_hand[0, :], dim=dim, scale=scales[dim])
left_hand[:, dim] = scale_around_center(left_hand, left_hand[0, :], dim=dim, scale=scales[dim])
def get_single_pose_cylinder_specs(args):
"""渲染单个pose的辅助函数,用于并行处理"""
idx, pose, focal, princpt, height, width, colors, limb_seq, draw_seq = args
cylinder_specs = []
for joints3d in pose: # 多人
joints3d = joints3d.cpu().numpy()
joints3d = process_data_to_COCO_format(joints3d)
for line_idx in draw_seq:
line = limb_seq[line_idx]
start, end = line[0], line[1]
if np.sum(joints3d[start]) == 0 or np.sum(joints3d[end]) == 0:
continue
else:
cylinder_specs.append((joints3d[start], joints3d[end], colors[line_idx]))
return cylinder_specs
def get_single_pose_cylinder_specs_mono(args):
"""渲染单个pose的辅助函数,用于并行处理"""
idx, pose, ori_pose, binary_frame, intrinsic_matrix, height, width, limb_seq, draw_seq = args
cylinder_specs = []
for joints3d, ori_joint3d in zip(pose, ori_pose): # 多人
joints3d = joints3d.cpu().numpy()
joints3d = process_data_to_COCO_format(joints3d)
ori_joint3d = ori_joint3d.cpu().numpy()
ori_joint3d = process_data_to_COCO_format(ori_joint3d)
specific_color = locate_binary_color(binary_frame, ori_joint3d, height, width) # 通过3D点的2D投影的像素位置,计算原本这个人对应的颜色
for line_idx in draw_seq:
line = limb_seq[line_idx]
start, end = line[0], line[1]
if np.sum(joints3d[start]) == 0 or np.sum(joints3d[end]) == 0:
continue
else:
cylinder_specs.append((joints3d[start], joints3d[end], specific_color))
return cylinder_specs
def get_single_pose_cylinder_specs_colored(args):
"""直接使用传入的每人颜色渲染,不做颜色查找。"""
idx, pose, person_colors, limb_seq, draw_seq = args
cylinder_specs = []
for person_idx, joints3d in enumerate(pose):
joints3d = joints3d.cpu().numpy()
joints3d = process_data_to_COCO_format(joints3d)
color = person_colors[person_idx] if person_idx < len(person_colors) else [0, 0, 0, 1]
for line_idx in draw_seq:
line = limb_seq[line_idx]
start, end = line[0], line[1]
if np.sum(joints3d[start]) == 0 or np.sum(joints3d[end]) == 0:
continue
cylinder_specs.append((joints3d[start], joints3d[end], color))
return cylinder_specs
def locate_binary_color(binary_frame, ori_joint3d, height, width):
"""通过3D点的2D投影的像素位置,计算原本这个人对应的颜色。
ori_joint3d: COCO format (18, 3) numpy array
binary_frame: H x W x 3,BGR,像素颜色只有6种纯色之一
"""
key_joint_indices = [1, 2, 5, 8, 11] # neck, left shoulder, right shoulder, left pelvis, right pelvis
key_joints_3d = ori_joint3d[key_joint_indices] # (5, 3)
valid_flag = key_joints_3d[:, 2] > 0.0001
point_2d = p3d_to_p2d(key_joints_3d[np.newaxis], height, width)[0] # (5, 3)
sampled = []
for is_valid, p2d in zip(valid_flag, point_2d):
if not is_valid:
continue
u = int(round(p2d[0]))
v = int(round(p2d[1]))
if 0 <= u < width and 0 <= v < height:
sampled.append(binary_frame[v, u].astype(np.float32))
if len(sampled) == 0:
return [0, 0, 0, 1]
avg_color = np.mean(sampled, axis=0)
binarized = (avg_color > 127).astype(np.float32) * 1.0
return [binarized[0], binarized[1], binarized[2], 1]
def collect_smpl_poses(data):
uncollected_smpl_poses = [item['nlfpose'] for item in data]
smpl_poses = [[] for _ in range(len(uncollected_smpl_poses))]
for frame_idx in range(len(uncollected_smpl_poses)):
for person_idx in range(len(uncollected_smpl_poses[frame_idx])): # 每个人(每个bbox)只给出一个pose
if len(uncollected_smpl_poses[frame_idx][person_idx]) > 0: # 有返回的骨骼
smpl_poses[frame_idx].append(uncollected_smpl_poses[frame_idx][person_idx][0])
else:
smpl_poses[frame_idx].append(torch.zeros((24, 3), dtype=torch.float32)) # 没有检测到人,就放一个全0的
return smpl_poses
def collect_smpl_poses_samurai(data):
uncollected_smpl_poses = [item['nlfpose'] for item in data]
smpl_poses_first = [[] for _ in range(len(uncollected_smpl_poses))]
smpl_poses_second = [[] for _ in range(len(uncollected_smpl_poses))]
for frame_idx in range(len(uncollected_smpl_poses)):
for person_idx in range(len(uncollected_smpl_poses[frame_idx])): # 每个人(每个bbox)只给出一个pose
if len(uncollected_smpl_poses[frame_idx][person_idx]) > 0: # 有返回的骨骼
if person_idx == 0:
smpl_poses_first[frame_idx].append(uncollected_smpl_poses[frame_idx][person_idx][0])
elif person_idx == 1:
smpl_poses_second[frame_idx].append(uncollected_smpl_poses[frame_idx][person_idx][0])
else:
if person_idx == 0:
smpl_poses_first[frame_idx].append(torch.zeros((24, 3), dtype=torch.float32)) # 没有检测到人,就放一个全0的
elif person_idx == 1:
smpl_poses_second[frame_idx].append(torch.zeros((24, 3), dtype=torch.float32))
return smpl_poses_first, smpl_poses_second
def render_nlf_as_images(data, poses, reshape_pool=None, intrinsic_matrix=None, draw_2d=True, aug_2d=False, aug_cam=False, binary_mask=None, person_colors=None, palette_offset=0):
""" return a list of images """
height, width = data[0]['video_height'], data[0]['video_width']
video_length = len(data)
base_colors_255_dict = {
# Warm Colors for Right Side (R.) - Red, Orange, Yellow
"Red": [255, 0, 0],
"Orange": [255, 85, 0],
"Golden Orange": [255, 170, 0],
"Yellow": [255, 240, 0],
"Yellow-Green": [180, 255, 0],
# Cool Colors for Left Side (L.) - Green, Blue, Purple
"Bright Green": [0, 255, 0],
"Light Green-Blue": [0, 255, 85],
"Aqua": [0, 255, 170],
"Cyan": [0, 255, 255],
"Sky Blue": [0, 170, 255],
"Medium Blue": [0, 85, 255],
"Pure Blue": [0, 0, 255],
"Purple-Blue": [85, 0, 255],
"Medium Purple": [170, 0, 255],
# Neutral/Central Colors (e.g., for Neck, Nose, Eyes, Ears)
"Grey": [150, 150, 150],
"Pink-Magenta": [255, 0, 170],
"Dark Pink": [255, 0, 85],
"Violet": [100, 0, 255],
"Dark Violet": [50, 0, 255],
}
ordered_colors_255 = [
base_colors_255_dict["Red"], # Neck -> R. Shoulder (Red)
base_colors_255_dict["Cyan"], # Neck -> L. Shoulder (Cyan)
base_colors_255_dict["Orange"], # R. Shoulder -> R. Elbow (Orange)
base_colors_255_dict["Golden Orange"], # R. Elbow -> R. Wrist (Golden Orange)
base_colors_255_dict["Sky Blue"], # L. Shoulder -> L. Elbow (Sky Blue)
base_colors_255_dict["Medium Blue"], # L. Elbow -> L. Wrist (Medium Blue)
base_colors_255_dict["Yellow-Green"], # Neck -> R. Hip ( Yellow-Green)
base_colors_255_dict["Bright Green"], # R. Hip -> R. Knee (Bright Green - transitioning warm to cool spectrum)
base_colors_255_dict["Light Green-Blue"], # R. Knee -> R. Ankle (Light Green-Blue - transitioning)
base_colors_255_dict["Pure Blue"], # Neck -> L. Hip (Pure Blue)
base_colors_255_dict["Purple-Blue"], # L. Hip -> L. Knee (Purple-Blue)
base_colors_255_dict["Medium Purple"], # L. Knee -> L. Ankle (Medium Purple)
base_colors_255_dict["Grey"], # Neck -> Nose (Grey)
base_colors_255_dict["Pink-Magenta"], # Nose -> R. Eye (Pink/Magenta)
base_colors_255_dict["Dark Violet"], # R. Eye -> R. Ear (Dark Pink)
base_colors_255_dict["Pink-Magenta"], # Nose -> L. Eye (Violet)
base_colors_255_dict["Dark Violet"], # L. Eye -> L. Ear (Dark Violet)
]
limb_seq = [
[1, 2], # 0 Neck -> R. Shoulder
[1, 5], # 1 Neck -> L. Shoulder
[2, 3], # 2 R. Shoulder -> R. Elbow
[3, 4], # 3 R. Elbow -> R. Wrist
[5, 6], # 4 L. Shoulder -> L. Elbow
[6, 7], # 5 L. Elbow -> L. Wrist
[1, 8], # 6 Neck -> R. Hip
[8, 9], # 7 R. Hip -> R. Knee
[9, 10], # 8 R. Knee -> R. Ankle
[1, 11], # 9 Neck -> L. Hip
[11, 12], # 10 L. Hip -> L. Knee
[12, 13], # 11 L. Knee -> L. Ankle
[1, 0], # 12 Neck -> Nose
[0, 14], # 13 Nose -> R. Eye
[14, 16], # 14 R. Eye -> R. Ear
[0, 15], # 15 Nose -> L. Eye
[15, 17], # 16 L. Eye -> L. Ear
]
draw_seq = [0, 2, 3, # Neck -> R. Shoulder -> R. Elbow -> R. Wrist
1, 4, 5, # Neck -> L. Shoulder -> L. Elbow -> L. Wrist
6, 7, 8, # Neck -> R. Hip -> R. Knee -> R. Ankle
9, 10, 11, # Neck -> L. Hip -> L. Knee -> L. Ankle
12, # Neck -> Nose
13, 14, # Nose -> R. Eye -> R. Ear
15, 16, # Nose -> L. Eye -> L. Ear
] # 从近心端往外扩展
colors = [[c / 300 + 0.15 for c in color_rgb] + [0.8] for color_rgb in ordered_colors_255]
# smpl_poses 会在这里被修改
if poses is not None or binary_mask is not None or person_colors is not None:
# 重新收集poses
smpl_poses = collect_smpl_poses(data)
if binary_mask is not None:
original_smpl_poses = copy.deepcopy(smpl_poses)
if poses is not None:
aligned_poses = copy.deepcopy(poses) # 2d poses
if reshape_pool is not None:
for i in range(video_length):
persons_joints_list = smpl_poses[i]
poses_list = aligned_poses[i]
# 对里面每一个人,取关节并进行变形;并且修改2d;如果3d不存在,把2d的手/脸也去掉
for person_idx, person_joints in enumerate(persons_joints_list):
candidate = poses_list['bodies']['candidate'][person_idx]
subset = poses_list['bodies']['subset'][person_idx]
face = poses_list["faces"][person_idx]
right_hand = poses_list["hands"][2 * person_idx]
left_hand = poses_list["hands"][2 * person_idx + 1]
reshape_pool.apply_random_reshapes(person_joints, candidate, left_hand, right_hand, face, subset)
else:
smpl_poses = [item['nlfpose'] for item in data] # 主要为了兼容多人评测集;搭配process_video_nlf_original
if intrinsic_matrix is None:
intrinsic_matrix = intrinsic_matrix_from_field_of_view((height, width))
focal_x = intrinsic_matrix[0,0]
focal_y = intrinsic_matrix[1,1]
princpt = (intrinsic_matrix[0,2], intrinsic_matrix[1,2]) # 主点 (cx, cy)
if aug_cam and random.random() < 0.3:
w_shift_factor = random.uniform(-0.04, 0.04)
h_shift_factor = random.uniform(-0.04, 0.04)
princpt = (princpt[0] - w_shift_factor * width, princpt[1] - h_shift_factor * height) # princpt变化和点的变化相反
new_intrinsic_matrix = copy.deepcopy(intrinsic_matrix)
new_intrinsic_matrix[0,2] = princpt[0]
new_intrinsic_matrix[1,2] = princpt[1]
shift_dwpose_according_to_nlf(smpl_poses, aligned_poses, intrinsic_matrix, new_intrinsic_matrix, height, width)
# person_colors 传入时,为每人生成独立肢体颜色方案(同 render_multi_nlf_as_images 的两套配色)
if person_colors is not None:
_palettes_255 = [
# Person 0: 浅色调
[[255,150,150],[180,230,240],[255,180,140],[255,215,150],[160,200,255],[100,120,255],
[200,255,100],[100,255,100],[140,255,180],[120,140,255],[180, 90,255],[190,120,255],
[210,210,210],[255,120,200],[130, 80,255],[255,120,200],[130, 80,255]],
# Person 1: 饱和色调
[[255, 20, 20],[ 0,230,255],[255, 60, 0],[255,110, 0],[ 0,130,255],[ 0, 70,255],
[160,255, 40],[ 0,255, 50],[ 0,255,100],[ 0, 0,255],[ 80, 0,255],[160, 0,255],
[130,130,130],[255, 0,150],[ 60, 0,255],[255, 0,150],[ 60, 0,255]],
]
colors_per_person = [
[[c / 300 + 0.15 for c in rgb] + [0.8]
for rgb in _palettes_255[(p + palette_offset) % len(_palettes_255)]]
for p in range(len(person_colors))
]
# 串行获取每一帧的cylinder_specs
cylinder_specs_list = []
cylinder_specs_list_mono = []
for i in range(video_length):
if person_colors is not None:
cylinder_specs = []
for p_idx, person_pose in enumerate(smpl_poses[i]):
p_limb_colors = colors_per_person[p_idx] if p_idx < len(colors_per_person) else colors
cylinder_specs.extend(get_single_pose_cylinder_specs(
(i, [person_pose], None, None, None, None, p_limb_colors, limb_seq, draw_seq)))
else:
cylinder_specs = get_single_pose_cylinder_specs((i, smpl_poses[i], None, None, None, None, colors, limb_seq, draw_seq))
cylinder_specs_list.append(cylinder_specs)
if person_colors is not None:
cylinder_specs_colored = get_single_pose_cylinder_specs_colored((i, smpl_poses[i], person_colors, limb_seq, draw_seq))
cylinder_specs_list_mono.append(cylinder_specs_colored)
elif binary_mask is not None:
cylinder_specs_mono = get_single_pose_cylinder_specs_mono((i, smpl_poses[i], original_smpl_poses[i], binary_mask[i], intrinsic_matrix, height, width, limb_seq, draw_seq))
cylinder_specs_list_mono.append(cylinder_specs_mono)
frames_np_rgba = render_whole(cylinder_specs_list, H=height, W=width, fx=focal_x, fy=focal_y, cx=princpt[0], cy=princpt[1])
frames_np_rgba_mono = render_whole(cylinder_specs_list_mono, H=height, W=width, fx=focal_x, fy=focal_y, cx=princpt[0], cy=princpt[1], use_specular=False) if (binary_mask is not None or person_colors is not None) else None
bg_color = np.array([0, 0, 0], dtype=np.uint8)
for frame in frames_np_rgba:
bg_mask = frame[:, :, 3] == 0
frame[:, :, :3][bg_mask] = bg_color
scale_h = random.uniform(0.85, 1.15)
scale_w = random.uniform(0.85, 1.15)
rescale_flag = random.random() < 0.4 if reshape_pool is not None else False
if poses is not None and draw_2d:
canvas_2d = draw_pose_to_canvas_np(aligned_poses, pool=None, H=height, W=width, reshape_scale=0, show_feet_flag=False, show_body_flag=False, show_cheek_flag=True, dw_hand=True)
for i in range(len(frames_np_rgba)):
frame_img = frames_np_rgba[i]
canvas_img = canvas_2d[i]
mask = canvas_img != 0
frame_img[:, :, :3][mask] = canvas_img[mask]
frames_np_rgba[i] = frame_img # no alpha blending
# 在 mono 版上用每人的颜色画 cheek/hand/face 2D 关键点
if frames_np_rgba_mono is not None and person_colors is not None:
poses_list = aligned_poses[i]
n_draw = min(len(poses_list['bodies']['candidate']), len(person_colors))
for p_idx in range(n_draw):
temp_canvas = np.zeros((height, width, 3), dtype=np.uint8)
p_candidate = poses_list['bodies']['candidate'][p_idx]
p_subset = poses_list['bodies']['subset'][p_idx:p_idx+1]
p_faces = poses_list['faces'][p_idx:p_idx+1]
p_hands = poses_list['hands'][2*p_idx:2*p_idx+2]
temp_canvas = draw_utils.draw_bodypose_augmentation(temp_canvas, p_candidate, p_subset, drop_aug=False, shift_aug=False, all_cheek_aug=True)
temp_canvas = draw_utils.draw_handpose(temp_canvas, p_hands)
temp_canvas = draw_utils.draw_facepose(temp_canvas, p_faces, optimized_face=True)
mask_2d = np.any(temp_canvas != 0, axis=-1)
mono_color = [int(c * 255) for c in person_colors[p_idx][:3]]
frames_np_rgba_mono[i][:, :, :3][mask_2d] = mono_color
if aug_2d:
if rescale_flag:
frames_np_rgba[i] = scale_image_hw_keep_size(frames_np_rgba[i], scale_h, scale_w)
border_mask = frames_np_rgba[i][:, :, 3] == 0
frames_np_rgba[i][:, :, :3][border_mask] = bg_color
if reshape_pool is not None and random.random() < 0.04:
# 4%的概率完全消除某些帧,两组同步
frames_np_rgba[i][:, :, :3] = bg_color
if frames_np_rgba_mono is not None:
frames_np_rgba_mono[i][:, :, 0:3] = 0
if frames_np_rgba_mono is not None and rescale_flag:
frames_np_rgba_mono[i] = scale_image_hw_keep_size(frames_np_rgba_mono[i], scale_h, scale_w)
else:
for i in range(len(frames_np_rgba)):
if aug_2d:
if rescale_flag:
frames_np_rgba[i] = scale_image_hw_keep_size(frames_np_rgba[i], scale_h, scale_w)
border_mask = frames_np_rgba[i][:, :, 3] == 0
frames_np_rgba[i][:, :, :3][border_mask] = bg_color
if reshape_pool is not None and random.random() < 0.04:
# 4%的概率完全消除某些帧,两组同步
frames_np_rgba[i][:, :, :3] = bg_color
if frames_np_rgba_mono is not None:
frames_np_rgba_mono[i][:, :, 0:3] = 0
if frames_np_rgba_mono is not None and rescale_flag:
frames_np_rgba_mono[i] = scale_image_hw_keep_size(frames_np_rgba_mono[i], scale_h, scale_w)
if binary_mask is not None or person_colors is not None:
return frames_np_rgba, frames_np_rgba_mono
return frames_np_rgba
def render_multi_nlf_as_images(data, poses, reshape_pool=None, intrinsic_matrix=None, draw_2d=True, aug_2d=False, aug_cam=False):
""" return a list of images """
height, width = data[0]['video_height'], data[0]['video_width']
video_length = len(data)
second_person_base_colors_255_dict = {
# Warm Colors for Right Side (R.) - Red, Orange, Yellow
"Red": [255, 20, 20],
"Orange": [255, 60, 0],
"Golden Orange": [255, 110, 0],
"Yellow": [255, 200, 0],
"Yellow-Green": [160, 255, 40],
# Cool Colors for Left Side (L.) - Green, Blue, Purple
"Bright Green": [0, 255, 50],
"Light Green-Blue": [0, 255, 100],
"Aqua": [0, 255, 200],
"Cyan": [0, 230, 255],
"Sky Blue": [0, 130, 255],
"Medium Blue": [0, 70, 255],
"Pure Blue": [0, 0, 255],
"Purple-Blue": [80, 0, 255],
"Medium Purple": [160, 0, 255],
# Neutral/Central Colors (e.g., for Neck, Nose, Eyes, Ears)
"Grey": [130, 130, 130],
"Pink-Magenta": [255, 0, 150],
"Dark Pink": [255, 0, 100],
"Violet": [120, 0, 255],
"Dark Violet": [60, 0, 255],
}
first_person_base_colors_255_dict = {
# Warm Colors for Right Side (R.) - Red, Orange, Yellow
"Red": [255, 150, 150],
"Orange": [255, 180, 140],
"Golden Orange": [255, 215, 150],
"Yellow": [255, 240, 170],
"Yellow-Green": [200, 255, 100],
# Cool Colors for Left Side (L.) - Green, Blue, Purple
"Bright Green": [100, 255, 100],
"Light Green-Blue": [140, 255, 180],
"Aqua": [150, 240, 200],
"Cyan": [180, 230, 240],
"Sky Blue": [160, 200, 255],
"Medium Blue": [100, 120, 255],
"Pure Blue": [120, 140, 255],
"Purple-Blue": [180, 90, 255],
"Medium Purple": [190, 120, 255],
# Neutral/Central Colors (e.g., for Neck, Nose, Eyes, Ears)
"Grey": [210, 210, 210],
"Pink-Magenta": [255, 120, 200],
"Dark Pink": [255, 150, 180],
"Violet": [200, 90, 255],
"Dark Violet": [130, 80, 255],
}
base_colors_255_dict_list = [first_person_base_colors_255_dict, second_person_base_colors_255_dict]
ordered_colors_255_list = [[
base_colors_255_dict["Red"], # Neck -> R. Shoulder (Red)
base_colors_255_dict["Cyan"], # Neck -> L. Shoulder (Cyan)
base_colors_255_dict["Orange"], # R. Shoulder -> R. Elbow (Orange)
base_colors_255_dict["Golden Orange"], # R. Elbow -> R. Wrist (Golden Orange)
base_colors_255_dict["Sky Blue"], # L. Shoulder -> L. Elbow (Sky Blue)
base_colors_255_dict["Medium Blue"], # L. Elbow -> L. Wrist (Medium Blue)
base_colors_255_dict["Yellow-Green"], # Neck -> R. Hip ( Yellow-Green)
base_colors_255_dict["Bright Green"], # R. Hip -> R. Knee (Bright Green - transitioning warm to cool spectrum)
base_colors_255_dict["Light Green-Blue"], # R. Knee -> R. Ankle (Light Green-Blue - transitioning)
base_colors_255_dict["Pure Blue"], # Neck -> L. Hip (Pure Blue)
base_colors_255_dict["Purple-Blue"], # L. Hip -> L. Knee (Purple-Blue)
base_colors_255_dict["Medium Purple"], # L. Knee -> L. Ankle (Medium Purple)
base_colors_255_dict["Grey"], # Neck -> Nose (Grey)
base_colors_255_dict["Pink-Magenta"], # Nose -> R. Eye (Pink/Magenta)
base_colors_255_dict["Dark Violet"], # R. Eye -> R. Ear (Dark Pink)
base_colors_255_dict["Pink-Magenta"], # Nose -> L. Eye (Violet)
base_colors_255_dict["Dark Violet"], # L. Eye -> L. Ear (Dark Violet)
] for base_colors_255_dict in base_colors_255_dict_list]
limb_seq = [
[1, 2], # 0 Neck -> R. Shoulder
[1, 5], # 1 Neck -> L. Shoulder
[2, 3], # 2 R. Shoulder -> R. Elbow
[3, 4], # 3 R. Elbow -> R. Wrist
[5, 6], # 4 L. Shoulder -> L. Elbow
[6, 7], # 5 L. Elbow -> L. Wrist
[1, 8], # 6 Neck -> R. Hip
[8, 9], # 7 R. Hip -> R. Knee
[9, 10], # 8 R. Knee -> R. Ankle
[1, 11], # 9 Neck -> L. Hip
[11, 12], # 10 L. Hip -> L. Knee
[12, 13], # 11 L. Knee -> L. Ankle
[1, 0], # 12 Neck -> Nose
[0, 14], # 13 Nose -> R. Eye
[14, 16], # 14 R. Eye -> R. Ear
[0, 15], # 15 Nose -> L. Eye
[15, 17], # 16 L. Eye -> L. Ear
]
draw_seq = [0, 2, 3, # Neck -> R. Shoulder -> R. Elbow -> R. Wrist
1, 4, 5, # Neck -> L. Shoulder -> L. Elbow -> L. Wrist
6, 7, 8, # Neck -> R. Hip -> R. Knee -> R. Ankle
9, 10, 11, # Neck -> L. Hip -> L. Knee -> L. Ankle
12, # Neck -> Nose
13, 14, # Nose -> R. Eye -> R. Ear
15, 16, # Nose -> L. Eye -> L. Ear
] # 从近心端往外扩展
colors_first = [[c / 300 + 0.15 for c in color_rgb] + [0.8] for color_rgb in ordered_colors_255_list[0]]
colors_second = [[c / 300 + 0.15 for c in color_rgb] + [0.8] for color_rgb in ordered_colors_255_list[1]]
smpl_poses_first, smpl_poses_second = collect_smpl_poses_samurai(data)
if intrinsic_matrix is None:
intrinsic_matrix = intrinsic_matrix_from_field_of_view((height, width))
focal_x = intrinsic_matrix[0,0]
focal_y = intrinsic_matrix[1,1]
princpt = (intrinsic_matrix[0,2], intrinsic_matrix[1,2]) # 主点 (cx, cy)
# 串行获取每一帧的cylinder_specs
cylinder_specs_list = []
for i in range(video_length):
cylinder_specs_first = get_single_pose_cylinder_specs((i, smpl_poses_first[i], None, None, None, None, colors_first, limb_seq, draw_seq))
cylinder_specs_second = get_single_pose_cylinder_specs((i, smpl_poses_second[i], None, None, None, None, colors_second, limb_seq, draw_seq))
cylinder_specs = cylinder_specs_first + cylinder_specs_second
cylinder_specs_list.append(cylinder_specs)
frames_np_rgba = render_whole(cylinder_specs_list, H=height, W=width, fx=focal_x, fy=focal_y, cx=princpt[0], cy=princpt[1])
if poses is not None and draw_2d:
aligned_poses = copy.deepcopy(poses)
canvas_2d = draw_pose_to_canvas_np(aligned_poses, pool=None, H=height, W=width, reshape_scale=0, show_feet_flag=False, show_body_flag=False, show_cheek_flag=True, dw_hand=True)
for i in range(len(frames_np_rgba)):
frame_img = frames_np_rgba[i]
canvas_img = canvas_2d[i]
mask = canvas_img != 0
frame_img[:, :, :3][mask] = canvas_img[mask]
frames_np_rgba[i] = frame_img
return frames_np_rgba
def run_nlf_from_masks(video_frames, masks, colors, model_nlf, nlf_render_path,
nlf_render_mask_path, fps=16, detector=None):
"""对每个人用墨绿色背景隔离后提取 NLF 姿态,再分别渲染普通和 mono 结果并保存为 MP4。
Args:
video_frames: (T, H, W, 3) uint8 numpy array, RGB
masks: list of (T, H, W) bool ndarray,每人一个
colors: list of BGR color tuples,与 masks 一一对应
model_nlf: TorchScript NLF 模型
nlf_render_path: 普通渲染输出路径(含 2D 关键点叠加)
nlf_render_mask_path: mono 渲染输出路径
fps: 输出帧率
detector: DWposeDetector,对原始帧提取多人 2D 关键点
"""
from NLFPoseExtract.extract_nlfpose_batch import process_video_multi_nlf
if len(masks) == 0:
print("No masks provided, skipping.")
return
T, H, W, C = video_frames.shape
dark_green = np.array([0, 100, 0], dtype=np.uint8)
vr_frames_list = []
for mask in masks:
person_frames = np.full((T, H, W, C), dark_green, dtype=np.uint8)
person_frames[mask] = video_frames[mask]
vr_frames_list.append(torch.from_numpy(person_frames))
nlf_results = process_video_multi_nlf(model_nlf, vr_frames_list)
poses = None
if detector is not None:
# Per-person DWpose: run detector on each SAM3 person's dark_green-bg crop so the
# 2D keypoints (face/hands/body) align with SAM3 person order. Stack the per-person
# single-person dicts back into multi-person dicts per frame, in SAM3 order.
N = len(masks)
EMPTY_BODY = np.full((24, 2), -1.0, dtype=np.float32)
EMPTY_SUBSET = np.full((24,), -1.0, dtype=np.float32)
EMPTY_FACE = np.full((68, 2), -1.0, dtype=np.float32)
EMPTY_HAND = np.full((21, 2), -1.0, dtype=np.float32)
per_person_per_frame = [[None] * T for _ in range(N)]
for p_idx in range(N):
person_frames_np = vr_frames_list[p_idx].numpy() # (T, H, W, 3) RGB, dark_green bg
for t in range(T):
pose_dict, _, _ = detector(Image.fromarray(person_frames_np[t]))
per_person_per_frame[p_idx][t] = pose_dict
poses = []
for t in range(T):
cand_rows, sub_rows, face_rows = [], [], []
hand_rows = []
for p_idx in range(N):
pd = per_person_per_frame[p_idx][t]
cands = pd['bodies']['candidate']
if cands is not None and len(cands) > 0:
cand_rows.append(cands[0])
sub_rows.append(pd['bodies']['subset'][0])
face_rows.append(pd['faces'][0])
hand_rows.append(pd['hands'][0])
hand_rows.append(pd['hands'][1])
else:
cand_rows.append(EMPTY_BODY)
sub_rows.append(EMPTY_SUBSET)
face_rows.append(EMPTY_FACE)
hand_rows.append(EMPTY_HAND)
hand_rows.append(EMPTY_HAND)
poses.append({
'bodies': {
'candidate': np.stack(cand_rows, axis=0),
'subset': np.stack(sub_rows, axis=0),
},
'faces': np.stack(face_rows, axis=0),
'hands': np.stack(hand_rows, axis=0),
})
person_colors_rgba = []
for bgr in colors:
b, g, r = bgr[0] / 255.0, bgr[1] / 255.0, bgr[2] / 255.0
person_colors_rgba.append([r, g, b, 1.0])
palette_offset = 1 if len(masks) == 1 else 0
frames_regular, frames_mono = render_nlf_as_images(
copy.deepcopy(nlf_results), poses=copy.deepcopy(poses),
reshape_pool=None, intrinsic_matrix=None,
draw_2d=True, aug_2d=False, aug_cam=False,
person_colors=person_colors_rgba, palette_offset=palette_offset,
)
for out_path in (nlf_render_path, nlf_render_mask_path):
out_dir = os.path.dirname(out_path)
if out_dir:
os.makedirs(out_dir, exist_ok=True)
frames_regular_rgb = [f[:, :, :3] for f in frames_regular]
frames_mono_rgb = [f[:, :, :3] for f in frames_mono]
mpy.ImageSequenceClip(frames_regular_rgb, fps=fps).write_videofile(nlf_render_path)
mpy.ImageSequenceClip(frames_mono_rgb, fps=fps).write_videofile(nlf_render_mask_path)