Pose Tracking¶
Task reference for pose estimation and motion capture in MGT-python: rendering skeleton video, extracting landmark trajectories as numpy arrays, deriving motion signals, segmenting postures, and reading mocap files. The full course narrative, including worked examples and the reasoning behind the defaults, is in chapter 10 of the wiki.
Pose extraction¶
pip install musicalgestures[pose] # MediaPipe backend (default)
import musicalgestures as mg
mv = mg.MgVideo('dance.avi')
mv.pose().show() # MediaPipe: 33 landmarks, single person
mv.pose(model='body_25') # OpenPose: multi-person, needs opencv-python<5
traj = mg.extract_pose_landmarks('dance.avi', fps=30, width=640)
pose() returns an MgVideo with the skeleton overlaid and writes a keypoint data file
(CSV/TSV/TXT/C3D via data_format); repeat calls with the same model and threshold
reuse cached keypoints. extract_pose_landmarks() returns a dict whose landmarks array
is (F, 33, 3) as (x_px, y_px, visibility), with NaN rows for undetected frames and a
detection_rate field; it requires MediaPipe. Without MediaPipe, pose() falls back to
OpenPose body_25, and on OpenCV 5 the OpenPose backends raise MgDependencyError
because the Caffe importer is gone.
Visualisations¶
mv.pose(style='skeleton', overlay=False, background='white', marker_history=10)
mv.pose_waterfall(style='trajectories').show() # (x, time, y) waterfall
mv.pose_segments(n_bins=24).show() # circular statistics per body segment
All return MgVideo or MgFigure objects and reuse cached keypoints from a prior
pose() call. pose() also saves an average-pose image and a marker-trajectories image
by default (save_average_pose, save_trajectories), each with a companion stats CSV.
Centring and normalising¶
mv.pose_center().show() # centre pose data on its global centroid (mccenter port)
mv.pose_distance().show() # per-marker cumulative distance (mccumdist port)
from musicalgestures import normalise_poses
normalised = normalise_poses(traj['landmarks']) # (F, L, 2), pelvis-centred, torso-scaled
normalise_poses() is detector-agnostic: it recognises MediaPipe-33, YOLO/COCO-17 and
the OpenPose skeletons by landmark count, and takes explicit anchors for anything else.
Frames the detector missed come back as NaN, never interpolated.
Posegram and pose timeline¶
mv.posegram() # one landmark per row, coloured by speed
mv.posegram_spatial() # image position on the y-axis, motiongram-like
mv.pose_timeline(view='strip') # postures at regular instants; 'room', 'bands'
All return MgFigure. The posegram shows landmark speed, so a held posture is dark; the
timeline's bands view shows joint angles, so a held posture is a flat band. See the
_posegram and
_posetimeline API pages.
Derived signals¶
wrists = traj['landmarks'][:, [15, 16], :2]
conf = traj['landmarks'][:, [15, 16], 2]
speed = mg.limb_speed_from_landmarks(wrists, conf, traj['fps']) # (F,) px/s
impacts = mg.impact_events(wrists, traj['fps']) # {'time', 'magnitude', ...}
qom, env, fs = mg.normalized_qom(traj['landmarks'][..., :2], traj['fps']) # body-lengths/s
All are numpy-only and NaN-aware, so they work without MediaPipe installed and on
landmarks from any source. A limb-speed peak precedes the actual contact moment, and
impact_events() also responds to the backswing, so treat impacts as candidates and
validate against another modality.
Posture segmentation¶
postures = mv.postures_from_pose() # list of Posture spans (held configurations)
from musicalgestures._postures import key_postures, match_postures
groups = key_postures(postures, radius=0.2) # recurring postures, longest-held first
match_postures(postures, template, 'T-shape') # label matches; a pose by example
segment_postures() judges stillness body-relatively, so a held shape carried across the
frame is one posture; a recording in which the body never stops holds no postures. Poses
are only ever proposed as labels on postures, never detected; the
Concepts page carries the position/posture/pose scheme.
Mocap I/O¶
names, data, fs = mg.read_qtm_tsv('take01.tsv') # data: (T, M, 3), zeros -> NaN
result = mg.compare_modality_envelopes(env_video, env_mocap, fs_a, fs_b)
result['r'], result['n'] # Pearson r, overlapping seconds
compare_modality_envelopes() takes precomputed 1-D envelopes, bins to a 1 s grid, and
returns NaN for r below 3 s of overlap. The per-second step is integer-rounded, so
29.97 fps material drifts over long signals: use it for validation, not alignment. These
functions live in micromotion and are
re-exported here.
Further¶
- Chapter 10 of the wiki—the course chapter this page summarises
_posetools—landmark extraction and derived signals_postures—posture segmentation and matching_posegram·_posetimeline—the pose pictures_pose·_pose_visualize—theMgVideo.pose()pipeline_mocap—mocap I/O re-exports- Video Analysis—full
MgVideo.pose()signature