Add pose_keypoint resize node

This commit is contained in:
grmchn
2025-12-13 15:49:35 +09:00
parent 3f3fcf3a28
commit 82a6e8a4b3
5 changed files with 194 additions and 3 deletions
+2 -1
View File
@@ -17,6 +17,7 @@ Additionally, the Openpose Editor node from [toyxyz/ComfyUI-ultimate-openpose-ed
- **ProportionChanger Params**: Adjusts parameters for individual KeyPoint parts
- **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)
- **pose_keypoint input**: Converts JSON text to KeyPoints
- **pose_keypoint preview**: Converts KeyPoints to JSON
@@ -60,7 +61,7 @@ Please refer to example_workflows.
### Common Issues
1. **Model Loading Errors**: Models should be automatically downloaded from HuggingFace. Please ensure DWPose models are in the correct directory.
2. **Incorrect body proportions after transformation with reference image**: Make sure the reference image size matches the pose image size. Fine-tune individual body parts using the "ProportionChanger Params" node.
2. **Incorrect body proportions after transformation with reference image**: The `pose_keypoint` and `reference_pose_keypoint` aspect ratios (canvas width/height) may not match. Make the reference image size match the pose image size, or use **pose_keypoint resize** to align `pose_keypoint` to the same `width`/`height`. Fine-tune individual body parts using the "ProportionChanger Params" node.
3. **Nothing displays after transformation with reference image**: The reference image's pose estimation by DWPose has failed. Use OpenposeEditor or similar tools to input parameter values manually.
## Attribution and Credits
+2 -1
View File
@@ -17,6 +17,7 @@
- **ProportionChanger Params**: KeyPointの各パーツのパラメータを調整する
- **ProportionChanger Interpolator**: KeyPoint動画を中割りして補間する
- **PoseData to pose_keypoint**: WanAnimateの`POSEDATA`を`POSE_KEYPOINT`に変換する
- **pose_keypoint resize**: `POSE_KEYPOINT`を指定サイズにリサイズ(アスペクト比が違う場合はパディング→スケールで歪みを防ぐ)
- **pose_keypoint input**: JSONテキストからKeyPointに変換する
- **pose_keypoint preview**: KeyPointからJSONに変換する
@@ -61,7 +62,7 @@ example_workflowsを参照してください。
### よくある問題
1. **モデル読み込みエラー**: HuggingFaceから自動ダウンロードされるはずです。DWPoseモデルが正しいディレクトリにあることを確認してください。
2. **リファレンス画像ありでプロポーション変換した後の体型がおかしい**:リファレンス画像のサイズをポーズ画像側と同じにしてください。体の各パーツの微調整は、「ProportionChanger Params」ノードで行ってください。
2. **リファレンス画像ありでプロポーション変換した後の体型がおかしい**: `pose_keypoint`と`reference_pose_keypoint`の縦横比(canvas幅/高さ)が一致していない可能性があります。リファレンス画像のサイズをポーズ画像側と同じにするか、**pose_keypoint resize**ノードで`pose_keypoint`側をリファレンスと同じ`width`/`height`に揃えてください。体の各パーツの微調整は「ProportionChanger Params」ノードで行ってください。
3. **リファレンス画像ありでプロポーション変換した後に何も表示されない**:リファレンス画像のDWPoseによる姿勢推定が失敗しています。OpenposeEditorなどを利用してパラメータの数値を入力してください。
## 帰属とクレジット
+3
View File
@@ -14,6 +14,7 @@ from .proportion_changer import (
ProportionChangerParams,
ProportionChangerInterpolator,
PoseDataToPoseKeypoint,
PoseKeypointResize,
# TODO: Re-enable after fixing Kalman filter issues
# ProportionChangerKeypointDenoiser,
# ProportionChangerKeypointDenoiserAdvanced
@@ -204,6 +205,7 @@ NODE_CLASS_MAPPINGS = {
# Utility nodes
"PoseDataToPoseKeypoint": PoseDataToPoseKeypoint,
"PoseKeypointResize": PoseKeypointResize,
"PoseJSONToPoseKeypoint": PoseJSONToPoseKeypoint,
"PoseKeypointPreview": PoseKeypointPreview,
}
@@ -222,6 +224,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
# Utility nodes
"PoseDataToPoseKeypoint": "PoseData to pose_keypoint",
"PoseKeypointResize": "pose_keypoint resize",
"PoseJSONToPoseKeypoint": "pose_keypoint input",
"PoseKeypointPreview": "pose_keypoint preview",
}
+3 -1
View File
@@ -30,7 +30,8 @@ from .interpolation_node import (
# Converter nodes
from .converter_nodes import (
PoseDataToPoseKeypoint
PoseDataToPoseKeypoint,
PoseKeypointResize,
)
# Import keypoint denoiser node classes
@@ -57,6 +58,7 @@ __all__ = [
# Converter nodes
"PoseDataToPoseKeypoint",
"PoseKeypointResize",
# KeyPoint Denoiser nodes
"ProportionChangerKeypointDenoiser",
+184
View File
@@ -4,6 +4,7 @@ Converter nodes for bridging WanAnimate POSEDATA into POSE_KEYPOINT format.
from __future__ import annotations
import copy
import numpy as np
try: # Standard package import (ComfyUI runtime)
@@ -100,6 +101,81 @@ def _extract_keypoints(meta, coord_name, conf_name, target_points, width, height
return points
def _looks_normalized_xy_triplets(triplets: list[float]) -> bool:
"""
Heuristic: treat POSE_KEYPOINT x/y as normalized if values are in 0..1-ish scale.
"""
if not triplets:
return True
max_abs = 0.0
# sample all points with confidence > 0
for i in range(0, len(triplets) - 2, 3):
conf = triplets[i + 2]
if conf is None or conf <= 0:
continue
x = triplets[i]
y = triplets[i + 1]
if not np.isfinite(x) or not np.isfinite(y):
continue
max_abs = max(max_abs, float(abs(x)), float(abs(y)))
return max_abs <= 2.0
def _resize_pose_triplets_with_pad(
triplets: list[float],
*,
src_w: float,
src_h: float,
dst_w: float,
dst_h: float,
pad_x: float,
pad_y: float,
scale: float,
) -> list[float]:
if not triplets:
return triplets
normalized = _looks_normalized_xy_triplets(triplets)
out = list(triplets)
for i in range(0, len(out) - 2, 3):
x = out[i]
y = out[i + 1]
conf = out[i + 2]
if conf is None or conf <= 0:
out[i] = 0.0
out[i + 1] = 0.0
out[i + 2] = 0.0 if conf is None else conf
continue
if not np.isfinite(x) or not np.isfinite(y) or not np.isfinite(conf):
out[i] = 0.0
out[i + 1] = 0.0
out[i + 2] = 0.0
continue
x = float(x)
y = float(y)
conf = float(conf)
if normalized:
x *= float(src_w)
y *= float(src_h)
# pad to match target aspect, then scale to destination
x = (x + pad_x) * scale
y = (y + pad_y) * scale
out[i] = x
out[i + 1] = y
out[i + 2] = conf
return out
class PoseDataToPoseKeypoint:
"""
Convert WanAnimatePreprocess POSEDATA into POSE_KEYPOINT format used by ProportionChanger.
@@ -180,3 +256,111 @@ class PoseDataToPoseKeypoint:
)
return (frames,)
class PoseKeypointResize:
"""
Resize POSE_KEYPOINT to desired width/height without stretching:
- If aspect ratio matches: scale directly.
- If aspect ratio differs: pad the shorter side (centered) then scale.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pose_keypoint": ("POSE_KEYPOINT", {"tooltip": "Input POSE_KEYPOINT to resize."}),
"width": ("INT", {"default": 512, "min": 1, "max": 8192, "step": 1}),
"height": ("INT", {"default": 768, "min": 1, "max": 8192, "step": 1}),
}
}
RETURN_TYPES = ("POSE_KEYPOINT",)
RETURN_NAMES = ("pose_keypoint",)
FUNCTION = "resize"
CATEGORY = "ProportionChanger"
def resize(self, pose_keypoint, width: int, height: int):
if width <= 0 or height <= 0:
raise ValueError("width and height must be positive integers")
if not pose_keypoint:
return (
[
{
"version": "1.0",
"people": [],
"canvas_width": width,
"canvas_height": height,
}
],
)
dst_w = float(width)
dst_h = float(height)
dst_ratio = dst_w / dst_h
frames_out = []
for frame in pose_keypoint:
frame_out = copy.deepcopy(frame) if isinstance(frame, dict) else {}
src_w = float(frame_out.get("canvas_width", width) or width)
src_h = float(frame_out.get("canvas_height", height) or height)
if src_w <= 0 or src_h <= 0:
src_w = float(width)
src_h = float(height)
src_ratio = src_w / src_h
if abs(src_ratio - dst_ratio) < 1e-8:
pad_x = 0.0
pad_y = 0.0
padded_w = src_w
padded_h = src_h
elif src_ratio < dst_ratio:
# pad width (left/right)
padded_h = src_h
padded_w = src_h * dst_ratio
pad_x = (padded_w - src_w) / 2.0
pad_y = 0.0
else:
# pad height (top/bottom)
padded_w = src_w
padded_h = src_w / dst_ratio
pad_x = 0.0
pad_y = (padded_h - src_h) / 2.0
scale = dst_w / padded_w if padded_w > 0 else 1.0
people = frame_out.get("people", [])
if not isinstance(people, list):
people = []
for person in people:
if not isinstance(person, dict):
continue
for key in (
"pose_keypoints_2d",
"face_keypoints_2d",
"hand_left_keypoints_2d",
"hand_right_keypoints_2d",
):
triplets = person.get(key)
if not isinstance(triplets, list):
continue
person[key] = _resize_pose_triplets_with_pad(
triplets,
src_w=src_w,
src_h=src_h,
dst_w=dst_w,
dst_h=dst_h,
pad_x=pad_x,
pad_y=pad_y,
scale=scale,
)
frame_out["canvas_width"] = width
frame_out["canvas_height"] = height
frames_out.append(frame_out)
return (frames_out,)