Files
kijai-ComfyUI-SCAIL-Pose/NLFPoseExtract/nlf_render.py
T

487 lines
22 KiB
Python

import numpy as np
import torch
import os, platform, copy
import logging
if platform.system() == 'Linux':
if 'PYOPENGL_PLATFORM' not in os.environ:
os.environ['PYOPENGL_PLATFORM'] = 'egl'
elif platform.system() == 'Windows':
os.environ.pop('PYOPENGL_PLATFORM', None)
from ..render_3d.taichi_cylinder import render_whole
from ..pose_draw.draw_pose_utils import draw_pose_to_canvas_np
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 process_data_to_COCO_format(joints):
"""Args:
joints: numpy array of shape (24, 2) or (24, 3)
Returns:
new_joints: numpy array of shape (17, 2) or (17, 3)
"""
if joints.ndim != 2:
raise ValueError(f"Expected shape (24,2) or (24,3), got {joints.shape}")
dim = joints.shape[1] # 2D or 3D
mapping = {
15: 0, # head
12: 1, # neck
17: 2, # left shoulder
16: 5, # right shoulder
19: 3, # left elbow
18: 6, # right elbow
21: 4, # left hand
20: 7, # right hand
2: 8, # left pelvis
1: 11, # right pelvis
5: 9, # left knee
4: 12, # right knee
8: 10, # left feet
7: 13, # right feet
}
new_joints = np.zeros((18, dim), dtype=joints.dtype)
for src, dst in mapping.items():
new_joints[dst] = joints[src]
return new_joints
def intrinsic_matrix_from_field_of_view(imshape, fov_degrees:float =55): # nlf default fov_degrees 55
imshape = np.array(imshape)
fov_radians = fov_degrees * np.array(np.pi / 180)
larger_side = np.max(imshape)
focal_length = larger_side / (np.tan(fov_radians / 2) * 2)
# intrinsic_matrix 3*3
return np.array([
[focal_length, 0, imshape[1] / 2],
[0, focal_length, imshape[0] / 2],
[0, 0, 1],
])
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, swap_hands=True, scale_hands=True, scale_x = 1.0, scale_y = 1.0):
########## warning: Will modify body; after shifting, the body is inaccurate ##########
for i in range(len(smpl_poses)):
persons_joints_list = smpl_poses[i]
poses_list = aligned_poses[i]
if len(persons_joints_list) != len(poses_list["bodies"]["candidate"]):
logging.warning(f"Warning: frame {i} has different number of persons between NLF pose and DW pose. NLF: {len(persons_joints_list)}, DW: {len(poses_list['bodies']['candidate'])}. Skipping shift for this frame.")
continue
# For each person inside, take the joints and deform them; also modify 2D; if 3D does not exist, remove the hand/face from 2D as well
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]
# Note: This is not 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_21_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_20_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
if swap_hands:
person_joint_20_2d_shift, person_joint_21_2d_shift = person_joint_21_2d_shift, person_joint_20_2d_shift
face[:, 0] += person_joint_15_2d_shift[0] / width
face[:, 1] += person_joint_15_2d_shift[1] / height
right_hand[:, 0] += person_joint_21_2d_shift[0] / width
right_hand[:, 1] += person_joint_21_2d_shift[1] / height
left_hand[:, 0] += person_joint_20_2d_shift[0] / width
left_hand[:, 1] += person_joint_20_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]).
if scale_hands:
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):
"""Helper function for rendering a single pose, used for parallel processing."""
idx, pose, focal, princpt, height, width, colors, limb_seq, draw_seq = args
cylinder_specs = []
for joints3d in pose: # multiple persons
# Skip if None or not a valid tensor
if joints3d is None:
continue
if isinstance(joints3d, torch.Tensor):
# Check if it's an all-zero tensor (missing person)
if torch.sum(torch.abs(joints3d)) < 0.01:
continue
joints3d = joints3d.cpu().numpy()
elif isinstance(joints3d, np.ndarray):
# Check if it's an all-zero array (missing person)
if np.sum(np.abs(joints3d)) < 0.01:
continue
else:
continue
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 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(smpl_poses, dw_poses, height, width, video_length, intrinsic_matrix=None, draw_2d=True, draw_face=True, draw_hands=True):
""" return a list of images """
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
] # Expanding outward from the proximal end
colors = [[c / 300 + 0.15 for c in color_rgb] + [0.8] for color_rgb in ordered_colors_255]
if dw_poses is not None:
aligned_poses = copy.deepcopy(dw_poses)
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)
# obtain cylinder_specs for each frame
cylinder_specs_list = []
for i in range(video_length):
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)
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 dw_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, show_face_flag=draw_face, show_hand_flag=draw_hands)
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 align_persons_across_frames(smpl_poses, max_persons=2):
"""
Aligns persons across frames so that the same index refers to the same individual.
Uses pelvis joint (index 0) for proximity matching.
"""
video_length = len(smpl_poses)
aligned = [[None for _ in range(max_persons)] for _ in range(video_length)]
# Initialize with first frame
for i in range(min(max_persons, len(smpl_poses[0]))):
aligned[0][i] = smpl_poses[0][i]
for t in range(1, video_length):
prev_persons = [p for p in aligned[t-1] if p is not None]
curr_persons = smpl_poses[t]
assigned = set()
for i, prev_pose in enumerate(prev_persons):
if prev_pose is None:
continue
prev_pelvis = prev_pose[0] # shape (3,)
# Find closest in current frame
min_dist = float('inf')
min_j = -1
for j, curr_pose in enumerate(curr_persons):
if j in assigned:
continue
curr_pelvis = curr_pose[0]
dist = np.linalg.norm(prev_pelvis.cpu().numpy() - curr_pelvis.cpu().numpy())
if dist < min_dist:
min_dist = dist
min_j = j
if min_j >= 0:
aligned[t][i] = curr_persons[min_j]
assigned.add(min_j)
# Fill unassigned slots with zeros
for i in range(max_persons):
if aligned[t][i] is None:
aligned[t][i] = torch.zeros((24, 3), dtype=torch.float32)
return aligned
def render_multi_nlf_as_images(smpl_poses, dw_poses, height, width, video_length, intrinsic_matrix=None, draw_2d=True, draw_face=True, draw_hands=True):
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
] # Expanding outward from the proximal end
# Determine max number of people across all frames
max_persons = max(len(frame) for frame in smpl_poses)
# Align persons across frames
aligned = align_persons_across_frames(smpl_poses, max_persons=max_persons)
# Separate poses by person and assign colors (alternating between two color schemes)
smpl_poses_by_person = []
colors_by_person = []
for person_idx in range(max_persons):
person_poses = [[frame[person_idx]] for frame in aligned]
smpl_poses_by_person.append(person_poses)
# Alternate colors between the two schemes
color_scheme_idx = person_idx % 2
colors = [[c / 300 + 0.15 for c in color_rgb] + [0.8] for color_rgb in ordered_colors_255_list[color_scheme_idx]]
colors_by_person.append(colors)
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)
# obtain cylinder_specs for each frame
cylinder_specs_list = []
for i in range(video_length):
cylinder_specs = []
for person_idx in range(max_persons):
person_specs = get_single_pose_cylinder_specs(
(i, smpl_poses_by_person[person_idx][i], None, None, None, None,
colors_by_person[person_idx], limb_seq, draw_seq)
)
cylinder_specs.extend(person_specs)
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 dw_poses is not None and draw_2d:
aligned_poses = copy.deepcopy(dw_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