diff --git a/README.md b/README.md index 1207df6..536f645 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Additionally, the Openpose Editor node from [toyxyz/ComfyUI-ultimate-openpose-ed - **ProportionChanger Pose Detector**: Detects KeyPoints from images - **ProportionChanger Reference**: Transforms proportions to reference poses - **ProportionChanger Pose Render**: Converts KeyPoints to images -- **ProportionChanger Params**: Adjusts parameters for individual KeyPoint parts +- **ProportionChanger Params**: Adjusts parameters for individual KeyPoint parts, including separate upper/lower arm, thigh/lower-leg, and feet scaling - **ProportionChanger Interpolator**: Interpolates KeyPoint videos with in-betweening - **PoseData to pose_keypoint**: Converts WanAnimate `POSEDATA` into `POSE_KEYPOINT` - **pose_keypoint resize**: Resizes `POSE_KEYPOINT` to a target size (pads then scales to avoid stretching when aspect differs) diff --git a/README_ja.md b/README_ja.md index 755477c..f61795a 100644 --- a/README_ja.md +++ b/README_ja.md @@ -14,7 +14,7 @@ - **ProportionChanger Pose Detector**: 画像からKeyPointを検出する - **ProportionChanger Reference**: 参照用ポーズにプロポーションを変換する - **ProportionChanger Pose Render**: KeyPointを画像に変換する -- **ProportionChanger Params**: KeyPointの各パーツのパラメータを調整する +- **ProportionChanger Params**: KeyPointの各パーツのパラメータを調整する(上腕・下腕・太腿・下腿・足先の個別スケールを含む) - **ProportionChanger Interpolator**: KeyPoint動画を中割りして補間する - **PoseData to pose_keypoint**: WanAnimateの`POSEDATA`を`POSE_KEYPOINT`に変換する - **pose_keypoint resize**: `POSE_KEYPOINT`を指定サイズにリサイズ(アスペクト比が違う場合はパディング→スケールで歪みを防ぐ) diff --git a/proportion_changer/params_node.py b/proportion_changer/params_node.py index 1e2cd06..1334786 100644 --- a/proportion_changer/params_node.py +++ b/proportion_changer/params_node.py @@ -27,9 +27,14 @@ class ProportionChangerParams: "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}), @@ -76,7 +81,9 @@ class ProportionChangerParams: 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, - leg_scale=1.0, hands_scale=1.0, overall_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データを各種パラメータで調整 @@ -106,7 +113,12 @@ class ProportionChangerParams: "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, @@ -144,7 +156,9 @@ class ProportionChangerParams: "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, - "leg_scale": 1.0, "hands_scale": 1.0, "overall_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 } @@ -297,7 +311,12 @@ class ProportionChangerParams: 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"]) @@ -422,66 +441,71 @@ class ProportionChangerParams: 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 (from original arm joints, based on position after shoulder adjustment) - if arm_scale != 1.0 and current_r_shoulder and current_l_shoulder: + # 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) ]: - for joint in ["Elbow", "Wrist"]: - kp_name = f"{side_prefix}{joint}" - if kp_name in KP: - orig_joint = get_point(original, KP[kp_name]) - if orig_joint and orig_shoulder: - new_joint = [ - current_shoulder[0] + (orig_joint[0] - orig_shoulder[0]) * arm_scale, - current_shoulder[1] + (orig_joint[1] - orig_shoulder[1]) * arm_scale - ] - set_point(adjusted, KP[kp_name], new_joint) - elif current_r_shoulder and current_l_shoulder: - # arm_scale=1.0の場合、shoulder移動の影響のみ適用 - for side_prefix, current_shoulder, orig_shoulder in [ - ("R", current_r_shoulder, orig_r_shoulder), - ("L", current_l_shoulder, orig_l_shoulder) - ]: - if orig_shoulder: - shoulder_movement = [current_shoulder[0] - orig_shoulder[0], current_shoulder[1] - orig_shoulder[1]] - for joint in ["Elbow", "Wrist"]: - kp_name = f"{side_prefix}{joint}" - if kp_name in KP: - orig_joint = get_point(original, KP[kp_name]) - if orig_joint: - new_joint = [orig_joint[0] + shoulder_movement[0], orig_joint[1] + shoulder_movement[1]] - set_point(adjusted, KP[kp_name], new_joint) + 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"]) - if leg_scale != 1.0: - for side_prefix, current_hip, orig_hip in [("R", current_r_hip, orig_r_hip), ("L", current_l_hip, orig_l_hip)]: - for joint in ["Knee", "Ankle"]: - kp_name = f"{side_prefix}{joint}" - if kp_name in KP: - orig_joint = get_point(original, KP[kp_name]) - if orig_joint and orig_hip: - new_joint = [ - current_hip[0] + (orig_joint[0] - orig_hip[0]) * leg_scale, - current_hip[1] + (orig_joint[1] - orig_hip[1]) * leg_scale - ] - set_point(adjusted, KP[kp_name], new_joint) - else: - # leg_scale=1.0の場合、pelvis移動の影響のみ適用 - for side_prefix, current_hip, orig_hip in [("R", current_r_hip, orig_r_hip), ("L", current_l_hip, orig_l_hip)]: - if orig_hip: - hip_movement = [current_hip[0] - orig_hip[0], current_hip[1] - orig_hip[1]] - for joint in ["Knee", "Ankle"]: - kp_name = f"{side_prefix}{joint}" - if kp_name in KP: - orig_joint = get_point(original, KP[kp_name]) - if orig_joint: - new_joint = [orig_joint[0] + hip_movement[0], orig_joint[1] + hip_movement[1]] - set_point(adjusted, KP[kp_name], new_joint) + 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"]) @@ -503,17 +527,9 @@ class ProportionChangerParams: current_ankle and orig_ankle): # Only if both ankle and toe exist orig_toe_heel = [original[keypoint_start], original[keypoint_start + 1]] - confidence = original[keypoint_start + 2] - - if leg_scale != 1.0: - # Maintain direction vector from ankle to toe, scale distance only - orig_relative = [orig_toe_heel[0] - orig_ankle[0], orig_toe_heel[1] - orig_ankle[1]] - scaled_relative = [orig_relative[0] * leg_scale, orig_relative[1] * leg_scale] - new_toe_heel = [current_ankle[0] + scaled_relative[0], current_ankle[1] + scaled_relative[1]] - else: - # leg_scale=1.0: follow ankle movement - ankle_movement = [current_ankle[0] - orig_ankle[0], current_ankle[1] - orig_ankle[1]] - new_toe_heel = [orig_toe_heel[0] + ankle_movement[0], orig_toe_heel[1] + ankle_movement[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] diff --git a/pyproject.toml b/pyproject.toml index 4cdb1b4..643a44f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "ComfyUI-ProportionChanger" description = "Advanced pose-based body proportion changer for ComfyUI" -version = "1.1.0" +version = "1.2.0" license = {file = "LICENSE"} dependencies = ["torch", "numpy", "opencv-python", "tqdm"]