Files
toyxyz-ComfyUI_toyxyz_test_…/openposeeditor/poseinter.py
T
2025-10-22 03:02:05 +09:00

824 lines
46 KiB
Python

import torch
import cv2
import numpy as np
import copy
from matplotlib.colors import hsv_to_rgb
import json
DEFAULT_BODY_LIMB_THICKNESS = 6
DEFAULT_BODY_POINT_RADIUS = 5
DEFAULT_HAND_LIMB_THICKNESS = 2
DEFAULT_HAND_POINT_RADIUS = 3
DEFAULT_FACE_POINT_RADIUS = 2
# --- 스켈레톤, 색상, KP 딕셔너리 정의 ---
body_colors = [
[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0],
[0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255],
[170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]
]
face_color = [255, 255, 255]
hand_keypoint_color = [0, 0, 255]
hand_limb_colors = [
[255,0,0],[255,60,0],[255,120,0],[255,180,0], [180,255,0],[120,255,0],[60,255,0],[0,255,0],
[0,255,60],[0,255,120],[0,255,180],[0,180,255], [0,120,255],[0,60,255],[0,0,255],[60,0,255],
[120,0,255],[180,0,255],[255,0,180],[255,0,120]
]
body_skeleton = [
[1, 2], [1, 5], [2, 3], [3, 4], [5, 6], [6, 7], [1, 8], [8, 9], [9, 10], [1, 11],
[11, 12], [12, 13], [1, 0], [0, 14], [14, 16], [0, 15], [15, 17]
]
face_skeleton = []
hand_skeleton = [
[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], [10, 11], [11, 12],
[0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]
]
KP = {
"Nose": 0, "Neck": 1, "RShoulder": 2, "RElbow": 3, "RWrist": 4,
"LShoulder": 5, "LElbow": 6, "LWrist": 7, "MidHip": 8, "RHip": 9,
"RKnee": 10, "RAnkle": 11, "LHip": 12, "LKnee": 13, "LAnkle": 14,
"REye": 15, "LEye": 16, "REar": 17, "LEar": 18, "LBigToe": 19,
"LSmallToe": 20, "LHeel": 21, "RBigToe": 22, "RSmallToe": 23, "RHeel": 24
}
def calculate_bone_length(kps, p1_idx, p2_idx):
if kps.shape[0] <= max(p1_idx, p2_idx): return 0.0
if kps[p1_idx, 2] == 0 or kps[p2_idx, 2] == 0: return 0.0
p1 = kps[p1_idx, :2]
p2 = kps[p2_idx, :2]
return np.linalg.norm(p1 - p2)
def get_valid_kps_coords(kps_np, confidence_threshold=0.1):
if kps_np is None or kps_np.ndim != 2 or kps_np.shape[1] != 3 or kps_np.size == 0: return None
valid_points = kps_np[kps_np[:, 2] > confidence_threshold][:, :2]
return valid_points if valid_points.shape[0] > 0 else None
def get_bounding_box_area_and_center(kps_np, confidence_threshold=0.1):
if kps_np is None or kps_np.ndim != 2 or kps_np.shape[1] != 3 or kps_np.size == 0:
return 0.0, None
valid_points_xy = []
for i in range(kps_np.shape[0]):
if kps_np[i, 2] > confidence_threshold:
valid_points_xy.append(kps_np[i, :2])
if not valid_points_xy or len(valid_points_xy) < 1:
return 0.0, None
valid_points_xy = np.array(valid_points_xy)
min_x, min_y = np.min(valid_points_xy, axis=0)
max_x, max_y = np.max(valid_points_xy, axis=0)
width, height = max_x - min_x, max_y - min_y
area = 0.0
if valid_points_xy.shape[0] >=2 :
area = width * height if width > 1e-6 and height > 1e-6 else 0.0
center = (np.mean(valid_points_xy[:, 0]), np.mean(valid_points_xy[:, 1]))
return area, center
def adjust_pose_to_reference_size(source_kps, ref_kps, confidence_threshold=0.1):
if source_kps.size == 0 or ref_kps.size == 0:
return source_kps
adjusted_kps = source_kps.copy()
RShoulder, LShoulder, RHip, LHip, Neck = KP["RShoulder"], KP["LShoulder"], KP["RHip"], KP["LHip"], KP["Neck"]
required_indices = [RShoulder, LShoulder, RHip, LHip, Neck]
if not all(idx < source_kps.shape[0] and source_kps[idx, 2] > confidence_threshold for idx in required_indices) or \
not all(idx < ref_kps.shape[0] and ref_kps[idx, 2] > confidence_threshold for idx in required_indices):
pass
else:
src_shoulder_width = np.linalg.norm(source_kps[LShoulder, :2] - source_kps[RShoulder, :2])
src_shoulder_center = 0.5 * (source_kps[LShoulder, :2] + source_kps[RShoulder, :2])
src_hip_center = 0.5 * (source_kps[LHip, :2] + source_kps[RHip, :2])
src_torso_height = np.linalg.norm(src_shoulder_center - src_hip_center)
ref_shoulder_width = np.linalg.norm(ref_kps[LShoulder, :2] - ref_kps[RShoulder, :2])
ref_shoulder_center = 0.5 * (ref_kps[LShoulder, :2] + ref_kps[RShoulder, :2])
ref_hip_center = 0.5 * (ref_kps[LHip, :2] + ref_kps[RHip, :2])
ref_torso_height = np.linalg.norm(ref_shoulder_center - ref_hip_center)
x_ratio = ref_shoulder_width / src_shoulder_width if src_shoulder_width > 1e-6 else 1.0
y_ratio = ref_torso_height / src_torso_height if src_torso_height > 1e-6 else 1.0
neck_pos = adjusted_kps[Neck, :2].copy()
for i in range(adjusted_kps.shape[0]):
if adjusted_kps[i, 2] > 0:
vec_from_neck = adjusted_kps[i, :2] - neck_pos
vec_from_neck[0] *= x_ratio
vec_from_neck[1] *= y_ratio
adjusted_kps[i, :2] = neck_pos + vec_from_neck
bones_to_adjust = [
(KP["Neck"], KP["Nose"], [KP["Nose"], KP["REye"], KP["LEye"], KP["REar"], KP["LEar"]]),
(KP["RShoulder"], KP["RElbow"], [KP["RElbow"], KP["RWrist"]]),
(KP["RElbow"], KP["RWrist"], [KP["RWrist"]]),
(KP["LShoulder"], KP["LElbow"], [KP["LElbow"], KP["LWrist"]]),
(KP["LElbow"], KP["LWrist"], [KP["LWrist"]]),
(KP["RHip"], KP["RKnee"], [KP["RKnee"], KP["RAnkle"], KP["RBigToe"], KP["RSmallToe"], KP["RHeel"]]),
(KP["RKnee"], KP["RAnkle"], [KP["RAnkle"], KP["RBigToe"], KP["RSmallToe"], KP["RHeel"]]),
(KP["LHip"], KP["LKnee"], [KP["LKnee"], KP["LAnkle"], KP["LBigToe"], KP["LSmallToe"], KP["LHeel"]]),
(KP["LKnee"], KP["LAnkle"], [KP["LAnkle"], KP["LBigToe"], KP["LSmallToe"], KP["LHeel"]]),
]
for parent_idx, child_idx, children_indices in bones_to_adjust:
if max(parent_idx, child_idx) >= adjusted_kps.shape[0] or max(parent_idx, child_idx) >= ref_kps.shape[0]: continue
if adjusted_kps[parent_idx, 2] < confidence_threshold or adjusted_kps[child_idx, 2] < confidence_threshold or \
ref_kps[parent_idx, 2] < confidence_threshold or ref_kps[child_idx, 2] < confidence_threshold:
continue
len_source = calculate_bone_length(adjusted_kps, parent_idx, child_idx)
len_ref = calculate_bone_length(ref_kps, parent_idx, child_idx)
if len_source == 0 or len_ref == 0: continue
ratio = len_ref / len_source
if abs(1.0 - ratio) < 0.01: continue
parent_pos = adjusted_kps[parent_idx, :2]
child_pos_old = adjusted_kps[child_idx, :2]
vector = child_pos_old - parent_pos
vector_new = vector * ratio
child_pos_new = parent_pos + vector_new
offset = child_pos_new - child_pos_old
for idx in children_indices:
if idx < adjusted_kps.shape[0] and adjusted_kps[idx, 2] > 0:
adjusted_kps[idx, :2] += offset
if Neck < adjusted_kps.shape[0] and Neck < ref_kps.shape[0] and \
adjusted_kps[Neck, 2] > confidence_threshold and ref_kps[Neck, 2] > confidence_threshold:
final_offset = ref_kps[Neck, :2] - adjusted_kps[Neck, :2]
for i in range(adjusted_kps.shape[0]):
if adjusted_kps[i, 2] > 0:
adjusted_kps[i, :2] += final_offset
head_kp_indices = [KP["Nose"], KP["REye"], KP["LEye"], KP["REar"], KP["LEar"]]
ref_head_points = np.array([ref_kps[i] for i in head_kp_indices if i < ref_kps.shape[0] and ref_kps[i, 2] > confidence_threshold])
adj_head_points = np.array([adjusted_kps[i] for i in head_kp_indices if i < adjusted_kps.shape[0] and adjusted_kps[i, 2] > confidence_threshold])
if ref_head_points.shape[0] >= 2 and adj_head_points.shape[0] >= 2:
ref_head_area, _ = get_bounding_box_area_and_center(ref_head_points, confidence_threshold)
adj_head_area, adj_head_center = get_bounding_box_area_and_center(adj_head_points, confidence_threshold)
if adj_head_area > 1e-6 and ref_head_area > 1e-6 and adj_head_center is not None:
scale_factor_head = np.sqrt(ref_head_area / adj_head_area)
if abs(1.0 - scale_factor_head) > 0.01:
center_x, center_y = adj_head_center
for kp_idx in head_kp_indices:
if kp_idx < adjusted_kps.shape[0] and adjusted_kps[kp_idx, 2] > confidence_threshold:
x, y, _ = adjusted_kps[kp_idx]
adjusted_kps[kp_idx, 0] = center_x + (x - center_x) * scale_factor_head
adjusted_kps[kp_idx, 1] = center_y + (y - center_y) * scale_factor_head
return adjusted_kps
def adjust_face_keypoints_size(full_face_kps_np, scale_factor, center_xy, confidence_threshold=0.1):
adjusted_kps = full_face_kps_np.copy()
if center_xy is None : return adjusted_kps
center_x, center_y = center_xy
for i in range(adjusted_kps.shape[0]):
x, y, conf = adjusted_kps[i]
if conf > confidence_threshold:
adjusted_kps[i, :2] = (center_x + (x - center_x) * scale_factor, center_y + (y - center_y) * scale_factor)
return adjusted_kps
def adjust_face_to_maintain_relative_offset(orig_target_body_kps, orig_target_face_kps, adjusted_body_kps, adjusted_face_kps, confidence_threshold=0.1):
FACE_NOSE_INDEX = 8
if not (orig_target_body_kps.shape[0] > KP["Nose"] and orig_target_body_kps[KP["Nose"], 2] >= confidence_threshold and
orig_target_face_kps.shape[0] > FACE_NOSE_INDEX and orig_target_face_kps[FACE_NOSE_INDEX, 2] >= confidence_threshold and
adjusted_body_kps.shape[0] > KP["Nose"] and adjusted_body_kps[KP["Nose"], 2] >= confidence_threshold and
adjusted_face_kps.shape[0] > FACE_NOSE_INDEX and adjusted_face_kps[FACE_NOSE_INDEX, 2] >= confidence_threshold):
return adjusted_face_kps
orig_body_nose_pos = orig_target_body_kps[KP["Nose"], :2]
orig_face_nose_pos = orig_target_face_kps[FACE_NOSE_INDEX, :2]
original_offset = orig_face_nose_pos - orig_body_nose_pos
adjusted_body_nose_pos = adjusted_body_kps[KP["Nose"], :2]
current_face_nose_pos = adjusted_face_kps[FACE_NOSE_INDEX, :2]
desired_face_nose_pos = adjusted_body_nose_pos + original_offset
translation_vector = desired_face_nose_pos - current_face_nose_pos
final_face_kps = adjusted_face_kps.copy()
for i in range(final_face_kps.shape[0]):
if final_face_kps[i, 2] > confidence_threshold:
final_face_kps[i, :2] += translation_vector
return final_face_kps
def calculate_hand_intrinsic_properties(hand_kps_np, confidence_threshold=0.1):
if hand_kps_np is None or hand_kps_np.size == 0: return None
hand_kp0_abs = None
if hand_kps_np.shape[0] > 0 and hand_kps_np[0, 2] > confidence_threshold:
hand_kp0_abs = hand_kps_np[0, :2].copy()
valid_hand_coords_xy = get_valid_kps_coords(hand_kps_np, confidence_threshold)
scale = 0.0
if valid_hand_coords_xy is not None and valid_hand_coords_xy.shape[0] >= 2:
min_x, min_y = np.min(valid_hand_coords_xy, axis=0)
max_x, max_y = np.max(valid_hand_coords_xy, axis=0)
width, height = max_x - min_x, max_y - min_y
scale = np.sqrt(width**2 + height**2) if width > 1e-6 and height > 1e-6 else 0.0
return {'scale': scale, 'kp0_abs': hand_kp0_abs}
def transform_hand_final(target_hand_kps_np_orig, target_body_wrist_pos_xy_adj, ref_hand_kps_np_orig, ref_body_wrist_pos_xy_orig, confidence_threshold=0.1):
ref_props = calculate_hand_intrinsic_properties(ref_hand_kps_np_orig, confidence_threshold)
target_orig_props = calculate_hand_intrinsic_properties(target_hand_kps_np_orig, confidence_threshold)
if not all([ref_props, target_orig_props,
ref_props.get('kp0_abs') is not None, target_orig_props.get('kp0_abs') is not None,
ref_body_wrist_pos_xy_orig is not None, target_body_wrist_pos_xy_adj is not None]):
return target_hand_kps_np_orig.flatten().tolist() if target_hand_kps_np_orig is not None and target_hand_kps_np_orig.size > 0 else []
ref_hand_kp0_pos, ref_scale = ref_props['kp0_abs'], ref_props['scale']
target_orig_hand_kp0_pos, target_orig_scale = target_orig_props['kp0_abs'], target_orig_props['scale']
ref_offset_bodywrist_to_handkp0 = ref_hand_kp0_pos - ref_body_wrist_pos_xy_orig
scale_factor = ref_scale / target_orig_scale if target_orig_scale > 1e-6 else 1.0
scaled_target_hand_kps = target_hand_kps_np_orig.copy()
pivot_for_scaling = target_orig_hand_kp0_pos.copy()
for i in range(scaled_target_hand_kps.shape[0]):
if scaled_target_hand_kps[i, 2] > confidence_threshold:
vec_from_pivot = scaled_target_hand_kps[i, :2] - pivot_for_scaling
scaled_target_hand_kps[i, :2] = pivot_for_scaling + (vec_from_pivot * scale_factor)
if scaled_target_hand_kps.shape[0] == 0 or scaled_target_hand_kps[0, 2] <= confidence_threshold:
return scaled_target_hand_kps.flatten().tolist()
current_abs_pos_of_scaled_target_hand_kp0 = scaled_target_hand_kps[0, :2]
desired_abs_pos_for_target_hand_kp0 = target_body_wrist_pos_xy_adj + ref_offset_bodywrist_to_handkp0
translation_vector = desired_abs_pos_for_target_hand_kp0 - current_abs_pos_of_scaled_target_hand_kp0
for i in range(scaled_target_hand_kps.shape[0]):
if scaled_target_hand_kps[i, 2] > confidence_threshold:
scaled_target_hand_kps[i, :2] += translation_vector
return scaled_target_hand_kps.flatten().tolist()
def draw_keypoints_and_skeleton(image, keypoints_data, skeleton_connections,
colors_config,
limb_thickness,
point_radius,
confidence_threshold=0.1,
is_body=False, is_face=False, is_hand=False,
hand_edges_count=0):
if not keypoints_data or len(keypoints_data) % 3 != 0: return
tri_tuples = [keypoints_data[i:i + 3] for i in range(0, len(keypoints_data), 3)]
if skeleton_connections:
for i, (joint_idx_a, joint_idx_b) in enumerate(skeleton_connections):
if joint_idx_a >= len(tri_tuples) or joint_idx_b >= len(tri_tuples): continue
a_x_f, a_y_f, a_confidence = tri_tuples[joint_idx_a]
b_x_f, b_y_f, b_confidence = tri_tuples[joint_idx_b]
if a_confidence >= confidence_threshold and b_confidence >= confidence_threshold:
a_x, a_y = int(round(a_x_f)), int(round(a_y_f))
b_x, b_y = int(round(b_x_f)), int(round(b_y_f))
current_limb_color = None
if is_body:
current_limb_color = tuple(colors_config[i % len(colors_config)])
elif is_hand:
if hand_edges_count > 0:
rgb_color = hsv_to_rgb([i / float(hand_edges_count), 1.0, 1.0])
current_limb_color = tuple((np.array(rgb_color) * 255).astype(np.uint8).tolist())
else:
current_limb_color = tuple(colors_config[i % len(colors_config)])
elif is_face:
if skeleton_connections:
current_limb_color = tuple(colors_config)
if current_limb_color is not None:
if is_body:
center_x, center_y = (a_x + b_x) // 2, (a_y + b_y) // 2
length = np.linalg.norm(np.array([a_x, a_y]) - np.array([b_x, b_y]))
if length >= 1:
angle_rad = np.arctan2(b_y - a_y, b_x - a_x)
angle_deg = np.degrees(angle_rad)
ellipse_major_axis = max(1, int(length / 2))
ellipse_minor_axis = max(1, int(limb_thickness / 2))
axes = (ellipse_major_axis, ellipse_minor_axis)
polygon_points = cv2.ellipse2Poly((center_x, center_y), axes, int(angle_deg), 0, 360, 10)
cv2.fillConvexPoly(image, polygon_points, current_limb_color)
else:
cv2.line(image, (a_x, a_y), (b_x, b_y), current_limb_color, limb_thickness)
for i, (x_f, y_f, confidence) in enumerate(tri_tuples):
if confidence >= confidence_threshold:
current_point_color = None
if is_body:
current_point_color = body_colors[i % len(body_colors)]
elif is_hand:
current_point_color = hand_keypoint_color
elif is_face:
current_point_color = face_color
if current_point_color:
cv2.circle(image, (int(round(x_f)), int(round(y_f))), point_radius, tuple(current_point_color), -1)
def gen_skeleton_with_face_hands(pose_keypoints_2d, face_keypoints_2d, hand_left_keypoints_2d, hand_right_keypoints_2d,
canvas_width, canvas_height, landmarkType, confidence_threshold=0.1):
image = np.zeros((canvas_height, canvas_width, 3), dtype=np.uint8)
def scale_keypoints(keypoints, target_w, target_h, input_is_normalized):
if not keypoints or len(keypoints) % 3 != 0 : return []
scaled = []
for i in range(0, len(keypoints), 3):
x, y, conf = keypoints[i:i+3]
scaled.extend([x * target_w, y * target_h, conf] if input_is_normalized else [x, y, conf])
return scaled
input_normalized = (landmarkType == "OpenPose")
scaled_pose = scale_keypoints(pose_keypoints_2d, canvas_width, canvas_height, input_normalized)
scaled_face = scale_keypoints(face_keypoints_2d, canvas_width, canvas_height, input_normalized)
scaled_hand_left = scale_keypoints(hand_left_keypoints_2d, canvas_width, canvas_height, input_normalized)
scaled_hand_right = scale_keypoints(hand_right_keypoints_2d, canvas_width, canvas_height, input_normalized)
draw_keypoints_and_skeleton(image, scaled_pose, body_skeleton, body_colors,
DEFAULT_BODY_LIMB_THICKNESS, DEFAULT_BODY_POINT_RADIUS,
confidence_threshold, is_body=True)
if scaled_face:
draw_keypoints_and_skeleton(image, scaled_face, face_skeleton, face_color,
0, DEFAULT_FACE_POINT_RADIUS,
confidence_threshold, is_face=True)
if scaled_hand_left:
draw_keypoints_and_skeleton(image, scaled_hand_left, hand_skeleton, hand_limb_colors,
DEFAULT_HAND_LIMB_THICKNESS, DEFAULT_HAND_POINT_RADIUS,
confidence_threshold, is_hand=True, hand_edges_count=len(hand_skeleton))
if scaled_hand_right:
draw_keypoints_and_skeleton(image, scaled_hand_right, hand_skeleton, hand_limb_colors,
DEFAULT_HAND_LIMB_THICKNESS, DEFAULT_HAND_POINT_RADIUS,
confidence_threshold, is_hand=True, hand_edges_count=len(hand_skeleton))
return image
def transform_all_keypoints(keypoints_1, keypoints_2, frames, interpolation="linear"):
def interpolate_keypoint_set(kp1, kp2, num_frames, interp_method):
kp1 = kp1 if kp1 is not None else []
kp2 = kp2 if kp2 is not None else []
if not kp1 and not kp2: return [[] for _ in range(num_frames)]
len_kp1 = len(kp1)
len_kp2 = len(kp2)
if len_kp1 == 0 and len_kp2 > 0:
kp1 = [0.0] * len_kp2
elif len_kp2 == 0 and len_kp1 > 0:
kp2 = [0.0] * len_kp1
elif len_kp1 != len_kp2 :
print(f"Warning: Keypoint list length mismatch. kp1 len: {len_kp1}, kp2 len: {len_kp2}. Interpolation might be unreliable.")
max_len = max(len_kp1, len_kp2)
if max_len % 3 != 0 :
print(f"Error: Max keypoint list length {max_len} is not a multiple of 3. Returning empty interpolation.")
return [[] for _ in range(num_frames)]
while len(kp1) < max_len: kp1.extend([0.0, 0.0, 0.0])
while len(kp2) < max_len: kp2.extend([0.0, 0.0, 0.0])
if not kp1 and not kp2: return [[] for _ in range(num_frames)]
num_kps1 = len(kp1) // 3
num_kps2 = len(kp2) // 3
if num_kps1 != num_kps2:
print(f"Critical Error: Mismatch in number of keypoints after padding. KPs1: {num_kps1}, KPs2: {num_kps2}")
return [[] for _ in range(num_frames)]
if num_kps1 == 0 : return [[] for _ in range(num_frames)]
tri_tuples_1 = [kp1[i:i + 3] for i in range(0, len(kp1), 3)]
tri_tuples_2 = [kp2[i:i + 3] for i in range(0, len(kp2), 3)]
keypoints_sequence = []
for j in range(num_frames):
interpolated_kps_for_frame = []
t = j / float(num_frames - 1) if num_frames > 1 else 0.0
if interp_method == "ease-in": interp_factor = t * t
elif interp_method == "ease-out": interp_factor = 1 - (1 - t) * (1 - t)
elif interp_method == "ease-in-out":
interp_factor = 4 * t * t * t if t < 0.5 else 1.0 - pow(-2 * t + 2, 3) / 2
else: interp_factor = t
for i in range(num_kps1):
x1, y1, c1 = tri_tuples_1[i]; x2, y2, c2 = tri_tuples_2[i]
new_x, new_y, new_c = 0.0, 0.0, 0.0
if c1 > 0 and c2 > 0:
new_x = x1 + (x2 - x1) * interp_factor
new_y = y1 + (y2 - y1) * interp_factor
new_c = c1 + (c2 - c1) * interp_factor
elif c1 > 0:
new_x, new_y = x1, y1
new_c = c1 * (1.0 - interp_factor)
elif c2 > 0:
new_x, new_y = x2, y2
new_c = c2 * interp_factor
interpolated_kps_for_frame.extend([new_x, new_y, new_c])
keypoints_sequence.append(interpolated_kps_for_frame)
return keypoints_sequence
parts = ['pose', 'face', 'hand_left', 'hand_right']
sequences = {}
for part in parts:
kp1_part = keypoints_1.get(f'{part}_keypoints_2d', [])
kp2_part = keypoints_2.get(f'{part}_keypoints_2d', [])
sequences[part] = interpolate_keypoint_set(kp1_part, kp2_part, frames, interpolation)
combined_sequence = []
for i in range(frames):
combined_frame_data = {}
valid_frame = False
for part in parts:
if i < len(sequences[part]) and sequences[part][i]:
combined_frame_data[f'{part}_keypoints_2d'] = sequences[part][i]
valid_frame = True
else:
combined_frame_data[f'{part}_keypoints_2d'] = []
if valid_frame:
combined_sequence.append(combined_frame_data)
return combined_sequence
def apply_confidence_threshold(keypoints_list, threshold):
if not keypoints_list:
return []
filtered_kps = []
for i in range(0, len(keypoints_list), 3):
x, y, c = keypoints_list[i:i+3]
if c < threshold:
filtered_kps.extend([0.0, 0.0, 0.0])
else:
filtered_kps.extend([x, y, c])
return filtered_kps
class Pose_Inter:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
interpolation_methods = ["linear", "ease-in", "ease-out", "ease-in-out"]
return {
"required": {
"pose_from": ("POSE_KEYPOINT", ), "pose_to": ("POSE_KEYPOINT", ),
"interpolate_frames": ("INT", {"default": 12, "min": 1, "max": 99999, "step": 1}),
"interpolation": (interpolation_methods, {"default": "linear"}),
"confidence_threshold": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}),
"adjust_body_shape": ("BOOLEAN", {"default": False}),
"landmarkType": (["OpenPose", "DWPose"], {"default": "DWPose"}),
"include_face": ("BOOLEAN", {"default": True}),
"include_hands": ("BOOLEAN", {"default": True}),
"pick_frame": ("INT", {"default": 0, "min": 0, "max": 99999, "step": 1}), # Allow negative for pick_frame list items
},
}
RETURN_TYPES = ("IMAGE", "POSE_KEYPOINT",)
RETURN_NAMES = ("image", "pose_keypoint",)
FUNCTION = "run"
CATEGORY = "ToyxyzTestNodes"
def run(self, pose_from, pose_to, interpolate_frames, interpolation, confidence_threshold, landmarkType, include_face, include_hands, adjust_body_shape, pick_frame):
if not pose_from or not pose_to:
raise ValueError("Input 'pose_from' or 'pose_to' data is empty.")
pose_from_list = pose_from if isinstance(pose_from, list) else [pose_from]
pose_to_list = pose_to if isinstance(pose_to, list) else [pose_to]
if not pose_from_list or not pose_to_list:
raise ValueError("Input 'pose_from' or 'pose_to' list is empty after ensuring it's a list.")
if len(pose_from_list) != len(pose_to_list):
raise ValueError(f"Batch size mismatch: 'pose_from' has {len(pose_from_list)} items, 'pose_to' has {len(pose_to_list)} items. They must be equal.")
batch_size = len(pose_from_list)
pick_rules_for_each_item_in_batch = []
if isinstance(pick_frame, list):
# If pick_frame is a list, it applies per batch item if batch_size > 1
# Or, if batch_size is 1, this list applies to that single item.
if batch_size > 1 and len(pick_frame) != batch_size:
raise ValueError(f"'pick_frame' list has {len(pick_frame)} items, but batch size is {batch_size}. They must be equal if 'pick_frame' is a list for multi-item batch processing where each list item corresponds to a batch item.")
for i in range(batch_size):
if batch_size == 1:
# If batch is 1, the entire pick_frame list is the rule for this one item
pick_rules_for_each_item_in_batch.append(pick_frame)
else:
# If batch > 1, then pick_frame[i] is the rule for pose_from_list[i]
# Ensure it's a list, even if it's a single int from the input pick_frame list
rule = pick_frame[i]
pick_rules_for_each_item_in_batch.append([rule] if isinstance(rule, int) else rule)
elif isinstance(pick_frame, int):
# If pick_frame is a single int, this int (wrapped in a list) becomes the rule for all batch items.
pick_rules_for_each_item_in_batch = [[pick_frame]] * batch_size
else:
raise TypeError("'pick_frame' must be an INT or a LIST of INTs/LISTs.")
final_output_images = []
final_output_poses = []
default_canvas_width = 512
default_canvas_height = 512
if batch_size > 0 and pose_from_list[0] and isinstance(pose_from_list[0], dict):
default_canvas_width = pose_from_list[0].get("canvas_width", default_canvas_width)
default_canvas_height = pose_from_list[0].get("canvas_height", default_canvas_height)
for i in range(batch_size):
current_pose_from_dict = pose_from_list[i]
current_pose_to_dict = pose_to_list[i]
current_pick_rule_list_for_item = pick_rules_for_each_item_in_batch[i]
if not isinstance(current_pose_from_dict, dict) or "people" not in current_pose_from_dict or not current_pose_from_dict["people"]:
print(f"Warning: Invalid or no people data in 'pose_from' for batch item {i}. Skipping.")
continue
if not isinstance(current_pose_to_dict, dict) or "people" not in current_pose_to_dict or not current_pose_to_dict["people"]:
print(f"Warning: Invalid or no people data in 'pose_to' for batch item {i}. Skipping.")
continue
person_from = current_pose_from_dict["people"][0]
person_to = current_pose_to_dict["people"][0]
keypoints_from_current_pair = {
'pose_keypoints_2d': person_from.get("pose_keypoints_2d", []),
'face_keypoints_2d': person_from.get("face_keypoints_2d", []) if include_face else [],
'hand_left_keypoints_2d': person_from.get("hand_left_keypoints_2d", []) if include_hands else [],
'hand_right_keypoints_2d': person_from.get("hand_right_keypoints_2d", []) if include_hands else []
}
keypoints_to_current_pair = {
'pose_keypoints_2d': person_to.get("pose_keypoints_2d", []),
'face_keypoints_2d': person_to.get("face_keypoints_2d", []) if include_face else [],
'hand_left_keypoints_2d': person_to.get("hand_left_keypoints_2d", []) if include_hands else [],
'hand_right_keypoints_2d': person_to.get("hand_right_keypoints_2d", []) if include_hands else []
}
original_person_to_face_kps_current = person_to.get("face_keypoints_2d", []) if include_face else []
original_person_to_hand_left_kps_current = person_to.get("hand_left_keypoints_2d", []) if include_hands else []
original_person_to_hand_right_kps_current = person_to.get("hand_right_keypoints_2d", []) if include_hands else []
kps_from_np_body = np.array(keypoints_from_current_pair['pose_keypoints_2d']).reshape(-1, 3) if keypoints_from_current_pair['pose_keypoints_2d'] else np.array([])
kps_to_np_body_for_adjustment = np.array(keypoints_to_current_pair['pose_keypoints_2d']).reshape(-1, 3) if keypoints_to_current_pair['pose_keypoints_2d'] else np.array([])
kps_to_np_body_final_for_interp = kps_to_np_body_for_adjustment.copy()
if adjust_body_shape:
temp_kps_to_body = kps_to_np_body_for_adjustment.copy()
if kps_from_np_body.size > 0 and temp_kps_to_body.size > 0:
adjusted_body_kps_intermediate = adjust_pose_to_reference_size(temp_kps_to_body, kps_from_np_body, confidence_threshold)
keypoints_to_current_pair['pose_keypoints_2d'] = adjusted_body_kps_intermediate.flatten().tolist()
kps_to_np_body_final_for_interp = adjusted_body_kps_intermediate
if include_face:
face_kps_from_np = np.array(keypoints_from_current_pair['face_keypoints_2d']).reshape(-1, 3) if keypoints_from_current_pair['face_keypoints_2d'] else np.array([])
face_kps_to_np_orig = np.array(original_person_to_face_kps_current).reshape(-1, 3) if original_person_to_face_kps_current else np.array([])
if face_kps_from_np.size > 0 and face_kps_to_np_orig.size > 0:
scaled_face_kps_to = face_kps_to_np_orig.copy()
area_from, _ = get_bounding_box_area_and_center(face_kps_from_np, confidence_threshold)
area_to_orig, center_to_face_orig = get_bounding_box_area_and_center(face_kps_to_np_orig, confidence_threshold)
if area_to_orig > 1e-6 and area_from > 1e-6 and center_to_face_orig is not None:
scale_factor = np.sqrt(area_from / area_to_orig)
scaled_face_kps_to = adjust_face_keypoints_size(face_kps_to_np_orig, scale_factor, center_to_face_orig, confidence_threshold)
final_adjusted_face_kps = adjust_face_to_maintain_relative_offset(
kps_to_np_body_for_adjustment,
face_kps_to_np_orig,
kps_to_np_body_final_for_interp,
scaled_face_kps_to,
confidence_threshold
)
keypoints_to_current_pair['face_keypoints_2d'] = final_adjusted_face_kps.flatten().tolist()
if include_hands:
for hand_type in ["left", "right"]:
wrist_kp_name = "LWrist" if hand_type == "left" else "RWrist"
ref_body_wrist_pos_xy_orig, target_body_wrist_pos_xy_adj = None, None
if kps_from_np_body.size > 0 and KP[wrist_kp_name] < kps_from_np_body.shape[0] and kps_from_np_body[KP[wrist_kp_name], 2] > confidence_threshold:
ref_body_wrist_pos_xy_orig = kps_from_np_body[KP[wrist_kp_name], :2]
if kps_to_np_body_final_for_interp.size > 0 and KP[wrist_kp_name] < kps_to_np_body_final_for_interp.shape[0] and kps_to_np_body_final_for_interp[KP[wrist_kp_name], 2] > confidence_threshold:
target_body_wrist_pos_xy_adj = kps_to_np_body_final_for_interp[KP[wrist_kp_name], :2]
ref_hand_kps_list = keypoints_from_current_pair.get(f'hand_{hand_type}_keypoints_2d', [])
ref_hand_kps_np_orig = np.array(ref_hand_kps_list).reshape(-1, 3) if ref_hand_kps_list and len(ref_hand_kps_list) % 3 == 0 else np.array([])
target_hand_kps_list_orig_current = original_person_to_hand_left_kps_current if hand_type == "left" else original_person_to_hand_right_kps_current
target_hand_kps_np_orig_current = np.array(target_hand_kps_list_orig_current).reshape(-1, 3) if target_hand_kps_list_orig_current and len(target_hand_kps_list_orig_current) % 3 == 0 else np.array([])
if ref_body_wrist_pos_xy_orig is not None and target_body_wrist_pos_xy_adj is not None and ref_hand_kps_np_orig.size > 0 and target_hand_kps_np_orig_current.size > 0:
adjusted_hand_kps_list = transform_hand_final(
target_hand_kps_np_orig_current,
target_body_wrist_pos_xy_adj,
ref_hand_kps_np_orig,
ref_body_wrist_pos_xy_orig,
confidence_threshold)
keypoints_to_current_pair[f'hand_{hand_type}_keypoints_2d'] = adjusted_hand_kps_list
interpolated_sequence_for_current_pair = transform_all_keypoints(
keypoints_from_current_pair,
keypoints_to_current_pair,
interpolate_frames,
interpolation
)
frames_to_render_this_item = []
if not interpolated_sequence_for_current_pair:
print(f"Warning: Interpolation failed for batch item {i}. No frames to pick.")
else:
num_available_frames = len(interpolated_sequence_for_current_pair)
if num_available_frames == 0:
print(f"Warning: Interpolation resulted in zero frames for batch item {i}.")
else:
for pick_value in current_pick_rule_list_for_item:
if not isinstance(pick_value, int):
print(f"Warning: Invalid non-integer pick_value '{pick_value}' for batch item {i}. Skipping this pick_value.")
continue
target_frame_num_1_based = pick_value
if target_frame_num_1_based == 0:
if interpolate_frames > 0 :
frames_to_render_this_item.extend(interpolated_sequence_for_current_pair)
else:
if target_frame_num_1_based < 1:
target_frame_num_1_based = 1 # Clamp to 1st frame
if target_frame_num_1_based > num_available_frames:
target_frame_num_1_based = num_available_frames # Clamp to last frame
frames_to_render_this_item.append(interpolated_sequence_for_current_pair[target_frame_num_1_based - 1])
# 중복 프레임 제거 (선택적: 만약 [0, 1] 같은 rule로 인해 중복이 생기는 것을 방지하고 싶다면)
# 이 경우, 추가된 순서가 중요하지 않다면 set으로 변환 후 list로 다시 만들 수 있지만,
# 여기서는 사용자가 명시적으로 여러 번 같은 프레임을 요청할 수도 있으므로 중복 제거 안 함.
canvas_width_current = current_pose_from_dict.get("canvas_width", default_canvas_width)
canvas_height_current = current_pose_from_dict.get("canvas_height", default_canvas_height)
for frame_data in frames_to_render_this_item: # 이미 선택/조정된 프레임 데이터 목록
pose_output_for_this_frame = copy.deepcopy(current_pose_from_dict)
pose_kps_final = apply_confidence_threshold(frame_data.get('pose_keypoints_2d', []), confidence_threshold)
face_kps_final = apply_confidence_threshold(frame_data.get('face_keypoints_2d', []), confidence_threshold)
hand_left_kps_final = apply_confidence_threshold(frame_data.get('hand_left_keypoints_2d', []), confidence_threshold)
hand_right_kps_final = apply_confidence_threshold(frame_data.get('hand_right_keypoints_2d', []), confidence_threshold)
if "people" in pose_output_for_this_frame and pose_output_for_this_frame["people"]:
pose_output_for_this_frame["people"][0]["pose_keypoints_2d"] = pose_kps_final
pose_output_for_this_frame["people"][0]["face_keypoints_2d"] = face_kps_final
pose_output_for_this_frame["people"][0]["hand_left_keypoints_2d"] = hand_left_kps_final
pose_output_for_this_frame["people"][0]["hand_right_keypoints_2d"] = hand_right_kps_final
pose_output_for_this_frame["canvas_width"] = canvas_width_current
pose_output_for_this_frame["canvas_height"] = canvas_height_current
final_output_poses.append(pose_output_for_this_frame)
image_np = gen_skeleton_with_face_hands(
pose_kps_final,
face_kps_final,
hand_left_kps_final,
hand_right_kps_final,
canvas_width_current,
canvas_height_current,
landmarkType,
confidence_threshold
)
image_tensor = torch.from_numpy(image_np.astype(np.float32) / 255.0)
final_output_images.append(image_tensor)
if not final_output_images:
print("Warning: No images were generated across all batch items. Returning a single black image.")
black_image_np = np.zeros((default_canvas_height, default_canvas_width, 3), dtype=np.float32)
return (torch.from_numpy(black_image_np).unsqueeze(0), [])
return (torch.stack(final_output_images), final_output_poses)
class PoseKeypointToCoordStr: #
def __init__(self): #
pass #
@classmethod
def INPUT_TYPES(cls): #
return { #
"required": { #
"pose_keypoint": ("POSE_KEYPOINT",), #
"enable_body": ("BOOLEAN", {"default": True, "label_on": "Body Enabled", "label_off": "Body Disabled"}),
"enable_face": ("BOOLEAN", {"default": True, "label_on": "Face Enabled", "label_off": "Face Disabled"}),
"enable_hand": ("BOOLEAN", {"default": True, "label_on": "Hands Enabled", "label_off": "Hands Disabled"}),
"enable_extra_points": ("BOOLEAN", {"default": False, "label_on": "Extra Body Points Enabled", "label_off": "Extra Body Points Disabled"}),
"num_extra_points_per_bone": ("INT", {"default": 5, "min": 0, "max": 1000, "step": 1, "label": "Extra Points Per Bone"}),
}
}
RETURN_TYPES = ("STRING",) # ComfyUI에서 문자열 리스트를 담는 단일 슬롯으로 처리될 수 있음
RETURN_NAMES = ("coord_str",) #
FUNCTION = "convert_to_coord_str" #
CATEGORY = "ToyxyzTestNodes" #
def convert_to_coord_str(self, pose_keypoint, enable_body, enable_face, enable_hand, enable_extra_points, num_extra_points_per_bone): # num_extra_points_per_bone 파라미터 추가
if not pose_keypoint:
return (["[]"],)
pose_keypoint_list = pose_keypoint if isinstance(pose_keypoint, list) else [pose_keypoint]
if not pose_keypoint_list:
return (["[]"],)
all_poses_kps_extracted = []
max_kps_count_overall = 0
# NUM_EXTRA_POINTS_PER_BONE 상수를 제거하고 입력 파라미터 사용
for pose_data_idx, pose_data in enumerate(pose_keypoint_list):
current_pose_all_coords_dicts_for_frame = []
if pose_data and "people" in pose_data and pose_data["people"]:
person_data = pose_data["people"][0]
# Body points 처리
if enable_body:
body_keypoints_flat = person_data.get("pose_keypoints_2d", [])
if body_keypoints_flat and len(body_keypoints_flat) > 0:
body_kps_triplets = []
for i in range(0, len(body_keypoints_flat), 3):
x = int(body_keypoints_flat[i])
y = int(body_keypoints_flat[i+1])
c = body_keypoints_flat[i+2]
body_kps_triplets.append({"x": x, "y": y, "c": c})
current_pose_all_coords_dicts_for_frame.append({"x": x, "y": y})
# enable_extra_points가 True이고, 사용자가 지정한 추가 포인트 수가 0보다 클 경우
if enable_extra_points and num_extra_points_per_bone > 0:
# body_skeleton은 전역 변수로 가정 (코드 상단에 정의된 것을 사용)
for p1_idx, p2_idx in body_skeleton:
if p1_idx < len(body_kps_triplets) and p2_idx < len(body_kps_triplets):
p1 = body_kps_triplets[p1_idx]
p2 = body_kps_triplets[p2_idx]
if p1["c"] > 0 and p2["c"] > 0: # 두 원본 포인트의 신뢰도가 유효할 때만
for j in range(1, num_extra_points_per_bone + 1):
ratio = j / float(num_extra_points_per_bone + 1)
extra_x = int(p1["x"] + (p2["x"] - p1["x"]) * ratio)
extra_y = int(p1["y"] + (p2["y"] - p1["y"]) * ratio)
current_pose_all_coords_dicts_for_frame.append({"x": extra_x, "y": extra_y})
# Face points 처리
if enable_face:
face_keypoints_flat = person_data.get("face_keypoints_2d", [])
if face_keypoints_flat and len(face_keypoints_flat) > 0:
for i in range(0, len(face_keypoints_flat), 3):
x = int(face_keypoints_flat[i])
y = int(face_keypoints_flat[i+1])
current_pose_all_coords_dicts_for_frame.append({"x": x, "y": y})
# Hand points 처리 (left and right)
if enable_hand:
for hand_type_key in ["hand_left_keypoints_2d", "hand_right_keypoints_2d"]:
hand_keypoints_flat = person_data.get(hand_type_key, [])
if hand_keypoints_flat and len(hand_keypoints_flat) > 0:
for i in range(0, len(hand_keypoints_flat), 3):
x = int(hand_keypoints_flat[i])
y = int(hand_keypoints_flat[i+1])
current_pose_all_coords_dicts_for_frame.append({"x": x, "y": y})
all_poses_kps_extracted.append(current_pose_all_coords_dicts_for_frame)
if len(current_pose_all_coords_dicts_for_frame) > max_kps_count_overall:
max_kps_count_overall = len(current_pose_all_coords_dicts_for_frame)
if max_kps_count_overall == 0:
return (["[]"],)
output_coord_groups_json_str = []
for i in range(max_kps_count_overall):
coords_for_this_track = []
for single_pose_kps_list in all_poses_kps_extracted:
if i < len(single_pose_kps_list):
coords_for_this_track.append(single_pose_kps_list[i])
else:
if coords_for_this_track:
coords_for_this_track.append({"x": coords_for_this_track[-1]["x"], "y": coords_for_this_track[-1]["y"]})
else:
coords_for_this_track.append({"x": 0, "y": 0})
output_coord_groups_json_str.append(json.dumps(coords_for_this_track))
return (output_coord_groups_json_str,)
class JoinPose:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"pose_keypoint_1": ("POSE_KEYPOINT",),
"pose_keypoint_2": ("POSE_KEYPOINT",),
}
}
RETURN_TYPES = ("POSE_KEYPOINT",)
RETURN_NAMES = ("pose_keypoint",)
FUNCTION = "join_poses"
CATEGORY = "ToyxyzTestNodes"
def join_poses(self, pose_keypoint_1, pose_keypoint_2):
# 입력이 None일 경우 빈 리스트로 처리하여 오류 방지
list_1 = pose_keypoint_1 if pose_keypoint_1 is not None else []
list_2 = pose_keypoint_2 if pose_keypoint_2 is not None else []
# 두 리스트를 순서대로 합침
joined_list = list(list_1) + list(list_2)
return (joined_list,)