821 lines
41 KiB
Python
821 lines
41 KiB
Python
"""
|
||
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"
|
||
}
|