This commit is contained in:
Brett Emaus
2026-02-19 16:32:47 -05:00
parent 130fddeb5c
commit 090fd66cb9
+52 -61
View File
@@ -445,70 +445,61 @@ def rotate_keypoints_3d(
return result
def _fit_and_center_keypoints_on_canvas(
keypoints: dict[str, list[tuple[float, float, float]]],
width: int,
height: int,
padding: float = 0.05,
def _get_anchor_point(keypoints: dict[str, list[tuple[float, float, float]]]) -> tuple[float, float] | None:
"""
Get anchor point from body keypoints for positioning.
Prefer neck (1); if missing, use midpoint of shoulders (2, 5); if only one shoulder, use it.
Returns (x, y) or None if insufficient keypoints.
"""
body = keypoints.get("body", [])
if len(body) < 2:
return None
def get_pt(idx: int) -> tuple[float, float] | None:
if idx >= len(body):
return None
x, y, c = body[idx]
return (x, y) if c > 0 else None
# 1. Neck (index 1)
neck = get_pt(1)
if neck is not None:
return neck
# 2. Both shoulders (2, 5)
r_sh = get_pt(2)
l_sh = get_pt(5)
if r_sh is not None and l_sh is not None:
return ((r_sh[0] + l_sh[0]) / 2, (r_sh[1] + l_sh[1]) / 2)
# 3. Single shoulder
if r_sh is not None:
return r_sh
if l_sh is not None:
return l_sh
return None
def _position_keypoints_by_anchor(
rotated: dict[str, list[tuple[float, float, float]]],
original: dict[str, list[tuple[float, float, float]]],
) -> dict[str, list[tuple[float, float, float]]]:
"""
Scale keypoints to fit within canvas (if they exceed bounds), then center.
Prevents feet/head from being cut off when the figure is taller than the image.
Uses main body only (excludes face indices) for centroid/bbox so face points
don't pull the figure down when centered.
Translate rotated keypoints so the anchor (neck or shoulders) matches the original position.
Keeps the figure anchored in place based on its pre-rotation position.
"""
all_x, all_y, total_conf = 0.0, 0.0, 0.0
xs, ys = [], []
for part, pts in keypoints.items():
for i, (x, y, c) in enumerate(pts):
if c <= 0:
continue
if part == "body" and i in FACE_INDICES:
continue # Exclude face from centroid/bbox - center on main body
all_x += x * c
all_y += y * c
total_conf += c
xs.append(x)
ys.append(y)
orig_anchor = _get_anchor_point(original)
rot_anchor = _get_anchor_point(rotated)
if orig_anchor is None or rot_anchor is None:
return rotated
if total_conf <= 0 or not xs or not ys:
return keypoints
min_x, max_x = min(xs), max(xs)
min_y, max_y = min(ys), max(ys)
# Centroid (stays fixed when scaling around it)
cx = all_x / total_conf
cy = all_y / total_conf
# Max extent from centroid in each direction (we center the centroid, so we must fit
# the larger of the two sides; using bbox_h/2 fails when centroid is off-center)
extent_x = max(cx - min_x, max_x - cx)
extent_y = max(cy - min_y, max_y - cy)
# Usable canvas with padding (half-extent each side of center)
pad_w = width * padding
pad_h = height * padding
usable_half_w = (width - 2 * pad_w) / 2.0
usable_half_h = (height - 2 * pad_h) / 2.0
# Scale down so extent from centroid fits within usable half-canvas
scale = 1.0
if extent_x > usable_half_w and usable_half_w > 0:
scale = min(scale, usable_half_w / extent_x)
if extent_y > usable_half_h and usable_half_h > 0:
scale = min(scale, usable_half_h / extent_y)
target_x = width / 2.0
target_y = height / 2.0
dx = target_x - cx
dy = target_y - cy
dx = orig_anchor[0] - rot_anchor[0]
dy = orig_anchor[1] - rot_anchor[1]
result: dict[str, list[tuple[float, float, float]]] = {}
for part, pts in keypoints.items():
result[part] = [
(cx + (x - cx) * scale + dx, cy + (y - cy) * scale + dy, c)
for x, y, c in pts
]
for part, pts in rotated.items():
result[part] = [(x + dx, y + dy, c) for x, y, c in pts]
return result
@@ -633,10 +624,10 @@ def rotate_openpose(
rotated = rotate_keypoints_3d(keypoints, pivot, degrees, direction)
rotated = join_broken_segments(rotated, 0.0)
# Scale to fit and center figure (prevents feet/head cutoff, fixes right/left bias)
rotated = _fit_and_center_keypoints_on_canvas(rotated, w, h)
# Position by anchor: align neck/shoulders with original figure position
rotated = _position_keypoints_by_anchor(rotated, keypoints)
# Console: post-rotated figure (after rotation, fit, and center)
# Console: post-rotated figure (after rotation and anchor positioning)
post_dict = _keypoints_to_openpose_dict(rotated)
print(f"[OpenPose Rotator] Image {image_index} POST-ROTATED (direction={direction}, degrees={degrees}):")
print(f" body: {rotated.get('body', [])}")