Add pose_keypoint resize node
This commit is contained in:
@@ -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
@@ -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などを利用してパラメータの数値を入力してください。
|
||||
|
||||
## 帰属とクレジット
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,)
|
||||
|
||||
Reference in New Issue
Block a user