| """ |
| 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 |
|
|
| |
|
|
| BONE_NAMES: list[str] = [ |
| "Hips", |
| "Spine", |
| "Chest", |
| "Neck", |
| "Head", |
| "LeftUpperArm", |
| "LeftLowerArm", |
| "LeftHand", |
| "RightUpperArm", |
| "RightLowerArm", |
| "RightHand", |
| "LeftUpperLeg", |
| "LeftLowerLeg", |
| "LeftFoot", |
| "RightUpperLeg", |
| "RightLowerLeg", |
| "RightFoot", |
| ] |
|
|
| 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, |
| 1, |
| 2, |
| 3, |
| 2, |
| 5, |
| 6, |
| 2, |
| 8, |
| 9, |
| 0, |
| 11, |
| 12, |
| 0, |
| 14, |
| 15, |
| ] |
|
|
| |
| 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_WORLD: np.ndarray = np.array([ |
| [ 0.000, 1.000, 0.000], |
| [ 0.000, 1.100, 0.000], |
| [ 0.000, 1.300, 0.000], |
| [ 0.000, 1.450, 0.000], |
| [ 0.000, 1.620, 0.000], |
| [-0.200, 1.350, 0.000], |
| [-0.450, 1.350, 0.000], |
| [-0.650, 1.350, 0.000], |
| [ 0.200, 1.350, 0.000], |
| [ 0.450, 1.350, 0.000], |
| [ 0.650, 1.350, 0.000], |
| [-0.100, 0.850, 0.000], |
| [-0.100, 0.450, 0.000], |
| [-0.100, 0.050, 0.000], |
| [ 0.100, 0.850, 0.000], |
| [ 0.100, 0.450, 0.000], |
| [ 0.100, 0.050, 0.000], |
| ], dtype=np.float32) |
|
|
| |
| 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_TO_BONE: dict[int, int] = { |
| 0: 0, |
| 7: 1, |
| 8: 2, |
| |
| 10: 4, |
| 11: 5, |
| 12: 6, |
| 13: 7, |
| 14: 8, |
| 15: 9, |
| 16: 10, |
| 4: 11, |
| 5: 12, |
| 6: 13, |
| 1: 14, |
| 2: 15, |
| 3: 16, |
| } |
|
|
| |
| |
| |
| |
|
|
| 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) |
|
|
| |
| h[:, 1, :] = coco[:, 12, :] |
| h[:, 2, :] = coco[:, 14, :] |
| h[:, 3, :] = coco[:, 16, :] |
| h[:, 4, :] = coco[:, 11, :] |
| h[:, 5, :] = coco[:, 13, :] |
| h[:, 6, :] = coco[:, 15, :] |
| h[:, 9, :] = coco[:, 0, :] |
| h[:, 11, :] = coco[:, 5, :] |
| h[:, 12, :] = coco[:, 7, :] |
| h[:, 13, :] = coco[:, 9, :] |
| h[:, 14, :] = coco[:, 6, :] |
| h[:, 15, :] = coco[:, 8, :] |
| h[:, 16, :] = coco[:, 10, :] |
|
|
| |
| h[:, 0, :] = (coco[:, 11, :] + coco[:, 12, :]) * 0.5 |
| h[:, 8, :] = (coco[:, 5, :] + coco[:, 6, :]) * 0.5 |
| h[:, 7, :] = (h[:, 0, :] + h[:, 8, :]) * 0.5 |
| h[:, 10, :] = (coco[:, 3, :] + coco[:, 4, :]) * 0.5 |
|
|
| 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, :] |
|
|
| |
| 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 |
|
|