154 lines
5.6 KiB
Python
154 lines
5.6 KiB
Python
|
import json
|
||
|
import numpy as np
|
||
|
from typing import List
|
||
|
import math
|
||
|
import cv2
|
||
|
|
||
|
class Keypoint:
|
||
|
def __init__(self, x: float, y: float, confidence: float = 1.0):
|
||
|
"""
|
||
|
Initialize a Keypoint object.
|
||
|
|
||
|
Args:
|
||
|
x (float): The x-coordinate of the keypoint.
|
||
|
y (float): The y-coordinate of the keypoint.
|
||
|
confidence (float): The confidence score of the keypoint. Default is 1.0.
|
||
|
"""
|
||
|
self.x = x
|
||
|
self.y = y
|
||
|
self.confidence = confidence
|
||
|
|
||
|
def __repr__(self):
|
||
|
return f"Keypoint(x={self.x}, y={self.y}, confidence={self.confidence})"
|
||
|
|
||
|
def is_normalized(keypoints: List[Keypoint]) -> bool:
|
||
|
"""
|
||
|
Check if the keypoints are normalized between 0 and 1.
|
||
|
|
||
|
Args:
|
||
|
keypoints (List[Keypoint]): A list of Keypoint objects.
|
||
|
|
||
|
Returns:
|
||
|
bool: True if all keypoints are normalized, False otherwise.
|
||
|
"""
|
||
|
for keypoint in keypoints:
|
||
|
if not (0 <= keypoint.x <= 1 and 0 <= keypoint.y <= 1):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
def draw_bodypose(canvas: np.ndarray, keypoints: List[Keypoint], xinsr_stick_scaling: bool = False) -> np.ndarray:
|
||
|
"""
|
||
|
Draw keypoints and limbs representing body pose on a given canvas.
|
||
|
|
||
|
Args:
|
||
|
canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the body pose.
|
||
|
keypoints (List[Keypoint]): A list of Keypoint objects representing the body keypoints to be drawn.
|
||
|
xinsr_stick_scaling (bool): Whether or not scaling stick width for xinsr ControlNet
|
||
|
|
||
|
Returns:
|
||
|
np.ndarray: A 3D numpy array representing the modified canvas with the drawn body pose.
|
||
|
|
||
|
Note:
|
||
|
The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1.
|
||
|
"""
|
||
|
if not is_normalized(keypoints):
|
||
|
H, W = 1.0, 1.0
|
||
|
else:
|
||
|
H, W, _ = canvas.shape
|
||
|
|
||
|
CH, CW, _ = canvas.shape
|
||
|
stickwidth = 4
|
||
|
|
||
|
# Ref: https://huggingface.co/xinsir/controlnet-openpose-sdxl-1.0
|
||
|
max_side = max(CW, CH)
|
||
|
if xinsr_stick_scaling:
|
||
|
stick_scale = 1 if max_side < 500 else min(2 + (max_side // 1000), 7)
|
||
|
else:
|
||
|
stick_scale = 1
|
||
|
|
||
|
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],
|
||
|
]
|
||
|
|
||
|
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 (k1_index, k2_index), color in zip(limbSeq, colors):
|
||
|
keypoint1 = keypoints[k1_index - 1]
|
||
|
keypoint2 = keypoints[k2_index - 1]
|
||
|
|
||
|
if keypoint1 is None or keypoint2 is None:
|
||
|
continue
|
||
|
|
||
|
Y = np.array([keypoint1.x, keypoint2.x]) * float(W)
|
||
|
X = np.array([keypoint1.y, keypoint2.y]) * 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), stickwidth*stick_scale), int(angle), 0, 360, 1)
|
||
|
cv2.fillConvexPoly(canvas, polygon, [int(float(c) * 0.6) for c in color])
|
||
|
|
||
|
for keypoint, color in zip(keypoints, colors):
|
||
|
if keypoint is None:
|
||
|
continue
|
||
|
|
||
|
x, y = keypoint.x, keypoint.y
|
||
|
x = int(x * W)
|
||
|
y = int(y * H)
|
||
|
cv2.circle(canvas, (int(x), int(y)), 4, color, thickness=-1)
|
||
|
|
||
|
return canvas
|
||
|
|
||
|
def openpose_json_to_keypoints(json_file: str) -> List[Keypoint]:
|
||
|
"""
|
||
|
Convert OpenPose JSON file to a list of Keypoint objects.
|
||
|
|
||
|
Args:
|
||
|
json_file (str): The path to the OpenPose JSON file.
|
||
|
|
||
|
Returns:
|
||
|
List[Keypoint]: A list of Keypoint objects representing the keypoints extracted from the JSON file.
|
||
|
"""
|
||
|
with open(json_file, 'r') as file:
|
||
|
data = json.load(file)
|
||
|
keypoints = data[0]['people'][0]['pose_keypoints_2d']
|
||
|
keypoints = [Keypoint(keypoints[i], keypoints[i + 1]) for i in range(0, len(keypoints), 3)]
|
||
|
return keypoints
|
||
|
|
||
|
def main():
|
||
|
# Create a blank canvas
|
||
|
canvas = np.zeros((768, 768, 3), dtype=np.uint8)
|
||
|
|
||
|
# Load the keypoints from the OpenPose JSON file
|
||
|
keypoints = openpose_json_to_keypoints('openpose_output.json')
|
||
|
|
||
|
# Draw the body pose on the canvas
|
||
|
canvas = draw_bodypose(canvas, keypoints)
|
||
|
|
||
|
# Display the result
|
||
|
cv2.imshow('Body Pose', canvas)
|
||
|
cv2.waitKey(0)
|
||
|
cv2.destroyAllWindows()
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|
||
|
|
||
|
# Here is an example of input json file, create a List of Keypoint objects from the json file and draw the body pose on a canvas using the draw_bodypose function.
|
||
|
#
|
||
|
# [
|
||
|
# {
|
||
|
# "people": [
|
||
|
# {
|
||
|
# "pose_keypoints_2d": [423.575046753433, 113.537678172812, 1, 378.126093103822, 185.044031914867, 1, 416.303214169495, 169.288394649668, 1, 470.841958549029, 122.021482854073, 1, 519.320842441947, 48.0911849173717, 1, 339.948972038149, 200.799669180065, 1, 322.981362675627, 289.273632284642, 1, 316.921502189012, 391.079288459771, 1, 439.330684018632, 363.203930221343, 1, 412.667297877527, 541.363828527819, 1, 375.096162860515, 702.556117471773, 1, 375.096162860515, 364.415902318666, 1, 359.340525595316, 530.456079651912, 1, 292.682060242553, 674.680759233345, 1, 411.455325780204, 105.053873491551, 1, 418.727158364141, 102.629929296905, 1, 383.579967541775, 120.80951075675, 1, 371.460246568546, 122.021482854073, 1]
|
||
|
# }
|
||
|
# ],
|
||
|
# "canvas_height": 768,
|
||
|
# "canvas_width": 768
|
||
|
# }
|
||
|
# ]
|