""" ProportionChangerParams node - Direct POSE_KEYPOINT editing node Ported from ComfyUI-ultimate-openpose-editor-toyxyz Adjusts pose_keypoint data with key parameters, performing only keypoint transformation without image generation """ import json import copy import math class ProportionChangerParams: """ Parameter adjustment node for direct POSE_KEYPOINT editing Performs only keypoint transformation without image rendering """ @classmethod def INPUT_TYPES(cls): return { "optional": { "pose_keypoint": ("POSE_KEYPOINT", {"default": None}), # === BODY KEYPOINTS (top to bottom order) === "head_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.01}), "neck_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "shoulder_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "arm_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "upper_arm_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "lower_arm_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "torso_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "pelvis_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "leg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "thigh_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "lower_leg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "feet_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), # === FACE KEYPOINTS (face top to bottom order) === "face_shape_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.01}), "eyebrow_height": ("FLOAT", {"default": 0.0, "min": -100.0, "max": 100.0, "step": 0.1}), "left_eyebrow_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.01}), "right_eyebrow_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.01}), "eye_distance_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.01}), "eye_height": ("FLOAT", {"default": 0.0, "min": -100.0, "max": 100.0, "step": 0.1}), "left_eye_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.01}), "right_eye_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.01}), "nose_scale_face": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.01}), "mouth_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.01}), # === HAND KEYPOINTS === "hands_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), # === GLOBAL TRANSFORM === "overall_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "rotate_angle": ("FLOAT", {"default": 0.0, "min": -360.0, "max": 360.0, "step": 0.1}), "translate_x": ("FLOAT", {"default": 0.0, "min": -10000.0, "max": 10000.0, "step": 0.1}), "translate_y": ("FLOAT", {"default": 0.0, "min": -10000.0, "max": 10000.0, "step": 0.1}), } } RETURN_TYPES = ("POSE_KEYPOINT",) RETURN_NAMES = ("changed_pose_keypoint",) FUNCTION = "adjust_pose_keypoint" CATEGORY = "ProportionChanger" @classmethod def IS_CHANGED(cls, **kwargs): """ パラメータが変更された場合は必ず再実行されるようにする ComfyUIのキャッシュ回避のため """ # Hash parameter values to detect changes import hashlib param_str = str(sorted(kwargs.items())) return hashlib.md5(param_str.encode()).hexdigest() def adjust_pose_keypoint(self, pose_keypoint=None, pelvis_scale=1.0, torso_scale=1.0, neck_scale=1.0, head_scale=1.0, eye_distance_scale=1.0, eye_height=0.0, eyebrow_height=0.0, left_eye_scale=1.0, right_eye_scale=1.0, left_eyebrow_scale=1.0, right_eyebrow_scale=1.0, mouth_scale=1.0, nose_scale_face=1.0, face_shape_scale=1.0, shoulder_scale=1.0, arm_scale=1.0, upper_arm_scale=1.0, lower_arm_scale=1.0, leg_scale=1.0, thigh_scale=1.0, lower_leg_scale=1.0, feet_scale=1.0, hands_scale=1.0, overall_scale=1.0, rotate_angle=0.0, translate_x=0.0, translate_y=0.0): """ pose_keypointデータを各種パラメータで調整 Args: pose_keypoint: 入力POSE_KEYPOINTデータ **params: 調整パラメータ Returns: adjusted_pose_keypoint: 調整後のPOSE_KEYPOINTデータ """ # Convert parameters to dictionary format params = { "pelvis_scale": pelvis_scale, "torso_scale": torso_scale, "neck_scale": neck_scale, "head_scale": head_scale, "eye_distance_scale": eye_distance_scale, "eye_height": eye_height, "eyebrow_height": eyebrow_height, "left_eye_scale": left_eye_scale, "right_eye_scale": right_eye_scale, "left_eyebrow_scale": left_eyebrow_scale, "right_eyebrow_scale": right_eyebrow_scale, "mouth_scale": mouth_scale, "nose_scale_face": nose_scale_face, "face_shape_scale": face_shape_scale, "shoulder_scale": shoulder_scale, "arm_scale": arm_scale, "upper_arm_scale": upper_arm_scale, "lower_arm_scale": lower_arm_scale, "leg_scale": leg_scale, "thigh_scale": thigh_scale, "lower_leg_scale": lower_leg_scale, "feet_scale": feet_scale, "hands_scale": hands_scale, "overall_scale": overall_scale, "rotate_angle": rotate_angle, "translate_x": translate_x, "translate_y": translate_y } if pose_keypoint: # Check input data details if isinstance(pose_keypoint, dict): frame_data = pose_keypoint elif isinstance(pose_keypoint, list) and len(pose_keypoint) > 0: frame_data = pose_keypoint[0] else: frame_data = None if frame_data and "people" in frame_data and len(frame_data["people"]) > 0: person = frame_data["people"][0] if "pose_keypoints_2d" in person and len(person["pose_keypoints_2d"]) >= 18: # 肩幅をチェック(Right Shoulder: index 2, Left Shoulder: index 5) kps = person["pose_keypoints_2d"] if len(kps) >= 18: r_shoulder_x, r_shoulder_y = kps[6], kps[7] # index 2 * 3 l_shoulder_x, l_shoulder_y = kps[15], kps[16] # index 5 * 3 shoulder_width = ((r_shoulder_x - l_shoulder_x)**2 + (r_shoulder_y - l_shoulder_y)**2)**0.5 if not pose_keypoint: return (pose_keypoint,) # Do nothing if all parameters are default values default_values = { "pelvis_scale": 1.0, "torso_scale": 1.0, "neck_scale": 1.0, "head_scale": 1.0, "eye_distance_scale": 1.0, "eye_height": 0.0, "eyebrow_height": 0.0, "left_eye_scale": 1.0, "right_eye_scale": 1.0, "left_eyebrow_scale": 1.0, "right_eyebrow_scale": 1.0, "mouth_scale": 1.0, "nose_scale_face": 1.0, "face_shape_scale": 1.0, "shoulder_scale": 1.0, "arm_scale": 1.0, "upper_arm_scale": 1.0, "lower_arm_scale": 1.0, "leg_scale": 1.0, "thigh_scale": 1.0, "lower_leg_scale": 1.0, "feet_scale": 1.0, "hands_scale": 1.0, "overall_scale": 1.0, "rotate_angle": 0.0, "translate_x": 0.0, "translate_y": 0.0 } # Check if all values are default all_default = True for key, default_val in default_values.items(): if abs(params.get(key, default_val) - default_val) > 0.001: # Allow small numerical errors all_default = False break if all_default: # Execute processing even with default values (process from original data, not cached modified data) pass # POSE_KEYPOINTの形式を正規化 (dict形式の場合はリストに変換) # Important: Always create deep copy to never modify the original object if isinstance(pose_keypoint, dict): adjusted_pose = [copy.deepcopy(pose_keypoint)] elif isinstance(pose_keypoint, list): adjusted_pose = copy.deepcopy(pose_keypoint) else: # Always return a copy even for unknown types return (copy.deepcopy(pose_keypoint),) # Process each frame data for frame_data in adjusted_pose: if not isinstance(frame_data, dict) or "people" not in frame_data: continue canvas_width = frame_data.get("canvas_width", 512) canvas_height = frame_data.get("canvas_height", 768) # Adjust pose data for each person for person in frame_data["people"]: self._adjust_person_pose(person, canvas_width, canvas_height, **params) # If input was dict format, return as single dict # Important: Always return as new object to break references to original data if isinstance(pose_keypoint, dict): return (adjusted_pose[0],) else: return (adjusted_pose,) def _adjust_person_pose(self, person, canvas_width, canvas_height, **params): """ 単一人物のポーズデータを調整 """ # Get basic parameters (set default values too) overall_scale = params.get("overall_scale", 1.0) rotate_angle = params.get("rotate_angle", 0.0) translate_x = params.get("translate_x", 0.0) translate_y = params.get("translate_y", 0.0) # 手首の移動量を記録するため、Body keypoints調整前の位置を保存 original_wrist_positions = {} if "pose_keypoints_2d" in person and person["pose_keypoints_2d"]: kps = person["pose_keypoints_2d"] # 手首の位置を記録: Left Wrist (index 7) and Right Wrist (index 4) if len(kps) >= 24: # 8 * 3 = 24 (index 7まで確保) original_wrist_positions["left"] = [kps[21], kps[22]] # index 7 * 3 if len(kps) >= 15: # 5 * 3 = 15 (index 4まで確保) original_wrist_positions["right"] = [kps[12], kps[13]] # index 4 * 3 # Body keypoints調整 if "pose_keypoints_2d" in person and person["pose_keypoints_2d"]: person["pose_keypoints_2d"] = self._adjust_body_keypoints( person["pose_keypoints_2d"], canvas_width, canvas_height, **params ) # 手首の移動量を計算 wrist_movements = {} if "pose_keypoints_2d" in person and person["pose_keypoints_2d"]: kps = person["pose_keypoints_2d"] # 調整後の手首位置と比較 if "left" in original_wrist_positions and len(kps) >= 24: new_left_wrist = [kps[21], kps[22]] wrist_movements["left"] = [ new_left_wrist[0] - original_wrist_positions["left"][0], new_left_wrist[1] - original_wrist_positions["left"][1] ] if "right" in original_wrist_positions and len(kps) >= 15: new_right_wrist = [kps[12], kps[13]] wrist_movements["right"] = [ new_right_wrist[0] - original_wrist_positions["right"][0], new_right_wrist[1] - original_wrist_positions["right"][1] ] # Face keypoints調整 if "face_keypoints_2d" in person and person["face_keypoints_2d"]: person["face_keypoints_2d"] = self._adjust_face_keypoints( person["face_keypoints_2d"], canvas_width, canvas_height, **params ) # Hand keypoints調整(手首の移動量も考慮) if "hand_left_keypoints_2d" in person and person["hand_left_keypoints_2d"]: person["hand_left_keypoints_2d"] = self._adjust_hand_keypoints( person["hand_left_keypoints_2d"], canvas_width, canvas_height, wrist_movement=wrist_movements.get("left", [0.0, 0.0]), **params ) if "hand_right_keypoints_2d" in person and person["hand_right_keypoints_2d"]: person["hand_right_keypoints_2d"] = self._adjust_hand_keypoints( person["hand_right_keypoints_2d"], canvas_width, canvas_height, wrist_movement=wrist_movements.get("right", [0.0, 0.0]), **params ) # 全体変換を最後に適用 self._apply_global_transform( person, canvas_width, canvas_height, overall_scale, rotate_angle, translate_x, translate_y ) def _adjust_body_keypoints(self, keypoints, canvas_width, canvas_height, **params): """ Body keypointsの調整処理 toyxyzの実装を参考にした基本的な部位別スケーリング 全ての変換を元のキーポイントを基準に独立して計算 """ if not keypoints or len(keypoints) < 54: # 18 keypoints * 3 = 54 return keypoints # 元のキーポイントをコピー(参照用) original = keypoints.copy() adjusted = keypoints.copy() # Pose keypoint definitions (corrected based on actual data structure) KP = { "Nose": 0, "Neck": 1, "RShoulder": 2, "RElbow": 3, "RWrist": 4, "LShoulder": 5, "LElbow": 6, "LWrist": 7, "LHip": 8, "LKnee": 9, "LAnkle": 10, "RHip": 11, "RKnee": 12, "RAnkle": 13, "REye": 14, "LEye": 15, "REar": 16, "LEar": 17 } def get_point(kps, index): if index * 3 + 2 >= len(kps) or kps[index * 3 + 2] == 0: return None return [kps[index * 3], kps[index * 3 + 1]] def set_point(kps, index, point): if index * 3 + 1 < len(kps) and point is not None: kps[index * 3] = point[0] kps[index * 3 + 1] = point[1] # Get scaling parameters pelvis_scale = params.get("pelvis_scale", 1.0) torso_scale = params.get("torso_scale", 1.0) neck_scale = params.get("neck_scale", 1.0) head_scale = params.get("head_scale", 1.0) shoulder_scale = params.get("shoulder_scale", 1.0) arm_scale = params.get("arm_scale", 1.0) upper_arm_scale = params.get("upper_arm_scale", 1.0) lower_arm_scale = params.get("lower_arm_scale", 1.0) leg_scale = params.get("leg_scale", 1.0) thigh_scale = params.get("thigh_scale", 1.0) lower_leg_scale = params.get("lower_leg_scale", 1.0) feet_scale = params.get("feet_scale", 1.0) # Get original reference points (always from original keypoints) orig_r_hip = get_point(original, KP["RHip"]) orig_l_hip = get_point(original, KP["LHip"]) orig_neck = get_point(original, KP["Neck"]) orig_nose = get_point(original, KP["Nose"]) if not all([orig_r_hip, orig_l_hip, orig_neck, orig_nose]): return adjusted # Return unchanged if essential keypoints are missing # 階層構造で変換を適用:hip_center -> neck -> shoulders -> arms, legs # 全て元の座標を基準に計算し、依存関係を考慮 # 1. 骨盤の調整(元の座標基準) orig_hip_center = [(orig_r_hip[0] + orig_l_hip[0]) / 2, (orig_r_hip[1] + orig_l_hip[1]) / 2] if pelvis_scale != 1.0: new_r_hip = [ orig_hip_center[0] + (orig_r_hip[0] - orig_hip_center[0]) * pelvis_scale, orig_hip_center[1] + (orig_r_hip[1] - orig_hip_center[1]) * pelvis_scale ] new_l_hip = [ orig_hip_center[0] + (orig_l_hip[0] - orig_hip_center[0]) * pelvis_scale, orig_hip_center[1] + (orig_l_hip[1] - orig_hip_center[1]) * pelvis_scale ] set_point(adjusted, KP["RHip"], new_r_hip) set_point(adjusted, KP["LHip"], new_l_hip) # 新しいhip_centerを計算 current_hip_center = [(new_r_hip[0] + new_l_hip[0]) / 2, (new_r_hip[1] + new_l_hip[1]) / 2] else: current_hip_center = orig_hip_center # 2. Torso adjustment (from original neck, based on hip_center after pelvis adjustment) if torso_scale != 1.0: new_neck = [ current_hip_center[0] + (orig_neck[0] - orig_hip_center[0]) * torso_scale, current_hip_center[1] + (orig_neck[1] - orig_hip_center[1]) * torso_scale ] set_point(adjusted, KP["Neck"], new_neck) current_neck = new_neck else: # pelvis調整による影響のみ適用 neck_offset = [current_hip_center[0] - orig_hip_center[0], current_hip_center[1] - orig_hip_center[1]] current_neck = [orig_neck[0] + neck_offset[0], orig_neck[1] + neck_offset[1]] set_point(adjusted, KP["Neck"], current_neck) # 3. Shoulder adjustment (from original shoulder, based on neck after torso adjustment) orig_r_shoulder = get_point(original, KP["RShoulder"]) orig_l_shoulder = get_point(original, KP["LShoulder"]) if orig_r_shoulder and orig_l_shoulder and shoulder_scale != 1.0: new_r_shoulder = [ current_neck[0] + (orig_r_shoulder[0] - orig_neck[0]) * shoulder_scale, current_neck[1] + (orig_r_shoulder[1] - orig_neck[1]) * shoulder_scale ] new_l_shoulder = [ current_neck[0] + (orig_l_shoulder[0] - orig_neck[0]) * shoulder_scale, current_neck[1] + (orig_l_shoulder[1] - orig_neck[1]) * shoulder_scale ] set_point(adjusted, KP["RShoulder"], new_r_shoulder) set_point(adjusted, KP["LShoulder"], new_l_shoulder) current_r_shoulder, current_l_shoulder = new_r_shoulder, new_l_shoulder else: # torso/pelvisによる影響のみ適用 neck_movement = [current_neck[0] - orig_neck[0], current_neck[1] - orig_neck[1]] current_r_shoulder = [orig_r_shoulder[0] + neck_movement[0], orig_r_shoulder[1] + neck_movement[1]] if orig_r_shoulder else None current_l_shoulder = [orig_l_shoulder[0] + neck_movement[0], orig_l_shoulder[1] + neck_movement[1]] if orig_l_shoulder else None if current_r_shoulder: set_point(adjusted, KP["RShoulder"], current_r_shoulder) if current_l_shoulder: set_point(adjusted, KP["LShoulder"], current_l_shoulder) # 4. Neck/head adjustment (from original nose/head parts, based on neck after torso adjustment) if neck_scale != 1.0 or head_scale != 1.0: # neck_scaleでnoseの位置を調整 if neck_scale != 1.0: new_nose = [ current_neck[0] + (orig_nose[0] - orig_neck[0]) * neck_scale, current_neck[1] + (orig_nose[1] - orig_neck[1]) * neck_scale ] else: # neck移動の影響のみ適用 neck_movement = [current_neck[0] - orig_neck[0], current_neck[1] - orig_neck[1]] new_nose = [orig_nose[0] + neck_movement[0], orig_nose[1] + neck_movement[1]] set_point(adjusted, KP["Nose"], new_nose) # 頭部の他のパーツも調整 for head_kp in ["REye", "LEye", "REar", "LEar"]: if head_kp in KP: orig_head_point = get_point(original, KP[head_kp]) if orig_head_point: # neck_scaleとhead_scaleを組み合わせて適用 # まずneck_scaleを適用(noseと同様に) if neck_scale != 1.0: neck_scaled = [ current_neck[0] + (orig_head_point[0] - orig_neck[0]) * neck_scale, current_neck[1] + (orig_head_point[1] - orig_neck[1]) * neck_scale ] else: # neck移動の影響のみ neck_movement = [current_neck[0] - orig_neck[0], current_neck[1] - orig_neck[1]] neck_scaled = [orig_head_point[0] + neck_movement[0], orig_head_point[1] + neck_movement[1]] # 次にhead_scaleを適用(new_noseを基準に) if head_scale != 1.0: head_scaled = [ new_nose[0] + (neck_scaled[0] - new_nose[0]) * head_scale, new_nose[1] + (neck_scaled[1] - new_nose[1]) * head_scale ] set_point(adjusted, KP[head_kp], head_scaled) else: set_point(adjusted, KP[head_kp], neck_scaled) else: # neck_scale=1.0 and head_scale=1.0の場合、neck移動の影響のみ適用 neck_movement = [current_neck[0] - orig_neck[0], current_neck[1] - orig_neck[1]] new_nose = [orig_nose[0] + neck_movement[0], orig_nose[1] + neck_movement[1]] set_point(adjusted, KP["Nose"], new_nose) for head_kp in ["REye", "LEye", "REar", "LEar"]: if head_kp in KP: orig_head_point = get_point(original, KP[head_kp]) if orig_head_point: head_moved = [orig_head_point[0] + neck_movement[0], orig_head_point[1] + neck_movement[1]] set_point(adjusted, KP[head_kp], head_moved) # 5. Arm adjustment. arm_scale remains the full-arm base scale; # upper/lower arm scales additionally affect each segment. if current_r_shoulder and current_l_shoulder: for side_prefix, current_shoulder, orig_shoulder in [ ("R", current_r_shoulder, orig_r_shoulder), ("L", current_l_shoulder, orig_l_shoulder) ]: if not orig_shoulder: continue orig_elbow = get_point(original, KP[f"{side_prefix}Elbow"]) orig_wrist = get_point(original, KP[f"{side_prefix}Wrist"]) if orig_elbow: new_elbow = [ current_shoulder[0] + (orig_elbow[0] - orig_shoulder[0]) * arm_scale * upper_arm_scale, current_shoulder[1] + (orig_elbow[1] - orig_shoulder[1]) * arm_scale * upper_arm_scale ] set_point(adjusted, KP[f"{side_prefix}Elbow"], new_elbow) else: new_elbow = None if orig_wrist: if orig_elbow and new_elbow: new_wrist = [ new_elbow[0] + (orig_wrist[0] - orig_elbow[0]) * arm_scale * lower_arm_scale, new_elbow[1] + (orig_wrist[1] - orig_elbow[1]) * arm_scale * lower_arm_scale ] else: new_wrist = [ current_shoulder[0] + (orig_wrist[0] - orig_shoulder[0]) * arm_scale * lower_arm_scale, current_shoulder[1] + (orig_wrist[1] - orig_shoulder[1]) * arm_scale * lower_arm_scale ] set_point(adjusted, KP[f"{side_prefix}Wrist"], new_wrist) # 6. Leg adjustment (from original leg joints, based on position after pelvis adjustment) current_r_hip = get_point(adjusted, KP["RHip"]) current_l_hip = get_point(adjusted, KP["LHip"]) for side_prefix, current_hip, orig_hip in [("R", current_r_hip, orig_r_hip), ("L", current_l_hip, orig_l_hip)]: if not current_hip or not orig_hip: continue orig_knee = get_point(original, KP[f"{side_prefix}Knee"]) orig_ankle = get_point(original, KP[f"{side_prefix}Ankle"]) if orig_knee: new_knee = [ current_hip[0] + (orig_knee[0] - orig_hip[0]) * leg_scale * thigh_scale, current_hip[1] + (orig_knee[1] - orig_hip[1]) * leg_scale * thigh_scale ] set_point(adjusted, KP[f"{side_prefix}Knee"], new_knee) else: new_knee = None if orig_ankle: if orig_knee and new_knee: new_ankle = [ new_knee[0] + (orig_ankle[0] - orig_knee[0]) * leg_scale * lower_leg_scale, new_knee[1] + (orig_ankle[1] - orig_knee[1]) * leg_scale * lower_leg_scale ] else: new_ankle = [ current_hip[0] + (orig_ankle[0] - orig_hip[0]) * leg_scale * lower_leg_scale, current_hip[1] + (orig_ankle[1] - orig_hip[1]) * leg_scale * lower_leg_scale ] set_point(adjusted, KP[f"{side_prefix}Ankle"], new_ankle) # 7. Toe/heel adjustment (ankle-based leg_scale scaling) current_l_ankle = get_point(adjusted, KP["LAnkle"]) current_r_ankle = get_point(adjusted, KP["RAnkle"]) orig_l_ankle = get_point(original, KP["LAnkle"]) orig_r_ankle = get_point(original, KP["RAnkle"]) if len(adjusted) >= 60: # Check if toe/heel keypoints exist # Toe/heel keypoint definitions (based on actual data structure) # 18: RBigToe (right foot toe), 19: LBigToe (left foot toe) TOE_HEEL_MAPPING = { 18: ("RBigToe", current_r_ankle, orig_r_ankle), # Right toe -> right ankle reference 19: ("LBigToe", current_l_ankle, orig_l_ankle), # Left toe -> left ankle reference } for idx, (name, current_ankle, orig_ankle) in TOE_HEEL_MAPPING.items(): keypoint_start = idx * 3 if (keypoint_start + 2 < len(original) and original[keypoint_start + 2] > 0 and current_ankle and orig_ankle): # Only if both ankle and toe exist orig_toe_heel = [original[keypoint_start], original[keypoint_start + 1]] orig_relative = [orig_toe_heel[0] - orig_ankle[0], orig_toe_heel[1] - orig_ankle[1]] scaled_relative = [orig_relative[0] * leg_scale * feet_scale, orig_relative[1] * leg_scale * feet_scale] new_toe_heel = [current_ankle[0] + scaled_relative[0], current_ankle[1] + scaled_relative[1]] # Update toe/heel position adjusted[keypoint_start] = new_toe_heel[0] adjusted[keypoint_start + 1] = new_toe_heel[1] # Keep confidence unchanged return adjusted def _adjust_face_keypoints(self, keypoints, canvas_width, canvas_height, **params): """ Face keypointsの調整処理 顔の各パーツ(目、眉、口、鼻)の調整 """ if not keypoints or len(keypoints) < 210: # 70 face keypoints * 3 = 210 return keypoints # Get parameters eye_distance_scale = params.get("eye_distance_scale", 1.0) eye_height = params.get("eye_height", 0.0) eyebrow_height = params.get("eyebrow_height", 0.0) left_eye_scale = params.get("left_eye_scale", 1.0) right_eye_scale = params.get("right_eye_scale", 1.0) left_eyebrow_scale = params.get("left_eyebrow_scale", 1.0) right_eyebrow_scale = params.get("right_eyebrow_scale", 1.0) mouth_scale = params.get("mouth_scale", 1.0) nose_scale_face = params.get("nose_scale_face", 1.0) face_shape_scale = params.get("face_shape_scale", 1.0) # Do nothing if all are default values if (eye_distance_scale == 1.0 and eye_height == 0.0 and eyebrow_height == 0.0 and left_eye_scale == 1.0 and right_eye_scale == 1.0 and left_eyebrow_scale == 1.0 and right_eyebrow_scale == 1.0 and mouth_scale == 1.0 and nose_scale_face == 1.0 and face_shape_scale == 1.0): return keypoints adjusted = keypoints.copy() # OpenPose face keypoint groups (standard definition) FACE_KP_GROUPS = { "Left_Eye": list(range(42, 48)), # 左目 (42-47) "Right_Eye": list(range(36, 42)), # 右目 (36-41) "Left_Eyebrow": list(range(22, 27)), # 左眉 (22-26) "Right_Eyebrow": list(range(17, 22)), # 右眉 (17-21) "Mouth": list(range(48, 68)), # 口 (48-67) "Nose_Face": list(range(27, 36)), # 鼻 (27-35) "Face_Shape": list(range(0, 17)) # 顔の輪郭 (0-16) } GROUP_SCALES = { "Left_Eye": left_eye_scale, "Right_Eye": right_eye_scale, "Left_Eyebrow": left_eyebrow_scale, "Right_Eyebrow": right_eyebrow_scale, "Mouth": mouth_scale, "Nose_Face": nose_scale_face, "Face_Shape": face_shape_scale } # 顔の中心を計算 (鼻の先端付近を使用) if len(adjusted) >= 93: # nose tip around index 30 * 3 = 90 face_center_x = adjusted[90] # nose tip x face_center_y = adjusted[91] # nose tip y else: face_center_x = canvas_width / 2 face_center_y = canvas_height / 2 # 各顔パーツのスケーリング for group_name, indices in FACE_KP_GROUPS.items(): scale_factor = GROUP_SCALES.get(group_name, 1.0) if scale_factor != 1.0: # Eye groups: use their own face keypoint center for scaling if group_name in ["Left_Eye", "Right_Eye"]: # Calculate current face eye center (centroid of eye keypoints) valid_points = [] for idx in indices: if idx * 3 + 2 < len(adjusted) and adjusted[idx * 3 + 2] > 0: valid_points.append((adjusted[idx * 3], adjusted[idx * 3 + 1])) if valid_points: # Eye center as scaling origin eye_center_x = sum(p[0] for p in valid_points) / len(valid_points) eye_center_y = sum(p[1] for p in valid_points) / len(valid_points) # Scale around the eye's own center for idx in indices: if idx * 3 + 2 < len(adjusted) and adjusted[idx * 3 + 2] > 0: x, y = adjusted[idx * 3], adjusted[idx * 3 + 1] # Scale from eye center rel_x = (x - eye_center_x) * scale_factor rel_y = (y - eye_center_y) * scale_factor # Keep centered at eye center adjusted[idx * 3] = eye_center_x + rel_x adjusted[idx * 3 + 1] = eye_center_y + rel_y else: # Other face parts use nose tip as center for idx in indices: if idx * 3 + 2 < len(adjusted) and adjusted[idx * 3 + 2] > 0: x, y = adjusted[idx * 3], adjusted[idx * 3 + 1] # Scale from face center rel_x = (x - face_center_x) * scale_factor rel_y = (y - face_center_y) * scale_factor adjusted[idx * 3] = face_center_x + rel_x adjusted[idx * 3 + 1] = face_center_y + rel_y # eye_height調整(目の位置を上下に移動) if eye_height != 0.0: for group_name in ["Left_Eye", "Right_Eye"]: for idx in FACE_KP_GROUPS[group_name]: if idx * 3 + 2 < len(adjusted) and adjusted[idx * 3 + 2] > 0: adjusted[idx * 3 + 1] += eye_height # eyebrow_height調整(眉の位置を上下に移動) if eyebrow_height != 0.0: for group_name in ["Left_Eyebrow", "Right_Eyebrow"]: for idx in FACE_KP_GROUPS[group_name]: if idx * 3 + 2 < len(adjusted) and adjusted[idx * 3 + 2] > 0: adjusted[idx * 3 + 1] += eyebrow_height # eye_distance_scale調整(目の間隔を調整) if eye_distance_scale != 1.0: # 左右の目の中心を計算 left_eye_center_x = left_eye_center_y = 0 right_eye_center_x = right_eye_center_y = 0 left_count = right_count = 0 for idx in FACE_KP_GROUPS["Left_Eye"]: if idx * 3 + 2 < len(adjusted) and adjusted[idx * 3 + 2] > 0: left_eye_center_x += adjusted[idx * 3] left_eye_center_y += adjusted[idx * 3 + 1] left_count += 1 for idx in FACE_KP_GROUPS["Right_Eye"]: if idx * 3 + 2 < len(adjusted) and adjusted[idx * 3 + 2] > 0: right_eye_center_x += adjusted[idx * 3] right_eye_center_y += adjusted[idx * 3 + 1] right_count += 1 if left_count > 0 and right_count > 0: left_eye_center_x /= left_count left_eye_center_y /= left_count right_eye_center_x /= right_count right_eye_center_y /= right_count # 目の間の中点 eyes_center_x = (left_eye_center_x + right_eye_center_x) / 2 eyes_center_y = (left_eye_center_y + right_eye_center_y) / 2 # Scale each eye away from center point for group_name, center_x, center_y in [ ("Left_Eye", left_eye_center_x, left_eye_center_y), ("Right_Eye", right_eye_center_x, right_eye_center_y) ]: for idx in FACE_KP_GROUPS[group_name]: if idx * 3 + 2 < len(adjusted) and adjusted[idx * 3 + 2] > 0: # Scale direction from eye center to overall center rel_x = (center_x - eyes_center_x) * eye_distance_scale rel_y = (center_y - eyes_center_y) * eye_distance_scale # 個々のキーポイントも同じ方向に移動 point_rel_x = adjusted[idx * 3] - center_x point_rel_y = adjusted[idx * 3 + 1] - center_y adjusted[idx * 3] = eyes_center_x + rel_x + point_rel_x adjusted[idx * 3 + 1] = eyes_center_y + rel_y + point_rel_y return adjusted def _adjust_hand_keypoints(self, keypoints, canvas_width, canvas_height, wrist_movement=None, **params): """ Hand keypointsの調整処理 手首を基準としたスケーリング + 腕の動きに連動した位置調整 Args: wrist_movement: [dx, dy] 手首の移動量(腕のスケーリングによる) """ if not keypoints or len(keypoints) < 63: # 21 keypoints * 3 = 63 return keypoints hands_scale = params.get("hands_scale", 1.0) # Set default value if wrist_movement is None if wrist_movement is None: wrist_movement = [0.0, 0.0] # Do nothing if hands_scale is 1.0 and wrist_movement is 0 if hands_scale == 1.0 and wrist_movement[0] == 0.0 and wrist_movement[1] == 0.0: return keypoints adjusted = keypoints.copy() # 手首は index 0 (OpenPose hand keypoint standard) wrist_x = adjusted[0] wrist_y = adjusted[1] wrist_conf = adjusted[2] if wrist_conf <= 0: # Move entire hand even when wrist is not detected if wrist_movement[0] != 0.0 or wrist_movement[1] != 0.0: adjusted_len = len(adjusted) for i in range(0, adjusted_len, 3): if i + 2 < adjusted_len and adjusted[i + 2] > 0: adjusted[i] += wrist_movement[0] adjusted[i + 1] += wrist_movement[1] return adjusted # Step 1: 手首を基準として hands_scale でスケーリング if hands_scale != 1.0: adjusted_len = len(adjusted) for i in range(3, adjusted_len, 3): # Start from joints other than wrist if i + 2 < adjusted_len and adjusted[i + 2] > 0: x, y = adjusted[i], adjusted[i + 1] # Scale by relative position from wrist scaled_x = wrist_x + (x - wrist_x) * hands_scale scaled_y = wrist_y + (y - wrist_y) * hands_scale adjusted[i] = scaled_x adjusted[i + 1] = scaled_y # Step 2: 腕の動きに連動して手全体を移動 if wrist_movement[0] != 0.0 or wrist_movement[1] != 0.0: for i in range(0, len(adjusted), 3): # 手首も含めて全てのキーポイントを移動 if i + 2 < len(adjusted) and adjusted[i + 2] > 0: # 信頼度チェック adjusted[i] += wrist_movement[0] adjusted[i + 1] += wrist_movement[1] return adjusted def _apply_global_transform(self, person, canvas_width, canvas_height, overall_scale, rotate_angle, translate_x, translate_y): """ 全体変換(回転・移動)を適用 """ if overall_scale == 1.0 and rotate_angle == 0.0 and translate_x == 0.0 and translate_y == 0.0: return # 変換不要 center_x = canvas_width / 2 center_y = canvas_height / 2 # 回転角度をラジアンに変換 angle_rad = math.radians(rotate_angle) cos_a = math.cos(angle_rad) sin_a = math.sin(angle_rad) # 各キーポイント配列に変換を適用 for keypoint_type in ["pose_keypoints_2d", "face_keypoints_2d", "hand_left_keypoints_2d", "hand_right_keypoints_2d"]: if keypoint_type in person and person[keypoint_type]: keypoints = person[keypoint_type] for i in range(0, len(keypoints), 3): if i + 2 < len(keypoints) and keypoints[i + 2] > 0: x, y = keypoints[i], keypoints[i + 1] # 中心を基準とした座標に変換 rel_x = x - center_x rel_y = y - center_y # overall_scaleを適用 if overall_scale != 1.0: rel_x *= overall_scale rel_y *= overall_scale # 回転変換 if rotate_angle != 0.0: new_x = rel_x * cos_a - rel_y * sin_a new_y = rel_x * sin_a + rel_y * cos_a else: new_x = rel_x new_y = rel_y # 移動変換と中心座標の復元 keypoints[i] = center_x + new_x + translate_x keypoints[i + 1] = center_y + new_y + translate_y # ノードクラスのエイリアス(ComfyUIでの参照用) NODE_CLASS_MAPPINGS = { "ProportionChangerParams": ProportionChangerParams, } NODE_DISPLAY_NAME_MAPPINGS = { "ProportionChangerParams": "ProportionChanger Params" }