Add PoseData to pose_keypoint converter node
This commit is contained in:
@@ -16,6 +16,7 @@ Additionally, the Openpose Editor node from [toyxyz/ComfyUI-ultimate-openpose-ed
|
||||
- **ProportionChanger DWPose Render**: Converts KeyPoints to images
|
||||
- **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 input**: Converts JSON text to KeyPoints
|
||||
- **pose_keypoint preview**: Converts KeyPoints to JSON
|
||||
|
||||
@@ -49,6 +50,12 @@ Please refer to example_workflows.
|
||||
|
||||

|
||||
|
||||
### Converting WanAnimate POSEDATA
|
||||
- Use WanAnimatePreprocess "Pose and Face Detection" to produce `POSEDATA`
|
||||
- Feed `POSEDATA`, `width`, and `height` into **PoseData to pose_keypoint**
|
||||
- Connect the resulting `pose_keypoint` to ProportionChanger Reference / Render nodes
|
||||
- Example workflow: `example_workflows/proportion_changer_pose_data_to_pose_keypoint.json`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
@@ -76,4 +83,4 @@ When combining code from Apache 2.0 and GPL 3.0 licensed projects, the resulting
|
||||
- ProportionChanger Params functionality: Copyright by toyxyz
|
||||
- Modifications and integration: This project's contributors
|
||||
|
||||
See the [LICENSE](LICENSE) file for the full GPL 3.0 license terms.
|
||||
See the [LICENSE](LICENSE) file for the full GPL 3.0 license terms.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
- **ProportionChanger DWPose Render**: KeyPointを画像に変換する
|
||||
- **ProportionChanger Params**: KeyPointの各パーツのパラメータを調整する
|
||||
- **ProportionChanger Interpolator**: KeyPoint動画を中割りして補間する
|
||||
- **PoseData to pose_keypoint**: WanAnimateの`POSEDATA`を`POSE_KEYPOINT`に変換する
|
||||
- **pose_keypoint input**: JSONテキストからKeyPointに変換する
|
||||
- **pose_keypoint preview**: KeyPointからJSONに変換する
|
||||
|
||||
@@ -49,6 +50,12 @@ example_workflowsを参照してください。
|
||||
|
||||

|
||||
|
||||
### WanAnimate POSEDATAの変換
|
||||
- WanAnimatePreprocessの「Pose and Face Detection」で`POSEDATA`を取得
|
||||
- **PoseData to pose_keypoint**ノードに`POSEDATA`とキャンバスの`width`/`height`を入力
|
||||
- 出力された`pose_keypoint`をProportionChanger Reference / Renderノードに接続
|
||||
- サンプルワークフロー: `example_workflows/proportion_changer_pose_data_to_pose_keypoint.json`
|
||||
|
||||
|
||||
## トラブルシューティング
|
||||
|
||||
|
||||
@@ -0,0 +1,725 @@
|
||||
{
|
||||
"id": "deb90f3a-9e14-4806-8e90-98c0eea4d98e",
|
||||
"revision": 0,
|
||||
"last_node_id": 12,
|
||||
"last_link_id": 18,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "DrawViTPose",
|
||||
"pos": [
|
||||
7440.435138869547,
|
||||
6129.696683823703
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
178
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pose_data",
|
||||
"type": "POSEDATA",
|
||||
"link": 9
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"link": 10
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"link": 11
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "pose_images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "ComfyUI-WanAnimatePreprocess",
|
||||
"ver": "2fcbcae7eec637fdc712fdec18e6266feb8ba3a7",
|
||||
"Node name for S&R": "DrawViTPose"
|
||||
},
|
||||
"widgets_values": [
|
||||
480,
|
||||
832,
|
||||
16,
|
||||
-1,
|
||||
-1,
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "GetImageSizeAndCount",
|
||||
"pos": [
|
||||
6621.080613343585,
|
||||
6467.158135374485
|
||||
],
|
||||
"size": [
|
||||
196.2994140625,
|
||||
86
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 16
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
13
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "512 width",
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
7,
|
||||
10,
|
||||
14
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "920 height",
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
8,
|
||||
11,
|
||||
15
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "16 count",
|
||||
"name": "count",
|
||||
"type": "INT",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-kjnodes",
|
||||
"ver": "f37df472dfb325f1f72c90e2df56e72f40f850f0",
|
||||
"Node name for S&R": "GetImageSizeAndCount"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "VHS_LoadVideo",
|
||||
"pos": [
|
||||
6273.467729604059,
|
||||
6468.808175961504
|
||||
],
|
||||
"size": [
|
||||
296.0120268545073,
|
||||
819.5215916898854
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "meta_batch",
|
||||
"shape": 7,
|
||||
"type": "VHS_BatchManager",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"shape": 7,
|
||||
"type": "VAE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
16
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "frame_count",
|
||||
"type": "INT",
|
||||
"links": []
|
||||
},
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"links": []
|
||||
},
|
||||
{
|
||||
"name": "video_info",
|
||||
"type": "VHS_VIDEOINFO",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-videohelpersuite",
|
||||
"ver": "8550981384301e9bc5bfea83e5c2c75258102593",
|
||||
"Node name for S&R": "VHS_LoadVideo"
|
||||
},
|
||||
"widgets_values": {
|
||||
"video": "yoshi1_sample_girl.mp4",
|
||||
"force_rate": 16,
|
||||
"custom_width": 512,
|
||||
"custom_height": 920,
|
||||
"frame_load_cap": 16,
|
||||
"skip_first_frames": 1,
|
||||
"select_every_nth": 1,
|
||||
"format": "AnimateDiff",
|
||||
"videopreview": {
|
||||
"hidden": false,
|
||||
"paused": false,
|
||||
"params": {
|
||||
"filename": "yoshi1_sample_girl.mp4",
|
||||
"type": "input",
|
||||
"format": "video/mp4",
|
||||
"force_rate": 16,
|
||||
"custom_width": 512,
|
||||
"custom_height": 920,
|
||||
"frame_load_cap": 16,
|
||||
"skip_first_frames": 1,
|
||||
"select_every_nth": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "OnnxDetectionModelLoader",
|
||||
"pos": [
|
||||
6276.524633017252,
|
||||
6134.099122226672
|
||||
],
|
||||
"size": [
|
||||
430.1680197224765,
|
||||
106
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "POSEMODEL",
|
||||
"links": [
|
||||
12
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "ComfyUI-WanAnimatePreprocess",
|
||||
"ver": "2fcbcae7eec637fdc712fdec18e6266feb8ba3a7",
|
||||
"Node name for S&R": "OnnxDetectionModelLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"onnx/vitpose_h_wholebody_model.onnx",
|
||||
"onnx/yolov10m.onnx",
|
||||
"CUDAExecutionProvider"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "PoseAndFaceDetection",
|
||||
"pos": [
|
||||
6902.806960621858,
|
||||
6134.325632658536
|
||||
],
|
||||
"size": [
|
||||
313.125,
|
||||
162
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "POSEMODEL",
|
||||
"link": 12
|
||||
},
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 13
|
||||
},
|
||||
{
|
||||
"name": "retarget_image",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"link": 14
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"link": 15
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "pose_data",
|
||||
"type": "POSEDATA",
|
||||
"links": [
|
||||
6,
|
||||
9
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "face_images",
|
||||
"type": "IMAGE",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "key_frame_body_points",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "bboxes",
|
||||
"type": "BBOX",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "face_bboxes",
|
||||
"type": "BBOX,",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "ComfyUI-WanAnimatePreprocess",
|
||||
"ver": "2fcbcae7eec637fdc712fdec18e6266feb8ba3a7",
|
||||
"Node name for S&R": "PoseAndFaceDetection"
|
||||
},
|
||||
"widgets_values": [
|
||||
480,
|
||||
832
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "PoseDataToPoseKeypoint",
|
||||
"pos": [
|
||||
6998.0968322251765,
|
||||
6726.796517643764
|
||||
],
|
||||
"size": [
|
||||
280.7720703125,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pose_data",
|
||||
"type": "POSEDATA",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"link": 7
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "pose_keypoint",
|
||||
"type": "POSE_KEYPOINT",
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "ComfyUI-ProportionChanger",
|
||||
"ver": "16cda84ffa029ac60fc2da05f727fafcdf90fcd6",
|
||||
"Node name for S&R": "PoseDataToPoseKeypoint"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
768
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "VHS_VideoCombine",
|
||||
"pos": [
|
||||
7773.121842444329,
|
||||
6122.950426243646
|
||||
],
|
||||
"size": [
|
||||
270.25917466462124,
|
||||
797.6844544754913
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
},
|
||||
{
|
||||
"name": "audio",
|
||||
"shape": 7,
|
||||
"type": "AUDIO",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "meta_batch",
|
||||
"shape": 7,
|
||||
"type": "VHS_BatchManager",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"shape": 7,
|
||||
"type": "VAE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "Filenames",
|
||||
"type": "VHS_FILENAMES",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-videohelpersuite",
|
||||
"ver": "0a75c7958fe320efcb052f1d9f8451fd20c730a8",
|
||||
"Node name for S&R": "VHS_VideoCombine"
|
||||
},
|
||||
"widgets_values": {
|
||||
"frame_rate": 16,
|
||||
"loop_count": 0,
|
||||
"filename_prefix": "ProportionChanger_Dev",
|
||||
"format": "video/h264-mp4",
|
||||
"pix_fmt": "yuv420p",
|
||||
"crf": 19,
|
||||
"save_metadata": true,
|
||||
"trim_to_audio": false,
|
||||
"pingpong": false,
|
||||
"save_output": true,
|
||||
"videopreview": {
|
||||
"hidden": false,
|
||||
"paused": false,
|
||||
"params": {
|
||||
"filename": "ProportionChanger_Dev_00011.mp4",
|
||||
"subfolder": "",
|
||||
"type": "output",
|
||||
"format": "video/h264-mp4",
|
||||
"frame_rate": 16,
|
||||
"workflow": "ProportionChanger_Dev_00011.png",
|
||||
"fullpath": "/home/shins/works/ComfyUI/output/ProportionChanger_Dev_00011.mp4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "VHS_VideoCombine",
|
||||
"pos": [
|
||||
7776.91575807864,
|
||||
6987.710777633694
|
||||
],
|
||||
"size": [
|
||||
270.25917466462124,
|
||||
797.6844544754913
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 18
|
||||
},
|
||||
{
|
||||
"name": "audio",
|
||||
"shape": 7,
|
||||
"type": "AUDIO",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "meta_batch",
|
||||
"shape": 7,
|
||||
"type": "VHS_BatchManager",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"shape": 7,
|
||||
"type": "VAE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "Filenames",
|
||||
"type": "VHS_FILENAMES",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-videohelpersuite",
|
||||
"ver": "0a75c7958fe320efcb052f1d9f8451fd20c730a8",
|
||||
"Node name for S&R": "VHS_VideoCombine"
|
||||
},
|
||||
"widgets_values": {
|
||||
"frame_rate": 16,
|
||||
"loop_count": 0,
|
||||
"filename_prefix": "ProportionChanger",
|
||||
"format": "video/h264-mp4",
|
||||
"pix_fmt": "yuv420p",
|
||||
"crf": 19,
|
||||
"save_metadata": true,
|
||||
"trim_to_audio": false,
|
||||
"pingpong": false,
|
||||
"save_output": true,
|
||||
"videopreview": {
|
||||
"hidden": false,
|
||||
"paused": false,
|
||||
"params": {
|
||||
"filename": "ProportionChanger_00001.mp4",
|
||||
"subfolder": "",
|
||||
"type": "output",
|
||||
"format": "video/h264-mp4",
|
||||
"frame_rate": 16,
|
||||
"workflow": "ProportionChanger_00001.png",
|
||||
"fullpath": "/home/shins/works/ComfyUI/output/ProportionChanger_00001.mp4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "ProportionChangerDWPoseRender",
|
||||
"pos": [
|
||||
7349.095248798733,
|
||||
6869.300113529442
|
||||
],
|
||||
"size": [
|
||||
337.8521484375,
|
||||
226
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pose_keypoint",
|
||||
"type": "POSE_KEYPOINT",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
18
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "ComfyUI-ProportionChanger",
|
||||
"ver": "16cda84ffa029ac60fc2da05f727fafcdf90fcd6",
|
||||
"Node name for S&R": "ProportionChangerDWPoseRender"
|
||||
},
|
||||
"widgets_values": [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
-1,
|
||||
4,
|
||||
3,
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
3,
|
||||
6,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"POSE_KEYPOINT"
|
||||
],
|
||||
[
|
||||
5,
|
||||
7,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
8,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"POSEDATA"
|
||||
],
|
||||
[
|
||||
7,
|
||||
9,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
8,
|
||||
9,
|
||||
2,
|
||||
6,
|
||||
2,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
9,
|
||||
8,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"POSEDATA"
|
||||
],
|
||||
[
|
||||
10,
|
||||
9,
|
||||
1,
|
||||
7,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
11,
|
||||
9,
|
||||
2,
|
||||
7,
|
||||
2,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
12,
|
||||
11,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"POSEMODEL"
|
||||
],
|
||||
[
|
||||
13,
|
||||
9,
|
||||
0,
|
||||
8,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
14,
|
||||
9,
|
||||
1,
|
||||
8,
|
||||
3,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
15,
|
||||
9,
|
||||
2,
|
||||
8,
|
||||
4,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
16,
|
||||
10,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
18,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.5559917313492256,
|
||||
"offset": [
|
||||
-6097.1435759818805,
|
||||
-6089.885949666708
|
||||
]
|
||||
},
|
||||
"workflowRendererVersion": "LG",
|
||||
"frontendVersion": "1.35.4",
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -13,6 +13,7 @@ from .proportion_changer import (
|
||||
ProportionChangerDWPoseRender,
|
||||
ProportionChangerParams,
|
||||
ProportionChangerInterpolator,
|
||||
PoseDataToPoseKeypoint,
|
||||
# TODO: Re-enable after fixing Kalman filter issues
|
||||
# ProportionChangerKeypointDenoiser,
|
||||
# ProportionChangerKeypointDenoiserAdvanced
|
||||
@@ -202,6 +203,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
# "ProportionChangerKeypointDenoiserAdvanced": ProportionChangerKeypointDenoiserAdvanced,
|
||||
|
||||
# Utility nodes
|
||||
"PoseDataToPoseKeypoint": PoseDataToPoseKeypoint,
|
||||
"PoseJSONToPoseKeypoint": PoseJSONToPoseKeypoint,
|
||||
"PoseKeypointPreview": PoseKeypointPreview,
|
||||
}
|
||||
@@ -219,6 +221,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
# "ProportionChangerKeypointDenoiserAdvanced": "ProportionChanger Denoiser (Advanced)",
|
||||
|
||||
# Utility nodes
|
||||
"PoseDataToPoseKeypoint": "PoseData to pose_keypoint",
|
||||
"PoseJSONToPoseKeypoint": "pose_keypoint input",
|
||||
"PoseKeypointPreview": "pose_keypoint preview",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Plan: PoseData to PoseKeypoint converter node
|
||||
|
||||
## Goal
|
||||
Add a ComfyUI node (e.g. `PoseDataToPoseKeypoint`) inside ProportionChanger that converts WanAnimatePreprocess `POSEDATA` into ProportionChanger/UniAnimate-style `POSE_KEYPOINT` so downstream nodes can reuse DWPose-format keypoints without needing WanAnimate code.
|
||||
|
||||
## Expected IO
|
||||
- Inputs (match Draw ViT Pose for familiarity):
|
||||
- `pose_data` (POSEDATA) – produced by Pose and Face Detection.
|
||||
- `width` (INT) – canvas width to scale normalized coords to pixels.
|
||||
- `height` (INT) – canvas height.
|
||||
- Output:
|
||||
- `pose_keypoint` (POSE_KEYPOINT) – list of frame dicts following UniAnimate/OpenPose style used in `ProportionChangerDWPoseDetector`.
|
||||
|
||||
## Source formats to bridge
|
||||
- `pose_data["pose_metas"]`: list of `AAPoseMeta` or humanapi meta clones; body/hand/face keypoints are **normalized 0–1** and packed as numpy arrays with confidences.
|
||||
- ProportionChanger expects per-frame dict:
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"people": [
|
||||
{
|
||||
"pose_keypoints_2d": [x,y,conf]*25,
|
||||
"face_keypoints_2d": [x,y,conf]*70,
|
||||
"hand_left_keypoints_2d": [x,y,conf]*21,
|
||||
"hand_right_keypoints_2d": [x,y,conf]*21
|
||||
}
|
||||
],
|
||||
"canvas_width": W,
|
||||
"canvas_height": H
|
||||
}
|
||||
```
|
||||
- Body point count mismatch: AAPoseMeta body has 20 keypoints (19-22 are averaged joints, no toes). DWPose/UniAnimate uses 25 (includes toes idx 19–24). Plan to map available 20 into first 20 slots and pad toes (19–24) with zeros/conf=0.
|
||||
|
||||
## Conversion rules
|
||||
1) For each `meta` in `pose_data["pose_metas"]`:
|
||||
- Scale all keypoints by `width`/`height` to pixels.
|
||||
- Body: take `meta.kps_body` (shape 20x2) + `meta.kps_body_p` to build 25*3 list; pad missing 5 joints (toe L/R and ankle variants) with 0s and conf 0.
|
||||
- Face: `meta.kps_face` (69 pts) -> first 69; pad to 70 with zeros; confidences from `kps_face_p` if available else 1.
|
||||
- Hands: `meta.kps_lhand`, `kps_rhand` (21 pts each); pad if missing; use `kps_*_p` for conf else 1.
|
||||
- Build `people` entry; if no valid body points (all conf 0), emit empty `people` list.
|
||||
2) Append `canvas_width/height` from input args.
|
||||
3) Return list ordered to match frames.
|
||||
|
||||
## Node design
|
||||
- New file: add class `PoseDataToPoseKeypoint` under `proportion_changer` package (e.g. `converter_nodes.py` or existing util file) and expose via `NODE_CLASS_MAPPINGS`/`NODE_DISPLAY_NAME_MAPPINGS` in `nodes.py`.
|
||||
- Category: `ProportionChanger`.
|
||||
- INPUT_TYPES same three fields; RETURN_TYPES (`POSE_KEYPOINT`,) RETURN_NAMES (`pose_keypoint`,).
|
||||
- Keep tensor-free; operates on Python lists/numpy.
|
||||
- Minimal deps: reuse numpy if already available; avoid torch.
|
||||
|
||||
## Edge cases to handle
|
||||
- Missing `kps_face` or hands: pad with zeros/conf=0.
|
||||
- NaN/None in keypoints: coerce to 0, conf=0.
|
||||
- Empty `pose_metas`: return `[ {version:"1.0", people:[], canvas_width:W, canvas_height:H} ]`.
|
||||
- Width/height ≤0: fail fast with ValueError.
|
||||
|
||||
## Tests / validation
|
||||
- Add lightweight unit test under `test/` to feed fake `pose_data` with one meta and assert lengths (75/210/63/63) and canvas sizes.
|
||||
- (Optional) round-trip check: feed output to `PoseKeypointPreview` to ensure JSON renders; manual workflow example in `example_workflows`.
|
||||
|
||||
## Integration steps
|
||||
1) Implement converter node code.
|
||||
2) Wire into `NODE_CLASS_MAPPINGS`/`NODE_DISPLAY_NAME_MAPPINGS`.
|
||||
3) Add doc blurb to `README.md` (usage + expected inputs).
|
||||
4) Add test and run `pytest` (or targeted script) if available.
|
||||
|
||||
## Reference file paths (relative to this plan)
|
||||
- `../ComfyUI-WanAnimatePreprocess/nodes.py` — DrawViTPose input schema (`pose_data`, `width`, `height`), PoseAndFaceDetection return structure.
|
||||
- `../ComfyUI-WanAnimatePreprocess/pose_utils/pose2d_utils.py` — `AAPoseMeta` structure, keypoint ordering, `load_from_meta` and related helpers.
|
||||
- `../ComfyUI-WanAnimatePreprocess/pose_utils/human_visualization.py` (optional) — shows how body/hand indices map to drawing; useful to confirm joint ordering if needed.
|
||||
@@ -28,6 +28,11 @@ from .interpolation_node import (
|
||||
ProportionChangerInterpolator
|
||||
)
|
||||
|
||||
# Converter nodes
|
||||
from .converter_nodes import (
|
||||
PoseDataToPoseKeypoint
|
||||
)
|
||||
|
||||
# Import keypoint denoiser node classes
|
||||
from .keypoint_denoiser_node import (
|
||||
ProportionChangerKeypointDenoiser,
|
||||
@@ -49,8 +54,11 @@ __all__ = [
|
||||
|
||||
# Interpolation nodes
|
||||
"ProportionChangerInterpolator",
|
||||
|
||||
# Converter nodes
|
||||
"PoseDataToPoseKeypoint",
|
||||
|
||||
# KeyPoint Denoiser nodes
|
||||
"ProportionChangerKeypointDenoiser",
|
||||
"ProportionChangerKeypointDenoiserAdvanced"
|
||||
]
|
||||
]
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Converter nodes for bridging WanAnimate POSEDATA into POSE_KEYPOINT format.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
try: # Standard package import (ComfyUI runtime)
|
||||
from ..utils import log
|
||||
except ImportError: # Fallback for standalone/unit test environments
|
||||
from utils import log
|
||||
|
||||
|
||||
def _to_array(value):
|
||||
"""Convert nested list/tuple/ndarray to numpy array or return None."""
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
arr = np.array(value, dtype=float)
|
||||
if arr.size == 0:
|
||||
return None
|
||||
return arr
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _looks_normalized(coords: np.ndarray) -> bool:
|
||||
"""
|
||||
Heuristic: treat coordinates as normalized (0..1-ish) if their absolute max is small.
|
||||
WanAnimate/UniAnimate often uses normalized coordinates; some POSEDATA variants already store pixels.
|
||||
"""
|
||||
if coords is None or coords.size == 0:
|
||||
return True
|
||||
finite = coords[np.isfinite(coords)]
|
||||
if finite.size == 0:
|
||||
return True
|
||||
return float(np.max(np.abs(finite))) <= 2.0
|
||||
|
||||
|
||||
def _extract_keypoints(meta, coord_name, conf_name, target_points, width, height, default_conf=1.0):
|
||||
"""
|
||||
Extract, scale, and pad keypoints to the desired target length.
|
||||
Missing coordinates or invalid values are zeroed with confidence 0.
|
||||
"""
|
||||
coords_raw = getattr(meta, coord_name, None)
|
||||
conf_raw = getattr(meta, conf_name, None)
|
||||
|
||||
# Fall back to dict-style access if present
|
||||
if coords_raw is None and isinstance(meta, dict):
|
||||
coords_raw = meta.get(coord_name)
|
||||
if conf_raw is None and isinstance(meta, dict):
|
||||
conf_raw = meta.get(conf_name)
|
||||
|
||||
coords = _to_array(coords_raw)
|
||||
confs = _to_array(conf_raw)
|
||||
if confs is not None:
|
||||
confs = confs.reshape(-1)
|
||||
|
||||
# Normalize coordinate shape to (N, 2)
|
||||
if coords is not None:
|
||||
if coords.ndim == 1:
|
||||
# Flattened x,y pairs
|
||||
if coords.size % 2 == 0:
|
||||
coords = coords.reshape(-1, 2)
|
||||
else:
|
||||
coords = None
|
||||
elif coords.shape[-1] >= 2:
|
||||
coords = coords.reshape(-1, coords.shape[-1])[:, :2]
|
||||
else:
|
||||
coords = None
|
||||
|
||||
scale_from_normalized = _looks_normalized(coords) if coords is not None else True
|
||||
|
||||
points = []
|
||||
max_points = coords.shape[0] if coords is not None else 0
|
||||
|
||||
for idx in range(target_points):
|
||||
if coords is not None and idx < max_points:
|
||||
x, y = coords[idx]
|
||||
conf = confs[idx] if confs is not None and idx < confs.shape[0] else default_conf
|
||||
|
||||
# Validate numerical values
|
||||
if not np.isfinite(x) or not np.isfinite(y) or not np.isfinite(conf):
|
||||
x = y = 0.0
|
||||
conf = 0.0
|
||||
else:
|
||||
x = float(x)
|
||||
y = float(y)
|
||||
if scale_from_normalized:
|
||||
x *= width
|
||||
y *= height
|
||||
conf = float(conf)
|
||||
else:
|
||||
x = y = 0.0
|
||||
conf = 0.0
|
||||
|
||||
points.extend([x, y, conf])
|
||||
|
||||
return points
|
||||
|
||||
|
||||
class PoseDataToPoseKeypoint:
|
||||
"""
|
||||
Convert WanAnimatePreprocess POSEDATA into POSE_KEYPOINT format used by ProportionChanger.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"pose_data": ("POSEDATA", {"tooltip": "POSEDATA output from WanAnimate Pose & Face Detection."}),
|
||||
"width": ("INT", {"default": 512, "min": 1, "max": 4096, "step": 1}),
|
||||
"height": ("INT", {"default": 768, "min": 1, "max": 4096, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("POSE_KEYPOINT",)
|
||||
RETURN_NAMES = ("pose_keypoint",)
|
||||
FUNCTION = "convert"
|
||||
CATEGORY = "ProportionChanger"
|
||||
|
||||
def convert(self, pose_data, width: int, height: int):
|
||||
if width <= 0 or height <= 0:
|
||||
raise ValueError("width and height must be positive integers")
|
||||
|
||||
pose_metas = None
|
||||
if isinstance(pose_data, dict):
|
||||
pose_metas = pose_data.get("pose_metas") or pose_data.get("pose_meta")
|
||||
if pose_metas is None:
|
||||
pose_metas = getattr(pose_data, "pose_metas", None)
|
||||
|
||||
if not pose_metas:
|
||||
empty_frame = {
|
||||
"version": "1.0",
|
||||
"people": [],
|
||||
"canvas_width": width,
|
||||
"canvas_height": height,
|
||||
}
|
||||
return ([empty_frame],)
|
||||
|
||||
frames = []
|
||||
for meta in pose_metas:
|
||||
try:
|
||||
body_points = _extract_keypoints(meta, "kps_body", "kps_body_p", 25, width, height, default_conf=1.0)
|
||||
# AAPoseMeta typically supplies 20 points; padder already set remaining to zeros
|
||||
face_points = _extract_keypoints(meta, "kps_face", "kps_face_p", 70, width, height, default_conf=1.0)
|
||||
lhand_points = _extract_keypoints(meta, "kps_lhand", "kps_lhand_p", 21, width, height, default_conf=1.0)
|
||||
rhand_points = _extract_keypoints(meta, "kps_rhand", "kps_rhand_p", 21, width, height, default_conf=1.0)
|
||||
|
||||
# Determine if body has any confidence > 0
|
||||
has_body = any(body_points[i] > 0 for i in range(2, len(body_points), 3))
|
||||
|
||||
frame = {
|
||||
"version": "1.0",
|
||||
"people": [],
|
||||
"canvas_width": width,
|
||||
"canvas_height": height,
|
||||
}
|
||||
|
||||
if has_body:
|
||||
person = {
|
||||
"pose_keypoints_2d": body_points,
|
||||
"face_keypoints_2d": face_points,
|
||||
"hand_left_keypoints_2d": lhand_points,
|
||||
"hand_right_keypoints_2d": rhand_points,
|
||||
}
|
||||
frame["people"].append(person)
|
||||
|
||||
frames.append(frame)
|
||||
except Exception as exc:
|
||||
log.error(f"PoseDataToPoseKeypoint conversion failed for frame: {exc}")
|
||||
frames.append(
|
||||
{
|
||||
"version": "1.0",
|
||||
"people": [],
|
||||
"canvas_width": width,
|
||||
"canvas_height": height,
|
||||
}
|
||||
)
|
||||
|
||||
return (frames,)
|
||||
@@ -308,7 +308,7 @@ class ProportionChangerParams:
|
||||
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
|
||||
# 階層構造で変換を適用:hip_center -> neck -> shoulders -> arms, legs
|
||||
# 全て元の座標を基準に計算し、依存関係を考慮
|
||||
|
||||
# 1. 骨盤の調整(元の座標基準)
|
||||
@@ -801,4 +801,4 @@ NODE_CLASS_MAPPINGS = {
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ProportionChangerParams": "ProportionChanger Params"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user