GameMaster-Mocap / utils /skeleton.py
vivekchakraverty's picture
Upload 16 files
fc3ca1b verified
"""
utils/skeleton.py
─────────────────
Bone registry, parent/child hierarchy, H36M↔custom-name mapping,
T-pose rest positions, and all coordinate-space helpers.
Custom bone layout
──────────────────
Idx Name Parent H36M src joint
0 Hips -1 0 (hip centre)
1 Spine 0 7 (spine)
2 Chest 1 8 (thorax)
3 Neck 2 lerp(chest, head, 0.4)
4 Head 3 10 (head / top)
5 LeftUpperArm 2 11 (L shoulder)
6 LeftLowerArm 5 12 (L elbow)
7 LeftHand 6 13 (L wrist)
8 RightUpperArm 2 14 (R shoulder)
9 RightLowerArm 8 15 (R elbow)
10 RightHand 9 16 (R wrist)
11 LeftUpperLeg 0 4 (L hip)
12 LeftLowerLeg 11 5 (L knee)
13 LeftFoot 12 6 (L ankle)
14 RightUpperLeg 0 1 (R hip)
15 RightLowerLeg 14 2 (R knee)
16 RightFoot 15 3 (R ankle)
"""
from __future__ import annotations
import numpy as np
# ── Registry ──────────────────────────────────────────────────────────────────
BONE_NAMES: list[str] = [
"Hips", # 0
"Spine", # 1
"Chest", # 2
"Neck", # 3
"Head", # 4
"LeftUpperArm", # 5
"LeftLowerArm", # 6
"LeftHand", # 7
"RightUpperArm", # 8
"RightLowerArm", # 9
"RightHand", # 10
"LeftUpperLeg", # 11
"LeftLowerLeg", # 12
"LeftFoot", # 13
"RightUpperLeg", # 14
"RightLowerLeg", # 15
"RightFoot", # 16
]
N_BONES: int = len(BONE_NAMES)
BONE_IDX: dict[str, int] = {n: i for i, n in enumerate(BONE_NAMES)}
BONE_PARENT: list[int] = [
-1, # 0 Hips root
0, # 1 Spine ← Hips
1, # 2 Chest ← Spine
2, # 3 Neck ← Chest
3, # 4 Head ← Neck
2, # 5 LeftUpperArm ← Chest
5, # 6 LeftLowerArm ← LeftUpperArm
6, # 7 LeftHand ← LeftLowerArm
2, # 8 RightUpperArm← Chest
8, # 9 RightLowerArm← RightUpperArm
9, # 10 RightHand ← RightLowerArm
0, # 11 LeftUpperLeg ← Hips
11, # 12 LeftLowerLeg ← LeftUpperLeg
12, # 13 LeftFoot ← LeftLowerLeg
0, # 14 RightUpperLeg← Hips
14, # 15 RightLowerLeg← RightUpperLeg
15, # 16 RightFoot ← RightLowerLeg
]
# Derived child lists
BONE_CHILDREN: dict[int, list[int]] = {i: [] for i in range(N_BONES)}
for _c, _p in enumerate(BONE_PARENT):
if _p >= 0:
BONE_CHILDREN[_p].append(_c)
# ── T-Pose rest positions (world space, metres, Y-up) ─────────────────────────
# Used for GLB inverse-bind matrices and Godot rest pose.
T_POSE_WORLD: np.ndarray = np.array([
[ 0.000, 1.000, 0.000], # 0 Hips
[ 0.000, 1.100, 0.000], # 1 Spine
[ 0.000, 1.300, 0.000], # 2 Chest
[ 0.000, 1.450, 0.000], # 3 Neck
[ 0.000, 1.620, 0.000], # 4 Head
[-0.200, 1.350, 0.000], # 5 LeftUpperArm
[-0.450, 1.350, 0.000], # 6 LeftLowerArm
[-0.650, 1.350, 0.000], # 7 LeftHand
[ 0.200, 1.350, 0.000], # 8 RightUpperArm
[ 0.450, 1.350, 0.000], # 9 RightLowerArm
[ 0.650, 1.350, 0.000], # 10 RightHand
[-0.100, 0.850, 0.000], # 11 LeftUpperLeg
[-0.100, 0.450, 0.000], # 12 LeftLowerLeg
[-0.100, 0.050, 0.000], # 13 LeftFoot
[ 0.100, 0.850, 0.000], # 14 RightUpperLeg
[ 0.100, 0.450, 0.000], # 15 RightLowerLeg
[ 0.100, 0.050, 0.000], # 16 RightFoot
], dtype=np.float32)
# T-pose LOCAL positions (relative to parent bone)
def _build_tpose_local() -> np.ndarray:
local = np.zeros_like(T_POSE_WORLD)
for j in range(N_BONES):
p = BONE_PARENT[j]
if p < 0:
local[j] = T_POSE_WORLD[j]
else:
local[j] = T_POSE_WORLD[j] - T_POSE_WORLD[p]
return local
T_POSE_LOCAL: np.ndarray = _build_tpose_local()
# ── H36M joint-index β†’ our bone index ────────────────────────────────────────
# H36M 17-joint order:
# 0=hip_ctr, 1=R_hip, 2=R_knee, 3=R_ankle,
# 4=L_hip, 5=L_knee, 6=L_ankle,
# 7=spine, 8=thorax, 9=nose, 10=head,
# 11=L_shoulder,12=L_elbow,13=L_wrist,
# 14=R_shoulder,15=R_elbow,16=R_wrist
H36M_TO_BONE: dict[int, int] = {
0: 0, # hip ctr β†’ Hips
7: 1, # spine β†’ Spine
8: 2, # thorax β†’ Chest
# 9 (nose) β†’ Neck [computed as lerp]
10: 4, # head β†’ Head
11: 5, # L shoulder β†’ LeftUpperArm
12: 6, # L elbow β†’ LeftLowerArm
13: 7, # L wrist β†’ LeftHand
14: 8, # R shoulder β†’ RightUpperArm
15: 9, # R elbow β†’ RightLowerArm
16: 10, # R wrist β†’ RightHand
4: 11, # L hip β†’ LeftUpperLeg
5: 12, # L knee β†’ LeftLowerLeg
6: 13, # L ankle β†’ LeftFoot
1: 14, # R hip β†’ RightUpperLeg
2: 15, # R knee β†’ RightLowerLeg
3: 16, # R ankle β†’ RightFoot
}
# ── COCO-17 β†’ H36M-17 (2-D) ──────────────────────────────────────────────────
# COCO: 0=nose,1=L_eye,2=R_eye,3=L_ear,4=R_ear,5=L_sh,6=R_sh,
# 7=L_el,8=R_el,9=L_wr,10=R_wr,11=L_hip,12=R_hip,
# 13=L_kn,14=R_kn,15=L_an,16=R_an
def coco_to_h36m_2d(coco: np.ndarray) -> np.ndarray:
"""
(T, 17, 2) COCO pixel coords β†’ (T, 17, 2) H36M-layout pixel coords.
"""
T = coco.shape[0]
h = np.zeros((T, 17, 2), dtype=np.float32)
# Direct copies
h[:, 1, :] = coco[:, 12, :] # R_hip
h[:, 2, :] = coco[:, 14, :] # R_knee
h[:, 3, :] = coco[:, 16, :] # R_ankle
h[:, 4, :] = coco[:, 11, :] # L_hip
h[:, 5, :] = coco[:, 13, :] # L_knee
h[:, 6, :] = coco[:, 15, :] # L_ankle
h[:, 9, :] = coco[:, 0, :] # nose β†’ H36M nose slot
h[:, 11, :] = coco[:, 5, :] # L_shoulder
h[:, 12, :] = coco[:, 7, :] # L_elbow
h[:, 13, :] = coco[:, 9, :] # L_wrist
h[:, 14, :] = coco[:, 6, :] # R_shoulder
h[:, 15, :] = coco[:, 8, :] # R_elbow
h[:, 16, :] = coco[:, 10, :] # R_wrist
# Derived midpoints
h[:, 0, :] = (coco[:, 11, :] + coco[:, 12, :]) * 0.5 # hip centre
h[:, 8, :] = (coco[:, 5, :] + coco[:, 6, :]) * 0.5 # thorax (shoulder mid)
h[:, 7, :] = (h[:, 0, :] + h[:, 8, :]) * 0.5 # spine midpoint
h[:, 10, :] = (coco[:, 3, :] + coco[:, 4, :]) * 0.5 # head (ear mid)
return h
def h36m_to_bone_positions(h36m_world: np.ndarray) -> np.ndarray:
"""
Map H36M (T, 17, 3) world positions β†’ our bone layout (T, N_BONES, 3).
Joint 3 (Neck) is linearly interpolated between Chest (bone 2) and Head (bone 4).
All other bones have a direct H36M source.
"""
T = h36m_world.shape[0]
out = np.zeros((T, N_BONES, 3), dtype=np.float32)
for h36m_j, bone_j in H36M_TO_BONE.items():
out[:, bone_j, :] = h36m_world[:, h36m_j, :]
# Neck = 60 % Chest + 40 % Head
out[:, 3, :] = out[:, 2, :] * 0.60 + out[:, 4, :] * 0.40
return out
def compute_local_positions(world: np.ndarray) -> np.ndarray:
"""
(T, N_BONES, 3) world positions β†’ (T, N_BONES, 3) parent-relative positions.
Root (Hips) keeps its world position.
"""
local = np.empty_like(world)
local[:, 0, :] = world[:, 0, :]
for j in range(1, N_BONES):
local[:, j, :] = world[:, j, :] - world[:, BONE_PARENT[j], :]
return local