diff --git a/README.md b/README.md index 2f3b4ea..7dc437b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ -# comfyui-openpose-render -ultimate node for openpose rendering +
+ +# ComfyUI ultimate openpose render + +
+ +

+ +

+ +This is a better version of openpose pose render in ComfyUI with both pose keypoints and pose json input options. It also gives the plotting controls with canvas size and pose marker size. + +If you like the project, please give me a star! ⭐ + +## Installation + +- Just install this repo through the Manager. Or you can manually install it, go to ComfyUI `/custom_nodes` directory + ```bash + git clone https://github.com/westNeighbor/ComfyUI-ultimate-openpose-render + cd ./ComfyUI-ultimate-openpose-render + pip install -r requirements.txt # if you use portable version, see below + ``` + if you use portable version, install requirement accordingly, for example, I have portable in my E: disk + ```bash + E:/ComfyUI_windows_portable/python_embeded/python.exe -m pip install -r requirements.txt + ``` +## Usage +- Insert node by `Right Click -> ultimate-openpose -> Openpose Render Node` + +## Features +- Accept both pose keypoints and pose json format input. Be ware that the edit priority is **POSE\_KEYPOINT > POSE\_JSON** + +- Can adjust output canvas size + +- Can control the pose marker size diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..df9ff70 --- /dev/null +++ b/__init__.py @@ -0,0 +1,12 @@ +from .openpose_render_nodes import OpenposeRenderNode + + +NODE_CLASS_MAPPINGS = { + "OpenposeRenderNode": OpenposeRenderNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "OpenposeRenderNode": "Openpose Render Node", +} + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/assets/render_example.png b/assets/render_example.png new file mode 100644 index 0000000..ab78670 Binary files /dev/null and b/assets/render_example.png differ diff --git a/openpose_render_nodes.py b/openpose_render_nodes.py new file mode 100644 index 0000000..768e3cb --- /dev/null +++ b/openpose_render_nodes.py @@ -0,0 +1,54 @@ +import json +import numpy as np +import torch +from .util import draw_pose, draw_pose_json + +class OpenposeRenderNode: + @classmethod + def INPUT_TYPES(s): + return { + "optional": { + "show_body": ("BOOLEAN", {"default": True}), + "show_face": ("BOOLEAN", {"default": True}), + "show_hands": ("BOOLEAN", {"default": True}), + "resolution_x": ("INT", { + "default": -1, + "min": -1, + "max": 12800 + }), + "pose_marker_size": ("INT", { + "default": 4, + "min": 0, + "max": 100 + }), + "face_marker_size": ("INT", { + "default": 3, + "min": 0, + "max": 100 + }), + "hand_marker_size": ("INT", { + "default": 2, + "min": 0, + "max": 100 + }), + "POSE_JSON": ("STRING", {"multiline": True}), + "POSE_KEYPOINT": ("POSE_KEYPOINT",{"default": None}), + }, + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "render_img" + CATEGORY = "ultimate-openpose" + + def render_img(self, show_body, show_face, show_hands, resolution_x, pose_marker_size, face_marker_size, hand_marker_size, POSE_JSON, POSE_KEYPOINT=None): + if POSE_KEYPOINT is not None: + POSE_JSON = json.dumps(POSE_KEYPOINT).replace("'",'"').replace('None','[]') + elif POSE_JSON: + POSE_JSON = POSE_JSON.replace("'",'"').replace('None','[]') + + pose_imgs = draw_pose_json(POSE_JSON, resolution_x, show_body, show_face, show_hands, pose_marker_size, face_marker_size, hand_marker_size) + if pose_imgs: + pose_imgs_np = np.array(pose_imgs).astype(np.float32) / 255 + return (torch.from_numpy(pose_imgs_np),) + else: + raise ValueError("Invalid input type. Expected an input to give an output.") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1e803d2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "ComfyUI-ultimate-openpose-render" +description = "The ultimate openpose render node for ComfyUI with flexible input, output and adjustment." +version = "1.0" +license = "LICENSE" + +[project.urls] +Repository = "https://github.com/westNeighbor/ComfyUI-openpose-render" +# Used by Comfy Registry https://comfyregistry.org + +[tool.comfy] +PublisherId = "westNeighbor" +DisplayName = "ComfyUI-ultimate-openpose-render" +Icon = "" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a4af5da --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +polygraphy +matplotlib +numpy +cv2 +torch diff --git a/util.py b/util.py new file mode 100644 index 0000000..795b118 --- /dev/null +++ b/util.py @@ -0,0 +1,191 @@ +import math +import json +import numpy as np +import matplotlib +import cv2 +from comfy.utils import ProgressBar + +eps = 0.01 + +def draw_pose_json(pose_json, resolution_x, show_body, show_face, show_hands, pose_marker_size, face_marker_size, hand_marker_size): + pose_imgs = [] + if pose_json: + if pose_json.startswith('{'): + pose_json = '[{}]'.format(pose_json) + images = json.loads(pose_json) + pbar = ProgressBar(len(images)) + for image in images: + if 'people' not in image: + pbar.update(len(images)) + return pose_imgs + figures = image['people'] + H = image['canvas_height'] + W = image['canvas_width'] + bodies = [] + candidate = [] + subset = [[]] + faces = [] + hands = [] + for figure in figures: + if 'pose_keypoints_2d' in figure: + body = figure['pose_keypoints_2d'] + if 'face_keypoints_2d' in figure: + face = figure['face_keypoints_2d'] + if 'hand_left_keypoints_2d' in figure: + lhand = figure['hand_left_keypoints_2d'] + if 'hand_right_keypoints_2d' in figure: + rhand = figure['hand_right_keypoints_2d'] + if body: + for i in range(0,len(body),3): + candidate.append(body[i:i+2]) + if not subset[0]: + subset[0].extend([len(subset[0])+(i//3) if body[i+2]>0 else -1 for i in range(0,len(body),3)]) + else: + subset.append([len(subset[0])*len(subset)+(i//3) if body[i+2]>0 else -1 for i in range(0,len(body),3)]) + if face: + faces.append([face[i:i+2] for i in range(0,len(face),3)]) + if lhand: + hands.append([lhand[i:i+2] for i in range(0,len(lhand),3)]) + if rhand: + hands.append([rhand[i:i+2] for i in range(0,len(rhand),3)]) + + normalized = 0.0 + if candidate: + candidate = np.array(candidate).astype(float) + subset = np.array(subset) + normalized = max(np.max(candidate[...,0]),np.max(candidate[...,1])) + if normalized>2.0: + candidate[...,0] /= float(W) + candidate[...,1] /= float(H) + if faces: + faces = np.array(faces).astype(float) + normalized = max(np.max(faces[...,0]),np.max(faces[...,1])) + if normalized>2.0: + faces[...,0] /= float(W) + faces[...,1] /= float(H) + if hands: + hands = np.array(hands).astype(float) + normalized = max(np.max(hands[...,0]),np.max(hands[...,1])) + if normalized>2.0: + hands[...,0] /= float(W) + hands[...,1] /= float(H) + bodies = dict(candidate=candidate, subset=subset) + pose = dict(bodies=bodies, faces=faces, hands=hands) + pose = dict(bodies=bodies if show_body else {'candidate':[], 'subset':[]}, faces=faces if show_face else [], hands=hands if show_hands else []) + W_scaled = resolution_x + if resolution_x < 64: + W_scaled = W + H_scaled = int(H*(W_scaled*1.0/W)) + pose_img = draw_pose(pose, H_scaled, W_scaled, pose_marker_size, face_marker_size, hand_marker_size) + pose_imgs.append(pose_img) + pbar.update(1) + + return pose_imgs + +def draw_pose(pose, H, W, pose_marker_size, face_marker_size, hand_marker_size): + bodies = pose['bodies'] + faces = pose['faces'] + hands = pose['hands'] + candidate = bodies['candidate'] + subset = bodies['subset'] + canvas = np.zeros(shape=(H, W, 3), dtype=np.uint8) + + if len(candidate) > 0: + canvas = draw_bodypose(canvas, candidate, subset, pose_marker_size) + + if len(hands) > 0: + canvas = draw_handpose(canvas, hands, hand_marker_size) + + if len(faces) > 0: + canvas = draw_facepose(canvas, faces, face_marker_size) + + return canvas + +def draw_bodypose(canvas, candidate, subset, pose_marker_size): + H, W, C = canvas.shape + candidate = np.array(candidate) + subset = np.array(subset) + + # stickwidth = 4 + + limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \ + [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \ + [1, 16], [16, 18], [3, 17], [6, 18]] + + colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], \ + [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], \ + [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]] + + for i in range(17): + for n in range(len(subset)): + index = subset[n][np.array(limbSeq[i]) - 1] + if -1 in index: + continue + Y = candidate[index.astype(int), 0] * float(W) + X = candidate[index.astype(int), 1] * float(H) + mX = np.mean(X) + mY = np.mean(Y) + length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5 + angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1])) + polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), pose_marker_size), int(angle), 0, 360, 1) + cv2.fillConvexPoly(canvas, polygon, colors[i]) + + canvas = (canvas * 0.6).astype(np.uint8) + + for i in range(18): + for n in range(len(subset)): + index = int(subset[n][i]) + if index == -1: + continue + x, y = candidate[index][0:2] + x = int(x * W) + y = int(y * H) + cv2.circle(canvas, (int(x), int(y)), pose_marker_size, colors[i], thickness=-1) + + return canvas + + +def draw_handpose(canvas, all_hand_peaks, hand_marker_size): + H, W, C = canvas.shape + + edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \ + [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]] + + for peaks in all_hand_peaks: + peaks = np.array(peaks) + + for ie, e in enumerate(edges): + x1, y1 = peaks[e[0]] + x2, y2 = peaks[e[1]] + x1 = int(x1 * W) + y1 = int(y1 * H) + x2 = int(x2 * W) + y2 = int(y2 * H) + if x1 > eps and y1 > eps and x2 > eps and y2 > eps: + cv2.line(canvas, (x1, y1), (x2, y2), matplotlib.colors.hsv_to_rgb([ie / float(len(edges)), 1.0, 1.0]) * 255, thickness=1 if hand_marker_size == 0 else hand_marker_size) + + joint_size=0 + if hand_marker_size < 2: + joint_size = hand_marker_size + 1 + else: + joint_size = hand_marker_size + 2 + for i, keyponit in enumerate(peaks): + x, y = keyponit + x = int(x * W) + y = int(y * H) + if x > eps and y > eps: + cv2.circle(canvas, (x, y), joint_size, (0, 0, 255), thickness=-1) + return canvas + + +def draw_facepose(canvas, all_lmks, face_marker_size): + H, W, C = canvas.shape + for lmks in all_lmks: + lmks = np.array(lmks) + for lmk in lmks: + x, y = lmk + x = int(x * W) + y = int(y * H) + if x > eps and y > eps: + cv2.circle(canvas, (x, y), face_marker_size, (255, 255, 255), thickness=-1) + return canvas