From 2472a51c899ff15230225561cae060d31a43d5cb Mon Sep 17 00:00:00 2001 From: "YULE-WINDOWS\\zlcbl" Date: Mon, 12 Jan 2026 10:21:52 +0900 Subject: [PATCH] Initial commit: Pose Keypoints To Image node for ComfyUI --- .gitignore | 10 +++ LICENSE | 21 +++++ README.md | 48 ++++++++++ README_EN.md | 48 ++++++++++ __init__.py | 3 + pose_keypoints_to_image.py | 176 +++++++++++++++++++++++++++++++++++++ 6 files changed, 306 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README_EN.md create mode 100644 __init__.py create mode 100644 pose_keypoints_to_image.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a036f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +.env +.venv +*.egg-info/ +.installed.cfg +*.egg diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c13f991 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a4132e --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# ComfyUI-PoseKeypointsToImage + +POSE_KEYPOINT(DWPose/OpenPose形式)からポーズ画像を描画する ComfyUI カスタムノードです。 + +[English](README_EN.md) + +## 概要 + +DWPose や OpenPose の JSON 形式のキーポイントデータから、ポーズ画像(IMAGE)を生成するノードです。FaceKeypointsFilter で顔パーツをフィルタリングした後の pose_kps を渡すことで、特定のパーツを除外したポーズ画像を作成できます。 + +## インストール + +### ComfyUI Manager を使用(推奨) + +ComfyUI Manager で「PoseKeypointsToImage」を検索してインストール。 + +### 手動インストール + +1. ComfyUI の `custom_nodes` フォルダに移動 +2. このリポジトリをクローン: + ```bash + git clone https://github.com/smartvain/ComfyUI-PoseKeypointsToImage.git + ``` +3. ComfyUI を再起動 + +## 使い方 + +1. DWPose Preprocessor(または `POSE_KEYPOINT` を出力するノード)の出力をこのノードに接続 +2. パラメータを設定: + - `width` / `height`: 出力画像のサイズ(デフォルト: 512x512) + - `point_radius`: キーポイントの描画半径(デフォルト: 2) + - `draw_body`: ボディキーポイントを描画するか + - `draw_face`: 顔キーポイントを描画するか + - `draw_hands`: 手のキーポイントを描画するか +3. 出力を他のノードに接続 + +## ノードの場所 + +`Pose/Face Utils` > `Pose Keypoints → Image` + +## 互換性 + +- `comfyui_controlnet_aux` の DWPose Preprocessor と連携 +- OpenPose JSON フォーマットに対応 + +## ライセンス + +MIT License diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..20f2b71 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,48 @@ +# ComfyUI-PoseKeypointsToImage + +A ComfyUI custom node that renders pose images from POSE_KEYPOINT (DWPose/OpenPose format). + +[日本語](README.md) + +## Description + +This node generates pose images (IMAGE) from DWPose or OpenPose JSON format keypoint data. By passing pose_kps that has been filtered with FaceKeypointsFilter, you can create pose images with specific parts excluded. + +## Installation + +### Using ComfyUI Manager (Recommended) + +Search for "PoseKeypointsToImage" in ComfyUI Manager and install. + +### Manual Installation + +1. Navigate to your ComfyUI's `custom_nodes` folder +2. Clone this repository: + ```bash + git clone https://github.com/smartvain/ComfyUI-PoseKeypointsToImage.git + ``` +3. Restart ComfyUI + +## Usage + +1. Connect the output of DWPose Preprocessor (or any node that outputs `POSE_KEYPOINT`) to this node +2. Configure the parameters: + - `width` / `height`: Output image size (default: 512x512) + - `point_radius`: Keypoint drawing radius (default: 2) + - `draw_body`: Whether to draw body keypoints + - `draw_face`: Whether to draw face keypoints + - `draw_hands`: Whether to draw hand keypoints +3. Connect the output to other nodes + +## Node Location + +`Pose/Face Utils` > `Pose Keypoints → Image` + +## Compatibility + +- Works with DWPose Preprocessor from `comfyui_controlnet_aux` +- Compatible with OpenPose JSON format + +## License + +MIT License diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..d7acad5 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .pose_keypoints_to_image import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/pose_keypoints_to_image.py b/pose_keypoints_to_image.py new file mode 100644 index 0000000..ddc1f15 --- /dev/null +++ b/pose_keypoints_to_image.py @@ -0,0 +1,176 @@ +# pose_keypoints_to_image.py +# +# POSE_KEYPOINT (DWPose / OpenPose 形式) から +# ポーズ画像(IMAGE)を描き直すノードです。 + +import numpy as np +from PIL import Image, ImageDraw +import torch + + +class PoseKeypointsToImage: + """ + POSE_KEYPOINT からポーズ画像を生成するノード。 + DWPose の JSON (frame["people"][0]["pose_keypoints_2d"], etc.) に対応。 + FaceKeypointsFilter で口を消した後の pose_kps などを渡すことで、 + 「口なし」の OpenPose 風画像を作ることができる。 + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "pose_kps": ("POSE_KEYPOINT",), + "width": ( + "INT", + {"default": 512, "min": 64, "max": 4096, "step": 8}, + ), + "height": ( + "INT", + {"default": 512, "min": 64, "max": 4096, "step": 8}, + ), + "point_radius": ( + "INT", + {"default": 2, "min": 1, "max": 10, "step": 1}, + ), + "draw_body": ("BOOLEAN", {"default": True}), + "draw_face": ("BOOLEAN", {"default": True}), + "draw_hands": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = "render" + CATEGORY = "Pose/Face Utils" + + # ------------------ helpers ------------------ # + + def _draw_points(self, draw, flat_list, radius, color=(255, 255, 255)): + """ + flat_list: [x0, y0, s0, x1, y1, s1, ...] + score(s) > 0 の点だけを描画する + """ + if flat_list is None: + return + if len(flat_list) % 3 != 0: + return + + for i in range(0, len(flat_list), 3): + x, y, score = flat_list[i : i + 3] + if score is None: + continue + if score <= 0: + continue + if np.isnan(x) or np.isnan(y): + continue + x = float(x) + y = float(y) + draw.ellipse( + (x - radius, y - radius, x + radius, y + radius), + fill=color, + ) + + def _get_people_list(self, frame): + """ + frame["people"] があればそれを返し、 + 無ければ [frame] を 1人分として扱う。 + """ + if not isinstance(frame, dict): + return [] + + if "people" in frame and isinstance(frame["people"], list) and frame["people"]: + return frame["people"] + + # 旧形式: frame 自体に keypoints がいる想定 + return [frame] + + # ------------------ main ------------------ # + + def render( + self, + pose_kps, + width, + height, + point_radius, + draw_body, + draw_face, + draw_hands, + ): + """ + pose_kps: DWPose Estimator などからの POSE_KEYPOINT + """ + + # pose_kps は通常「各フレーム dict のリスト」 + if isinstance(pose_kps, dict): + frames = [pose_kps] + elif isinstance(pose_kps, list): + frames = pose_kps + else: + blank_np = np.zeros((1, height, width, 3), dtype=np.float32) + blank = torch.from_numpy(blank_np) + return (blank,) + + # 可能なら 1フレーム目の canvas_width/height で上書き + if frames and isinstance(frames[0], dict): + cw = frames[0].get("canvas_width", width) + ch = frames[0].get("canvas_height", height) + try: + width = int(cw) + height = int(ch) + except Exception: + pass + + images = [] + + for frame in frames: + img = Image.new("RGB", (width, height), (0, 0, 0)) + draw = ImageDraw.Draw(img) + + if not isinstance(frame, dict): + images.append(np.array(img).astype(np.float32) / 255.0) + continue + + people = self._get_people_list(frame) + + for person in people: + if not isinstance(person, dict): + continue + + if draw_body and "pose_keypoints_2d" in person: + self._draw_points(draw, person["pose_keypoints_2d"], point_radius) + + if draw_face and "face_keypoints_2d" in person: + self._draw_points(draw, person["face_keypoints_2d"], point_radius) + + if draw_hands: + if "hand_left_keypoints_2d" in person: + self._draw_points( + draw, person["hand_left_keypoints_2d"], point_radius + ) + if "hand_right_keypoints_2d" in person: + self._draw_points( + draw, person["hand_right_keypoints_2d"], point_radius + ) + + img_arr = np.array(img).astype(np.float32) / 255.0 # H,W,3 + images.append(img_arr) + + if not images: + blank_np = np.zeros((1, height, width, 3), dtype=np.float32) + blank = torch.from_numpy(blank_np) + return (blank,) + + batch_np = np.stack(images, axis=0) # (B,H,W,3) + batch = torch.from_numpy(batch_np) # torch.Tensor に変換 + + return (batch,) + + +NODE_CLASS_MAPPINGS = { + "PoseKeypointsToImage": PoseKeypointsToImage, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "PoseKeypointsToImage": "Pose Keypoints → Image", +}