- Remove debug colored dots (red, green, blue) from DWPose render when no valid keypoints - Remove debug white circle when no valid keypoints detected - Failed pose detection now properly displays black screen instead of test markers - Affects ProportionChanger DWPose Render node when reference pose detection fails 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
338 lines
14 KiB
Python
338 lines
14 KiB
Python
"""
|
|
DWPose rendering utilities for drawing poses on canvas
|
|
Handles body, hand, and face keypoint visualization with DWPose styling
|
|
"""
|
|
|
|
import cv2
|
|
import math
|
|
import numpy as np
|
|
import colorsys
|
|
import os
|
|
import logging
|
|
|
|
# Debug mode control via environment variable (local implementation to avoid imports)
|
|
DEBUG_MODE = os.getenv('PROPORTION_CHANGER_DEBUG', 'false').lower() in ('true', '1', 'yes')
|
|
log = logging.getLogger(__name__)
|
|
|
|
def debug_log(message):
|
|
"""Conditional debug logging - only outputs if DEBUG_MODE is enabled"""
|
|
if DEBUG_MODE:
|
|
log.debug(f"🔍 {message}")
|
|
|
|
|
|
def draw_dwpose_render(pose_keypoint, resolution_x, show_body, show_face, show_hands, show_feet,
|
|
pose_marker_size, face_marker_size, hand_marker_size):
|
|
"""
|
|
Render POSE_KEYPOINT data using DWPose style with 25-point support including toe keypoints
|
|
Compatible with ultimate-openpose-render parameters but using DWPose rendering algorithms
|
|
"""
|
|
|
|
if not pose_keypoint or len(pose_keypoint) == 0:
|
|
return []
|
|
|
|
pose_imgs = []
|
|
|
|
# Handle single frame vs multi-frame input
|
|
if isinstance(pose_keypoint, dict):
|
|
frames = [pose_keypoint]
|
|
else:
|
|
frames = pose_keypoint
|
|
|
|
for frame_data in frames:
|
|
if 'people' not in frame_data or len(frame_data['people']) == 0:
|
|
# Create empty image for frames with no people
|
|
H = frame_data.get('canvas_height', 768)
|
|
W = frame_data.get('canvas_width', 512)
|
|
if resolution_x > 0:
|
|
W = resolution_x
|
|
H = int(frame_data.get('canvas_height', 768) * (W / frame_data.get('canvas_width', 512)))
|
|
pose_imgs.append(np.zeros((H, W, 3), dtype=np.uint8))
|
|
continue
|
|
|
|
# Get canvas dimensions
|
|
H = frame_data.get('canvas_height', 768)
|
|
W = frame_data.get('canvas_width', 512)
|
|
|
|
# Apply resolution scaling
|
|
if resolution_x > 0:
|
|
W_scaled = resolution_x
|
|
H_scaled = int(H * (W_scaled / W))
|
|
else:
|
|
W_scaled, H_scaled = W, H
|
|
|
|
# Create canvas
|
|
canvas = np.zeros((H_scaled, W_scaled, 3), dtype=np.uint8)
|
|
|
|
# Process each person in the frame
|
|
for person in frame_data['people']:
|
|
# Draw body keypoints
|
|
if show_body and 'pose_keypoints_2d' in person:
|
|
canvas = draw_dwpose_body_and_foot(canvas, person['pose_keypoints_2d'],
|
|
W_scaled, H_scaled, pose_marker_size, show_feet)
|
|
|
|
# Draw hand keypoints
|
|
if show_hands:
|
|
if 'hand_left_keypoints_2d' in person:
|
|
canvas = draw_dwpose_handpose(canvas, person['hand_left_keypoints_2d'],
|
|
W_scaled, H_scaled, hand_marker_size)
|
|
if 'hand_right_keypoints_2d' in person:
|
|
canvas = draw_dwpose_handpose(canvas, person['hand_right_keypoints_2d'],
|
|
W_scaled, H_scaled, hand_marker_size)
|
|
|
|
# Draw face keypoints
|
|
if show_face and 'face_keypoints_2d' in person:
|
|
canvas = draw_dwpose_facepose(canvas, person['face_keypoints_2d'],
|
|
W_scaled, H_scaled, face_marker_size)
|
|
|
|
pose_imgs.append(canvas)
|
|
|
|
return pose_imgs
|
|
|
|
|
|
def draw_dwpose_body_and_foot(canvas, body_keypoints, W, H, pose_marker_size, show_feet):
|
|
"""
|
|
Draw body and foot keypoints using DWPose style (25-point support)
|
|
Based on WanVideo DWPose draw_body_and_foot function
|
|
"""
|
|
|
|
if not body_keypoints or len(body_keypoints) < 54: # At least 18 points * 3
|
|
return canvas
|
|
|
|
# Define limb connections (bone structure)
|
|
if show_feet and len(body_keypoints) >= 75: # 25 points * 3
|
|
# DWPose 25-point with toe connections
|
|
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], [14, 19], [11, 20]] # Added toe connections
|
|
else:
|
|
# Standard 18-point OpenPose
|
|
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]]
|
|
|
|
# Color palette for bones
|
|
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], [170, 255, 255], [255, 255, 0]]
|
|
|
|
# First pass: determine coordinate system by checking all coordinates
|
|
max_coord = 0
|
|
for i in range(0, len(body_keypoints), 3):
|
|
if i + 2 < len(body_keypoints):
|
|
x_raw = body_keypoints[i]
|
|
y_raw = body_keypoints[i + 1]
|
|
if x_raw > 0 and y_raw > 0:
|
|
max_coord = max(max_coord, x_raw, y_raw)
|
|
|
|
# Determine if coordinates are normalized or already in pixel space
|
|
is_normalized = max_coord <= 2.0
|
|
|
|
# Convert keypoints to coordinate pairs with consistent coordinate system
|
|
keypoints = []
|
|
confidences = []
|
|
|
|
for i in range(0, len(body_keypoints), 3):
|
|
if i + 2 < len(body_keypoints):
|
|
x_raw = body_keypoints[i]
|
|
y_raw = body_keypoints[i + 1]
|
|
conf = body_keypoints[i + 2]
|
|
|
|
# Apply consistent coordinate transformation
|
|
if is_normalized:
|
|
# Data is normalized (0-1), convert to pixel coordinates
|
|
x = x_raw * W
|
|
y = y_raw * H
|
|
else:
|
|
# Data is already in pixel coordinates, use as-is
|
|
x = x_raw
|
|
y = y_raw
|
|
|
|
keypoints.append([x, y])
|
|
confidences.append(conf)
|
|
|
|
# Debug: check if we have valid keypoints
|
|
valid_keypoints = sum(1 for conf in confidences if conf > 0.0)
|
|
debug_log(f"Body Debug - Canvas size: {W}x{H}, Max coord: {max_coord:.4f}, Normalized: {is_normalized}")
|
|
debug_log(f"Body Debug - Valid keypoints: {valid_keypoints}/{len(confidences)}")
|
|
debug_log(f"Body Debug - First 3 keypoints: {[(i, f'{keypoints[i][0]:.1f}, {keypoints[i][1]:.1f}', f'{confidences[i]:.3f}') for i in range(min(3, len(keypoints)))]}")
|
|
|
|
if valid_keypoints == 0:
|
|
debug_log("Body Debug - No valid keypoints, returning black screen")
|
|
# Return black screen for failed pose detection
|
|
return canvas
|
|
|
|
# Draw limb connections (bones)
|
|
bones_drawn = 0
|
|
for i, limb in enumerate(limbSeq):
|
|
if len(keypoints) >= max(limb[0], limb[1]):
|
|
pt1_idx, pt2_idx = limb[0] - 1, limb[1] - 1 # Convert to 0-based indexing
|
|
|
|
# Use more lenient confidence threshold
|
|
if (pt1_idx < len(confidences) and pt2_idx < len(confidences) and
|
|
confidences[pt1_idx] > 0.0 and confidences[pt2_idx] > 0.0):
|
|
|
|
x1, y1 = keypoints[pt1_idx]
|
|
x2, y2 = keypoints[pt2_idx]
|
|
|
|
# More lenient coordinate check
|
|
if x1 >= 0 and y1 >= 0 and x2 >= 0 and y2 >= 0:
|
|
# Calculate bone properties (following WanVideo DWPose convention)
|
|
# Note: WanVideo uses X=height, Y=width convention
|
|
X1, Y1 = y1, x1 # Convert to WanVideo coordinate convention
|
|
X2, Y2 = y2, x2
|
|
mX = (X1 + X2) / 2 # Mean height coordinate
|
|
mY = (Y1 + Y2) / 2 # Mean width coordinate
|
|
length = ((X1 - X2) ** 2 + (Y1 - Y2) ** 2) ** 0.5
|
|
|
|
if length > 1: # Only draw if bone has reasonable length
|
|
# Use WanVideo's angle calculation
|
|
angle = math.degrees(math.atan2(X1 - X2, Y1 - Y2))
|
|
|
|
# Draw bone as ellipse polygon (note coordinate order: Y, X)
|
|
stick_width = max(1, pose_marker_size)
|
|
polygon = cv2.ellipse2Poly((int(mY), int(mX)),
|
|
(int(length / 2), stick_width),
|
|
int(angle), 0, 360, 1)
|
|
color_idx = min(i, len(colors) - 1)
|
|
cv2.fillConvexPoly(canvas, polygon, colors[color_idx])
|
|
bones_drawn += 1
|
|
|
|
# Apply transparency to bones only if bones were drawn
|
|
if bones_drawn > 0:
|
|
canvas = (canvas * 0.6).astype(np.uint8)
|
|
|
|
# Draw keypoint markers
|
|
if pose_marker_size > 0:
|
|
max_points = min(len(keypoints), 25 if show_feet else 18)
|
|
points_drawn = 0
|
|
for i in range(max_points):
|
|
if i < len(confidences) and confidences[i] > 0.0:
|
|
x, y = keypoints[i]
|
|
if x >= 0 and y >= 0 and x < W and y < H:
|
|
color_idx = min(i, len(colors) - 1)
|
|
cv2.circle(canvas, (int(x), int(y)), pose_marker_size, colors[color_idx], thickness=-1)
|
|
points_drawn += 1
|
|
|
|
# No debug points - let failed poses show as black screen
|
|
|
|
return canvas
|
|
|
|
|
|
def draw_dwpose_handpose(canvas, hand_keypoints, W, H, hand_marker_size):
|
|
"""
|
|
Draw hand keypoints using DWPose style
|
|
Based on WanVideo DWPose draw_handpose function
|
|
"""
|
|
|
|
if not hand_keypoints or len(hand_keypoints) < 63: # 21 points * 3
|
|
return canvas
|
|
|
|
# Hand bone connections
|
|
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]]
|
|
|
|
# First pass: determine coordinate system
|
|
max_coord = 0
|
|
for i in range(0, len(hand_keypoints), 3):
|
|
if i + 2 < len(hand_keypoints):
|
|
x_raw = hand_keypoints[i]
|
|
y_raw = hand_keypoints[i + 1]
|
|
if x_raw > 0 and y_raw > 0:
|
|
max_coord = max(max_coord, x_raw, y_raw)
|
|
|
|
is_normalized = max_coord <= 2.0
|
|
|
|
# Convert keypoints to coordinate pairs with consistent coordinate system
|
|
keypoints = []
|
|
confidences = []
|
|
|
|
for i in range(0, len(hand_keypoints), 3):
|
|
if i + 2 < len(hand_keypoints):
|
|
x_raw = hand_keypoints[i]
|
|
y_raw = hand_keypoints[i + 1]
|
|
conf = hand_keypoints[i + 2]
|
|
|
|
# Apply consistent coordinate transformation
|
|
if is_normalized:
|
|
x = x_raw * W
|
|
y = y_raw * H
|
|
else:
|
|
x = x_raw
|
|
y = y_raw
|
|
|
|
keypoints.append([x, y])
|
|
confidences.append(conf)
|
|
|
|
# Draw hand connections
|
|
if hand_marker_size > 0:
|
|
for ie, edge in enumerate(edges):
|
|
if (len(keypoints) > max(edge) and
|
|
confidences[edge[0]] > 0.0 and confidences[edge[1]] > 0.0):
|
|
|
|
x1, y1 = keypoints[edge[0]]
|
|
x2, y2 = keypoints[edge[1]]
|
|
|
|
if x1 >= 0 and y1 >= 0 and x2 >= 0 and y2 >= 0:
|
|
# Generate color using HSV
|
|
h = (ie / float(len(edges))) % 1.0
|
|
r, g, b = colorsys.hsv_to_rgb(h, 1.0, 1.0)
|
|
color = (int(255 * r), int(255 * g), int(255 * b))
|
|
cv2.line(canvas, (int(x1), int(y1)), (int(x2), int(y2)), color, thickness=2)
|
|
|
|
# Draw hand keypoints
|
|
if hand_marker_size > 0:
|
|
for i, (x, y) in enumerate(keypoints):
|
|
if i < len(confidences) and confidences[i] > 0.0 and x >= 0 and y >= 0:
|
|
cv2.circle(canvas, (int(x), int(y)), hand_marker_size, (0, 0, 255), thickness=-1)
|
|
|
|
return canvas
|
|
|
|
|
|
def draw_dwpose_facepose(canvas, face_keypoints, W, H, face_marker_size):
|
|
"""
|
|
Draw face keypoints using DWPose style
|
|
Based on WanVideo DWPose draw_facepose function
|
|
"""
|
|
|
|
if not face_keypoints or len(face_keypoints) < 210: # 70 points * 3
|
|
return canvas
|
|
|
|
# First pass: determine coordinate system
|
|
max_coord = 0
|
|
for i in range(0, len(face_keypoints), 3):
|
|
if i + 2 < len(face_keypoints):
|
|
x_raw = face_keypoints[i]
|
|
y_raw = face_keypoints[i + 1]
|
|
if x_raw > 0 and y_raw > 0:
|
|
max_coord = max(max_coord, x_raw, y_raw)
|
|
|
|
is_normalized = max_coord <= 2.0
|
|
|
|
# Convert keypoints to coordinate pairs with consistent coordinate system
|
|
keypoints = []
|
|
confidences = []
|
|
|
|
for i in range(0, len(face_keypoints), 3):
|
|
if i + 2 < len(face_keypoints):
|
|
x_raw = face_keypoints[i]
|
|
y_raw = face_keypoints[i + 1]
|
|
conf = face_keypoints[i + 2]
|
|
|
|
# Apply consistent coordinate transformation
|
|
if is_normalized:
|
|
x = x_raw * W
|
|
y = y_raw * H
|
|
else:
|
|
x = x_raw
|
|
y = y_raw
|
|
|
|
keypoints.append([x, y])
|
|
confidences.append(conf)
|
|
|
|
# Draw face keypoints
|
|
if face_marker_size > 0:
|
|
for i, (x, y) in enumerate(keypoints):
|
|
if i < len(confidences) and confidences[i] > 0.0 and x >= 0 and y >= 0:
|
|
cv2.circle(canvas, (int(x), int(y)), face_marker_size, (255, 255, 255), thickness=-1)
|
|
|
|
return canvas |