Datasets & Benchmarks
Human3.6M
The laboratory motion-capture dataset of 3.6 million 3D human poses with synchronized video, depth and body scans, its capture record, licence, split and evaluation server, the paper's own error measures, and how its headline numbers are aggregated.
intermediate
Human3.6M is a dataset of 3.6 million 3D human poses recorded with a marker-based motion-capture system, together with synchronized video from four calibrated cameras, time-of-flight depth, laser body scans of the actors, silhouettes and bounding boxes. It was built by Catalin Ionescu, Dragos Papava, Vlad Olaru and Cristian Sminchisescu at the Institute of Mathematics of the Romanian Academy, with affiliations at the University of Bonn and Lund University, and described in the IEEE Transactions on Pattern Analysis and Machine Intelligence in 2014 (vol. 36, no. 7, July 2014, as the dataset page gives it); the licence also requires citing the authors’ ICCV 2011 paper on latent structured models (Ionescu, Li and Sminchisescu). Eleven professional actors perform 15 everyday scenarios such as giving directions, eating, phoning, sitting down and walking a dog. The name is shortened to H36M, as in 3d-pose-baseline’s code and on the ranking page, or to H3.6M, as in Martinez et al. (2017).
3D Human Pose Estimation describes Human3.6M as a benchmark in outline: the subject split used in practice, frame sampling, MPJPE and its Procrustes-aligned variant, and the protocol numbers. This article covers the dataset itself: what was captured and how, the counts and where the sources disagree on them, the licence and access, the evaluation server and the ECCV 2018 challenge, the paper’s four error measures, and how a published Human3.6M number is aggregated from per-frame errors. The worked example computes these aggregations on a synthetic skeleton, with a port of the community’s reference evaluation code.
Purpose
Ionescu et al. (2014, §1) motivate the dataset by scale and realism; section, equation, figure and table numbers in this article follow the authors’ copy of the paper linked from the dataset page. HumanEva, the earlier synchronized benchmark, holds about 40,000 different poses and was designed mainly for evaluating algorithms; the CMU motion-capture collection is diverse but not synchronized with images. The authors wanted data large enough to train models for “realistic environments”, so they recorded poses that are common in urban and office scenes, gave the actors scenarios with general instructions, and left them freedom to improvise. They compare their goal with the proprietary training set of about a million poses behind the Kinect system, and aim to provide something similar in the harder setting of intensity images, or with their time-of-flight data in a Kinect-like setting.
The paper’s experiments support the scale argument with its own baselines: the caption of Fig. 9, which plots test error against training-set size, says that larger training sets benefit all models tried, and the authors report that their best large-scale model, LinKDE, reduces its error by roughly 20% when the training set grows by two orders of magnitude (§5.2).
Dataset Size
The capture took place in a laboratory area of about 6 m × 5 m, with an effective capture space of about 4 m × 3 m in which subjects were fully visible to all video cameras (§2.1). Fig. 2(b) of the paper lists the sensors:
| System | Sensors | Resolution | Rate | Synchronization |
|---|---|---|---|---|
| Motion capture | 10 × Vicon T40 | 4 megapixels | 200 Hz | hardware |
| Video | 4 × Basler piA1000 | 1000 × 1000 | 50 Hz | hardware |
| Time of flight | 1 × Mesa SR4000 | 176 × 144 | 25 Hz | software |
| Body scanner | Vitus Smart LC3 (3 lasers) | 7 dots/cm³ (point density) | — | — |
The four video cameras stand in the corners of the capture space; the time-of-flight sensor is on top of one of them (§2.1; the dataset page says “next to” and “near” one). The dataset page’s Time-of-Flight section reports that the sensor’s own clock was found to run at the desired rate “in a great majority of cases” and that code is provided to correct desynchronizations.
Counts. The paper’s Fig. 2(a) gives the number of 3D poses per scenario in training, validation and testing; the caption says the number of video frames equals the number of poses. The columns total 1,464,216 training, 646,180 validation and 1,467,684 test poses, of which 105,576 test poses belong to a “Miscellaneous” scenario with no training or validation data. The three columns sum to 3,578,080, which the example below recomputes from the transcribed table. The abstract describes “3.6Million accurate 3D Human poses”, §1 “over 3.6 million different human poses”, and the dataset page “over 3.6 Million” video frames, against the table’s total of 3,578,080. The scenario count also varies: the paper’s text and the dataset page’s scenario list name 15, Fig. 2(a) has 16 rows with Miscellaneous, and the dataset page’s overview says “17 scenarios”.
The TOF frame count is the table entry divided by 8, from one sensor at 25 Hz (Fig. 2(a) caption). The paper and dataset page also describe ways to enlarge the monocular training set: rotating and translating poses so that all four cameras share one coordinate system (four-fold), and mirroring images and poses, which the paper says can yield 7 million images with 3D poses (§2.2).
Mixed reality. A separate test set inserts rendered, clothed 3D characters, animated with poses from the laboratory test set, into real videos filmed with moving cameras, with the camera motion and ground plane estimated so that insertions and occlusions are geometrically correct. The paper gives 5 dynamic backgrounds and 7,466 frames, of which 1,270 contain occlusion (§2.2); the dataset page gives 10,350 examples and the same 1,270 occluded frames.
Classes
There are no object classes. The units of organization are subjects and scenarios. The eleven actors, 5 female and 6 male, were chosen to span a body mass index from 17 to 29 (§2.2; the dataset page says “6 male and 5 female”). The scenarios are grouped by Fig. 2(a) into upper-body movement (Directions, Discussion), full-body upright variations (Greeting, Posing, Purchases, Taking Photo, Waiting), walking variations (Walking, Walking Dog, Walking Pair), variations while seated on a chair (Eating, Phone Talk, Sitting, Smoking) and sitting on the floor (Sitting Down), plus Miscellaneous for testing. Names differ between sources: the paper’s Table 3 calls Purchases “Buying”, the dataset page lists “Making purchases” and “Walking together”, and 3d-pose-baseline’s define_actions uses “Phoning”, “Photo” and “WalkTogether”. The same code skips files starting with “SittingDown” when it loads “Sitting”, because the file names share a prefix (data_utils.py at commit 666080d).
Annotation Types
- 3D poses on a 32-joint skeleton, in two parametrizations with the same joints: relative 3D joint positions (R3DJP), computed by forward kinematics from the joint angles of Vicon’s skeleton fitting, and a kinematic representation (KR) of relative joint angles (§2.2). The mixed-reality section names the angles as Euler ZXY angles. In R3DJP the root joint, roughly at the pelvis, is the origin. The paper’s experiments, and the community, reduce the skeleton to 17 joints, down to the wrists and ankles (§5.2); the dataset page says that testing reduces the joints to “the relevant ones”.
- 2D poses, projected with the camera parameters (dataset page).
- Camera calibration: because the Vicon calibration does not estimate lens distortion, the authors labelled 30 reflective markers on the capture surface in each camera and fitted models with second-order radial distortion, reaching a mean re-projection error of 0.17 pixels (§2.2).
- Silhouettes: figure–ground segmentation from per-camera background models (mixtures of Gaussians on RGB, HSV and gradient channels) in a graph cut whose feature weights were learned on manually labelled silhouettes (background subtraction); bounding boxes fitted around the projected skeleton (§2.2).
- Time-of-flight range images, body scans as meshes in Wavefront OBJ format, and, on the dataset page, pixel-level labels of 24 body parts and precomputed image descriptors.
- Body shape: the scans are offered for evaluating body-shape estimation (§2.2), which parametric human body models pursue.
Splits
The paper reserves 7 subjects, 3 female and 4 male, for training and validation and 4 subjects, 2 female and 2 male, for testing (§2.2); the Fig. 2(a) caption splits the seven into 5 for training (2 female, 3 male) and 2 for validation (1 female, 1 male), without naming which subjects are which. Each scenario was captured in two trials per subject, used for training and validation in the subject-specific setting, and two further motions per subject were reserved for testing (§5.2). The paper’s own experiments use three set-ups: subject-specific models, activity-specific models trained on the seven subjects and tested on the four, and a general model over all activities.
For privacy, the images of one test subject, S10, are withheld; the other data of S10, including silhouettes, boxes and descriptors, are released, and the evaluation server scores test results with and without S10 (§5.2). The ranking page accordingly offers test sets “H36M” and “H36M_NOS10”. Only the training and validation subjects have public 3D poses, as 3D Human Pose Estimation reports, so published work trains on S1, S5, S6, S7 and S8 and tests on S9 and S11, as 3D Human Pose Estimation explains with the papers that use and question this practice. 3d-pose-baseline encodes the same lists (TRAIN_SUBJECTS = [1,5,6,7,8], TEST_SUBJECTS = [9,11] in data_utils.py), and Martinez et al. (2017, §4) call it “the standard protocol”.
Tasks
The dataset was designed for monocular and multi-view 3D human pose estimation from images, and it also serves 2D human pose estimation, depth-based pose estimation from the TOF data, body-shape estimation from the scans, and person segmentation from the silhouettes. Its pose sequences are used for motion modelling: video prediction uses Human3.6M for structure-based prediction, and the 3D skeleton sequences match the input of skeleton-based action recognition. Human3.6M has no 2D in-the-wild component; MPII Human Pose, which Martinez et al. (2017) call a standard dataset for 2D pose estimation, covers images in the wild.
Evaluation
The paper’s measures
Ionescu et al. (2014, §4, “Evaluation and Error Measures”) propose four measures and provide support for all of them, since each has advantages and disadvantages. Let be the predicted position of joint in frame , the ground truth, and the number of joints of skeleton (17 in the paper’s experiments, with the root at the origin of the prediction coordinate system, §5.2).
MPJPE (Eq. 8), the mean per joint position error of a frame:
and “for a set of frames the error is the average over the MPJPEs of all frames”. It is reported in millimetres for 3D and in pixels for 2D. Methods that predict joint angles can have them converted to positions by forward kinematics on the test subject’s skeleton. Unlike mean squared error, the distance is not squared, so a joint’s contribution grows linearly with its error.
UMPJPE. Because many methods find limb lengths hard to predict, the paper proposes a universal MPJPE that “considers the same limb lengths for all subjects, by means of a normalization process”, on a universal skeleton with one set of limb lengths. The paper does not give the normalization as an equation. The dataset page describes the same idea in practice: the size problem of R3DJP “is obviated” by providing the same skeleton for all subjects, including those in testing, if needed.
MPJAE (Eq. 9), for methods that predict joint angles, averages angle differences, each taken modulo ±180°:
The paper calls it unintuitive, because a 30° error at the elbow misplaces only the wrist while 30° of global rotation misplaces every joint. Its mixed-reality results (Table 5) use MPJAE without the global rotation.
MPJLE (Eq. 10), the mean per joint localization error at a perceptual tolerance , is the fraction of joints whose error is at least :
It addresses two weaknesses the authors see in MPJPE and MPJAE: one bad joint has unbounded influence, and imperceptible errors count fully. The paper suggests fixing at a perceptual threshold, or integrating over an interval such as [0, 200] mm as an estimate of the average error. Fig. 10 plots it for LinKDE on “Eating”, which places on average 14 of 17 joints within 150 mm.
Evaluation server and test subsets
The test set’s 3D poses are held by the authors, and results are scored by an evaluation server (§5.2; dataset page, “performance evaluation on withheld test set”). The news page records that from December 2017 results can also be tested on a subset of selected actions, as described in the dataset’s README, which was not read. The ranking page, as of November 2026, offers rankings on CHALL_H80K, H80K, H36M_NOS10, H36M, MR and H80K_PartLabels in MPJPE or MPJAE, with columns for the 15 scenarios, A1 to A15, and an average. The authors’ code is available after login (dataset page) and was not read, and the public pages read for this article do not say how the server averages.
How a published number is aggregated
The paper defines the dataset-level MPJPE as the average over frames. Papers that use the S9 and S11 protocol instead report one number per action and an “Avg” column. 3d-pose-baseline, the MIT-licensed code of Martinez et al. (2017), shows how such a number is computed. Read at commit 666080d:
- Per action, then unweighted. With
--evaluateActionWise,predict_3dpose.pyloops over the 15 actions ofdefine_actions, computes each action’s error withevaluate_batchesover all its test frames, and printscum_err / len(actions)as “Average”: a mean over actions in which every action has weight 1/15, whatever its number of frames. - The default is pooled.
evaluateActionWisedefaults to False. Without the flag, the script evaluates all test frames together and prints a single “Val error avg”, a mean over frames like the paper’s. - 17 joints with the root. The network predicts 16 joints (the root’s dimensions are removed in
normalization_stats), butevaluate_batchesadds the root’s three dimensions back (dtu3d = np.hstack((np.arange(3), dim_to_use_3d))) before computing distances, and averages over all 17. After root centring inpostprocess_3d, the root’s error is zero, so the mean is 16/17 of the mean over the other 16 joints. In our reading, Eq. 8 with the paper’s 17 joints and the root at the origin averages a zero as well. - Every frame, every camera, minus a remainder. The 3D sequences are loaded without subsampling, and each is projected into the four cameras (
project_to_cameras), so each test frame enters the evaluation once per camera, consistent with Martinez et al.’s description of an error “across all joints and cameras”.get_all_batchesthen drops the last examples so that the test set divides into batches of 64, andevaluate_batchesaverages what remains.
The difference between the two aggregations is an exact identity. If action has frames and mean error , with and actions,
which is times the covariance, over actions, between an action’s share of the frames and its error. The pooled number is lower when the longer actions are the easier ones, and higher when they are the harder ones.
Example: aggregating Human3.6M numbers
The example has two parts. Part 0 does arithmetic on published tables. It checks the transcribed Fig. 2(a) counts against their printed totals, recomputes the paper’s Table 3 scenario errors as an unweighted mean and as a mean weighted by Fig. 2(a)’s test counts, and compares the “Avg” columns of Martinez et al.’s Table 2 and of the ECCV 2018 challenge ranking with the unweighted mean of their per-action values.
Part A builds a synthetic test set with known ground truth. A 17-joint stick skeleton in 3d-pose-baseline’s joint order is posed by three Euler angles per joint (here in the parent’s frame, with up and positions in millimetres; the motion-capture angles are Euler ZXY, our convention is our own). Each of the 15 actions has smooth sinusoidal motions with its own centres, amplitudes and frequencies; the three walking actions walk in a circle at 36° per second, so their heading passes through ±180°. Two subjects, A and B, have limbs scaled differently (legs by 0.94 and 1.07, arms by 0.92 and 1.09, torso by 0.97 and 1.04), and each action has two trials per subject at 50 Hz. The number of frames per action is Fig. 2(a)’s validation count divided by 10, so the actions keep the paper’s proportions; the paper does not name its two validation subjects. No image is involved.
The “predictor” is an error model, not a trained network. It knows the true angles but uses the average of the two subjects’ bones, a limb-length bias, and adds Gaussian noise to the 16 non-root joints, scaled per action so that the noise alone has an expected joint error of one quarter of LinKDE’s (bounding box) error for that scenario in the paper’s Table 3. A separate angle predictor adds 5° of Gaussian noise to every true angle. The action lengths and noise levels are design choices made before any run, taken from the paper’s tables so that the relation between action length and error is sourced rather than tuned. UMPJPE uses our own analogue of the paper’s normalization, since the paper describes it only in words: each bone of prediction and ground truth keeps its direction, gets the universal skeleton’s length (the unscaled base skeleton), and the skeleton is rebuilt from the root. The UMPJPE outcomes test this analogue, not the paper’s server.
The script ports 3d-pose-baseline’s evaluate_batches and the truncation in get_all_batches, with the TensorFlow model replaced by the synthetic predictions, and carries the full MIT licence text. The synthetic test set has one view, so each frame enters once rather than once per camera. It runs with NumPy 2.3.5 (Python 3.13) in about 2 seconds on one thread.
import os
os.environ["OPENBLAS_NUM_THREADS"] = "1" # one BLAS thread: identical sums on every run
os.environ["OMP_NUM_THREADS"] = "1"
import numpy as np
# Part 0: arithmetic on published tables.
# Ionescu et al. (2014), Fig. 2(a): (train, validation, test) poses per scenario,
# under 3d-pose-baseline's action names; Miscellaneous has test poses only.
FIG2A = {"Directions": (83856, 50808, 114080), "Discussion": (154392, 68640, 140764),
"Eating": (109360, 39372, 97192), "Greeting": (69984, 33096, 84980),
"Phoning": (132612, 39308, 92036), "Photo": (67152, 38216, 89608),
"Posing": (70948, 25800, 85912), "Purchases": (49096, 33268, 48496),
"Sitting": (110228, 46520, 89616), "SittingDown": (112172, 50384, 105396),
"Smoking": (138028, 50776, 85520), "Waiting": (98232, 54928, 123432),
"WalkDog": (77068, 30648, 59032), "Walking": (114468, 47540, 93320),
"WalkTogether": (76620, 36876, 52724)}
ACTIONS = list(FIG2A) # the order of define_actions() in 3d-pose-baseline
MISC_TEST = 105576
cols = np.array([FIG2A[a] for a in ACTIONS]).sum(0)
cols[2] += MISC_TEST
print("Fig. 2(a) column sums:", cols.tolist(), "printed: [1464216, 646180, 1467684];",
"all three:", int(cols.sum()))
# Ionescu et al. (2014), Table 3: activity-specific models, test set incl. S10 (mm)
T3 = {"kNN BB": [154.23, 151.18, 136.23, 165.94, 147.51, 234.05, 175.58, 180.82, 194.09, 209.06, 161.22, 176.16, 239.38, 167.00, 180.91],
"kNN BS": [166.28, 163.68, 132.19, 188.33, 145.89, 247.57, 199.89, 174.38, 174.67, 237.05, 169.41, 193.78, 216.53, 158.27, 189.80],
"KRR BB": [118.96, 116.77, 109.65, 128.51, 123.05, 182.50, 136.23, 153.55, 176.90, 184.58, 120.19, 139.66, 183.27, 129.13, 143.19],
"KRR BS": [130.04, 124.96, 118.60, 140.73, 125.35, 197.21, 152.21, 157.45, 159.30, 213.72, 130.47, 150.39, 175.34, 119.28, 150.43],
"LinKRR BB": [123.67, 121.23, 116.09, 136.77, 130.60, 194.32, 142.40, 165.14, 180.69, 204.62, 128.62, 144.54, 191.92, 133.49, 147.87],
"LinKRR BS": [136.07, 132.33, 123.90, 149.99, 132.83, 208.14, 158.98, 162.36, 168.12, 231.57, 139.88, 157.92, 185.64, 126.90, 156.33],
"LinKDE BB": [115.79, 113.27, 99.52, 128.80, 113.44, 183.09, 131.01, 144.89, 160.92, 172.98, 114.00, 138.95, 180.56, 131.15, 146.14],
"LinKDE BS": [124.19, 117.44, 93.01, 138.90, 111.40, 197.13, 145.43, 136.94, 139.29, 203.10, 118.37, 146.30, 166.10, 115.28, 153.59]}
n_test = np.array([FIG2A[a][2] for a in ACTIONS], float)
print("Table 3 (mm) mean of actions weighted by test poses")
for k, v in T3.items():
v = np.array(v)
print(f" {k:10s} {v.mean():10.3f} {(v * n_test).sum() / n_test.sum():14.3f}")
# Martinez et al. (2017), Table 2, protocol #1: 15 actions and the printed "Avg"
M2 = {"LinKDE (SA)": ([132.7, 183.6, 132.3, 164.4, 162.1, 205.9, 150.6, 171.3, 151.6, 243.0, 162.1, 170.7, 177.1, 96.6, 127.9], 162.1),
"Tekin [46] (SA)": ([102.4, 147.2, 88.8, 125.3, 118.0, 182.7, 112.4, 129.2, 138.9, 224.9, 118.4, 138.8, 126.3, 55.1, 65.8], 125.0),
"Zhou [56] (MA)": ([87.4, 109.3, 87.1, 103.2, 116.2, 143.3, 106.9, 99.8, 124.5, 199.2, 107.4, 118.1, 114.2, 79.4, 97.7], 113.0),
"Du (SA)": ([85.1, 112.7, 104.9, 122.1, 139.1, 135.9, 105.9, 166.2, 117.5, 226.9, 120.0, 117.7, 137.4, 99.3, 106.5], 126.5),
"Park (SA)": ([100.3, 116.2, 90.0, 116.5, 115.3, 149.5, 117.6, 106.9, 137.2, 190.8, 105.8, 125.1, 131.9, 62.6, 96.2], 117.3),
"Zhou [54] (MA)": ([91.8, 102.4, 96.7, 98.8, 113.4, 125.2, 90.0, 93.8, 132.2, 159.0, 107.0, 94.4, 126.0, 79.0, 99.0], 107.3),
"Pavlakos (MA)": ([67.4, 71.9, 66.7, 69.1, 72.0, 77.0, 65.0, 68.3, 83.7, 96.5, 71.7, 65.8, 74.9, 59.1, 63.2], 71.9),
"Ours SH (SA)": ([61.6, 73.4, 63.3, 58.3, 91.8, 93.6, 66.3, 62.0, 91.7, 109.4, 75.7, 86.5, 67.2, 51.2, 52.3], 73.6),
"Ours SH (MA)": ([53.3, 60.8, 62.9, 62.7, 86.4, 82.4, 57.8, 58.7, 81.9, 99.8, 69.1, 63.9, 67.1, 50.9, 54.8], 67.5),
"Ours SH FT (MA)": ([51.8, 56.2, 58.1, 59.0, 69.5, 78.4, 55.2, 58.1, 74.0, 94.6, 62.3, 59.1, 65.1, 49.5, 52.4], 62.9),
"Ours GT (MA)": ([37.7, 44.4, 40.3, 42.1, 48.2, 54.9, 44.4, 42.1, 54.6, 58.0, 45.1, 46.4, 47.6, 36.4, 40.4], 45.5)}
print("Martinez Table 2 mean of 15 printed Avg |diff|")
for k, (v, avg) in M2.items():
print(f" {k:16s} {np.mean(v):10.3f} {avg:10.1f} {abs(np.mean(v) - avg):8.3f}")
# Human3.6M ranking page, ECCV 2018 challenge (CHALL_H80K, MPJPE): A1..A15 and Avg
CH = [[38, 40, 43, 40, 43, 40, 47, 58, 64, 43, 48, 42, 36, 50, 38, 45],
[38, 43, 46, 41, 46, 40, 49, 65, 73, 48, 49, 43, 38, 52, 38, 47],
[44, 46, 50, 47, 56, 47, 52, 63, 70, 54, 54, 48, 46, 58, 46, 52],
[53, 54, 54, 52, 56, 55, 58, 70, 78, 60, 59, 57, 48, 61, 56, 58],
[53, 52, 52, 53, 55, 55, 54, 71, 84, 56, 60, 58, 51, 64, 57, 58],
[52, 56, 55, 51, 57, 53, 64, 73, 81, 61, 60, 57, 49, 61, 53, 59],
[51, 53, 58, 52, 64, 53, 67, 94, 132, 65, 64, 57, 53, 67, 53, 66],
[58, 59, 64, 62, 65, 60, 68, 77, 92, 65, 68, 62, 60, 70, 59, 66]]
print("challenge rows: |mean of A1-A15 - Avg| =",
[f"{abs(np.mean(r[:15]) - r[15]):.3f}" for r in CH])
# Part A: a synthetic Human3.6M-like test set.
# 17 joints in 3d-pose-baseline's order: Hip, RHip, RKnee, RFoot, LHip, LKnee, LFoot,
# Spine, Thorax, Neck/Nose, Head, LShoulder, LElbow, LWrist, RShoulder, RElbow, RWrist.
PARENT = [-1, 0, 1, 2, 0, 4, 5, 0, 7, 8, 9, 8, 11, 12, 8, 14, 15]
OFF = np.array([[0, 0, 0], [-130, 0, 0], [0, -450, 0], [0, -440, 0], [130, 0, 0], [0, -450, 0],
[0, -440, 0], [0, 230, 0], [0, 250, 0], [0, 110, 0], [0, 120, 0], [170, -30, 0],
[0, -280, 0], [0, -250, 0], [-170, -30, 0], [0, -280, 0], [0, -250, 0]], float) # mm, y up
GROUP = np.array([0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2]) # torso, legs, arms
SCALE = {"A": np.array([0.97, 0.94, 0.92]), "B": np.array([1.04, 1.07, 1.09])}
BONES = {s: OFF * SCALE[s][GROUP][:, None] for s in SCALE}
BONES["mean"] = 0.5 * (BONES["A"] + BONES["B"]) # what the predictor assumes
UNIVERSAL = OFF # our analogue of the universal skeleton
FPS = 50
def rot(axis, deg):
t = np.radians(deg); c, s = np.cos(t), np.sin(t)
R = np.zeros(t.shape + (3, 3)); R[..., 0, 0] = R[..., 1, 1] = R[..., 2, 2] = 1
i, j = {"x": (1, 2), "y": (2, 0), "z": (0, 1)}[axis]
R[..., i, i] = c; R[..., j, j] = c; R[..., i, j] = -s; R[..., j, i] = s
return R
def fk(angles, bones):
"""Forward kinematics: angles (F, 17, 3) in degrees, applied as R = Ry Rx Rz
in each joint's parent frame -> joint positions (F, 17, 3), root at the origin."""
R = rot("y", angles[..., 0]) @ rot("x", angles[..., 1]) @ rot("z", angles[..., 2])
G = np.zeros(R.shape); P = np.zeros(angles.shape)
for j, p in enumerate(PARENT):
if p < 0:
G[:, j] = R[:, j]
else:
P[:, j] = P[:, p] + G[:, p] @ bones[j]
G[:, j] = G[:, p] @ R[:, j]
return P
def wrap(d):
"""d mod +-180, into [-180, 180) degrees (Ionescu et al., Eq. 9)."""
return (d + 180.0) % 360.0 - 180.0
rng = np.random.default_rng(0)
WALKS = {"Walking", "WalkDog", "WalkTogether"}
LENGTH = {a: round(FIG2A[a][1] / 10) for a in ACTIONS} # validation column / 10
# position noise per coordinate: expected joint error = 1/4 of LinKDE BB in Table 3
SIGMA = {a: v / 4 / (2 * np.sqrt(2 / np.pi)) for a, v in zip(ACTIONS, T3["LinKDE BB"])}
data = []
for a in ACTIONS:
centre = rng.uniform(-20, 20, (17, 3)); amp = rng.uniform(5, 40, (17, 3))
freq = rng.uniform(0.2, 1.5, (17, 3))
centre[0] = [rng.uniform(-180, 180), rng.uniform(-5, 5), rng.uniform(-5, 5)]
amp[0] = [60, 5, 5] # root: heading swings by +-60 deg
q = LENGTH[a] // 4
lens = [q, q, q, LENGTH[a] - 3 * q]
for k, (subj, trial) in enumerate([("A", 1), ("A", 2), ("B", 1), ("B", 2)]):
t = np.arange(lens[k]) / FPS
ph = rng.uniform(0, 2 * np.pi, (17, 3))
ang = centre + amp * np.sin(2 * np.pi * freq * t[:, None, None] + ph)
if a in WALKS: # walking in a circle, 36 deg/s
ang[:, 0, 0] = centre[0, 0] + 36.0 * t + rng.uniform(0, 360)
ang = wrap(ang)
gt = fk(ang, BONES[subj])
bias = fk(ang, BONES["mean"]) # right angles, average bones
noise = rng.normal(0, SIGMA[a], gt.shape); noise[:, 0] = 0
ang_noise = rng.normal(0, 5.0, ang.shape) # an angle predictor, 5 deg noise
data.append(dict(subj=subj, a=a, trial=trial, ang=ang, gt=gt, bias=bias,
pred=bias + noise, ang_noise=ang_noise, ang_pred=wrap(ang + ang_noise)))
def joint_err(P, G):
return np.linalg.norm(P - G, axis=-1) # (F, 17) in mm
def per_action(f):
return {a: np.concatenate([f(d) for d in data if d["a"] == a]) for a in ACTIONS}
bone_dev = max(np.abs(np.linalg.norm(d["gt"][:, j] - d["gt"][:, PARENT[j]], axis=1)
- np.linalg.norm(BONES[d["subj"]][j])).max() for d in data for j in range(1, 17))
print(f"\ndata: {sum(len(d['gt']) for d in data)} frames at {FPS} Hz (expected {sum(LENGTH.values())}),"
f" bone-length deviation {bone_dev:.1e} mm, root at origin: {all(not d['gt'][:, 0].any() for d in data)}")
print("yaw wraps through +-180 per action:",
{a: sum(int((np.abs(np.diff(d['ang'][:, 0, 0])) > 180).sum()) for d in data if d['a'] == a)
for a in ACTIONS})
# A1. Pooled over frames (Ionescu et al.) against the mean of per-action means
fm = per_action(lambda d: joint_err(d["pred"], d["gt"]).mean(1))
n = np.array([len(fm[a]) for a in ACTIONS]); m = np.array([fm[a].mean() for a in ACTIONS])
pooled, action_mean = np.concatenate(list(fm.values())).mean(), m.mean()
w = n / n.sum()
identity = ((w - 1 / 15) * m).sum()
T = np.array(T3["LinKDE BB"])
print(f"\nA1 pooled over frames {pooled:.4f} mm, mean of 15 actions {action_mean:.4f} mm,"
f" difference {pooled - action_mean:.4f}")
print(f" sum (w_a - 1/15) m_a = {identity:.4f} |identity error| {abs(pooled - action_mean - identity):.1e};"
f" sign predicted from lengths and Table 3: {np.sign(((w - 1 / 15) * T).sum()):+.0f}")
# 3d-pose-baseline at 666080d, predict_3dpose.py evaluate_batches() and
# linear_model.py get_all_batches(), ported (TensorFlow model replaced by our arrays):
# concatenate the frames, drop the last n % 64 (batch size 64), average over frames
# and all 17 joints, root included. Copyright notice of the original:
#
# MIT License
#
# Copyright (c) 2016 Julieta Martinez, Rayat Hossain, Javier Romero
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
def evaluate_batches(dists, batch_size=64):
n_extra = len(dists) % batch_size
if n_extra > 0:
dists = dists[:-n_extra]
return np.mean(dists)
def dists_of(items):
return np.concatenate([joint_err(d["pred"], d["gt"]) for d in items])
by_key = sorted(data, key=lambda d: (d["subj"], d["a"], d["trial"])) # sorted dictionary keys
actionwise = np.mean([evaluate_batches(dists_of([d for d in by_key if d["a"] == a])) for a in ACTIONS])
default = evaluate_batches(dists_of(by_key))
print(f" 3d-pose-baseline: --evaluateActionWise 'Average' {actionwise:.4f} (untruncated {action_mean:.4f},"
f" change {actionwise - action_mean:+.4f}); default 'Val error avg' {default:.4f} (pooled {pooled:.4f})")
m16 = np.mean([dists_of([d for d in data if d["a"] == a])[:, 1:].mean() for a in ACTIONS])
print(f" root excluded (16 joints) {m16:.4f}; 17-joint/16-joint {action_mean / m16:.12f},"
f" |ratio - 16/17| {abs(action_mean / m16 - 16 / 17):.1e}")
# A2. Frame strides: every frame, 10 fps (every 5th), every 64th; all start offsets
print("\nA2 stride mean of actions: min over offsets max width (stride 1 inside?)")
for s in (1, 5, 64):
v = np.array([np.mean([np.concatenate([joint_err(d["pred"][o::s], d["gt"][o::s]).mean(1)
for d in data if d["a"] == a]).mean() for a in ACTIONS])
for o in range(s)])
print(f" {s:6d} {v.min():22.4f} {v.max():8.4f} {v.max() - v.min():6.4f}"
f" {v.min() <= action_mean <= v.max()}")
# A3. MPJPE against UMPJPE (our analogue: keep each bone's direction, give it the
# universal skeleton's length, rebuild from the root; prediction and truth alike)
def retarget(P, bones):
Q = np.zeros_like(P)
for j in range(1, 17):
v = P[:, j] - P[:, PARENT[j]]
Q[:, j] = Q[:, PARENT[j]] + v / np.linalg.norm(v, axis=1, keepdims=True) * np.linalg.norm(bones[j])
return Q
rows, check = [], 0.0
for a in ACTIONS:
ds = [d for d in data if d["a"] == a]
G, P, B, A = (np.concatenate([d[k] for d in ds]) for k in ("gt", "pred", "bias", "ang"))
Gu = retarget(G, UNIVERSAL)
check = max(check, np.abs(Gu - fk(A, UNIVERSAL)).max())
rows.append([joint_err(B, G).mean(), joint_err(retarget(B, UNIVERSAL), Gu).mean(),
joint_err(P, G).mean(), joint_err(retarget(P, UNIVERSAL), Gu).mean()])
rows = np.array(rows)
print(f"\nA3 retargeted truth vs FK on universal skeleton: max deviation {check:.1e} mm")
print(f" bias-only predictor: MPJPE {rows[:, 0].mean():.4f}, UMPJPE max over actions {rows[:, 1].max():.1e}")
print(f" full predictor: MPJPE {rows[:, 2].mean():.4f}, UMPJPE {rows[:, 3].mean():.4f};"
f" UMPJPE < MPJPE in {int((rows[:, 3] < rows[:, 2]).sum())}/15 actions,"
f" smallest margin {(rows[:, 2] - rows[:, 3]).min():.4f}")
# A4. MPJAE: 3 angles x 17 joints per frame, with and without the mod +-180 wrap
A0, AP, AN = (np.concatenate([d[k] for d in data]) for k in ("ang", "ang_pred", "ang_noise"))
wrapped, naive = np.abs(wrap(AP - A0)).mean(), np.abs(AP - A0).mean()
crossed = np.abs(AP - A0) > 180
print(f"\nA4 MPJAE wrapped {wrapped:.6f} deg (mean |noise| {np.abs(AN).mean():.6f},"
f" |diff| {abs(wrapped - np.abs(AN).mean()):.1e}); naive {naive:.6f} deg")
print(f" angles across +-180: {int(crossed.sum())} of {AN.size} ({crossed.mean():.6f});"
f" naive - wrapped {naive - wrapped:.6f}, sum(360 - 2|n|)/N {(360 - 2 * np.abs(AN[crossed])).sum() / AN.size:.6f}"
f" (|diff| {abs(naive - wrapped - (360 - 2 * np.abs(AN[crossed])).sum() / AN.size):.1e})")
# A5. MPJLE@t (Eq. 10): share of joints with error >= t, and its integral over [0, 200) mm
E = np.concatenate([joint_err(d["pred"], d["gt"]) for d in data])
print("\nA5 MPJLE@t:", ", ".join(f"t = {t} mm: {(E >= t).mean():.4f}" for t in (50, 100, 150)))
riemann = 0.1 * sum((E >= t).mean() for t in np.arange(0, 200, 0.1))
print(f" integral over t in [0, 200) mm, step 0.1: {riemann:.4f}; mean of min(error, 200): "
f"{np.minimum(E, 200).mean():.4f}; difference {riemann - np.minimum(E, 200).mean():.4f}")
Fig. 2(a) column sums: [1464216, 646180, 1467684] printed: [1464216, 646180, 1467684]; all three: 3578080
Table 3 (mm) mean of actions weighted by test poses
kNN BB 178.224 175.340
kNN BS 183.848 182.589
KRR BB 143.076 140.707
KRR BS 149.699 148.128
LinKRR BB 150.798 148.253
LinKRR BS 158.064 156.609
LinKDE BB 138.301 135.860
LinKDE BS 140.431 138.995
Martinez Table 2 mean of 15 printed Avg |diff|
LinKDE (SA) 162.127 162.1 0.027
Tekin [46] (SA) 124.947 125.0 0.053
Zhou [56] (MA) 112.913 113.0 0.087
Du (SA) 126.480 126.5 0.020
Park (SA) 117.460 117.3 0.160
Zhou [54] (MA) 107.247 107.3 0.053
Pavlakos (MA) 71.487 71.9 0.413
Ours SH (SA) 73.620 73.6 0.020
Ours SH (MA) 67.500 67.5 0.000
Ours SH FT (MA) 62.887 62.9 0.013
Ours GT (MA) 45.507 45.5 0.007
challenge rows: |mean of A1-A15 - Avg| = ['0.333', '0.267', '0.067', '0.067', '0.333', '0.133', '0.467', '0.067']
data: 64620 frames at 50 Hz (expected 64620), bone-length deviation 2.8e-13 mm, root at origin: True
yaw wraps through +-180 per action: {'Directions': 0, 'Discussion': 0, 'Eating': 0, 'Greeting': 0, 'Phoning': 162, 'Photo': 0, 'Posing': 26, 'Purchases': 0, 'Sitting': 0, 'SittingDown': 137, 'Smoking': 0, 'Waiting': 132, 'WalkDog': 7, 'Walking': 11, 'WalkTogether': 8}
A1 pooled over frames 41.1862 mm, mean of 15 actions 41.5239 mm, difference -0.3377
sum (w_a - 1/15) m_a = -0.3377 |identity error| 1.2e-14; sign predicted from lengths and Table 3: -1
3d-pose-baseline: --evaluateActionWise 'Average' 41.5238 (untruncated 41.5239, change -0.0001); default 'Val error avg' 41.1871 (pooled 41.1862)
root excluded (16 joints) 44.1192; 17-joint/16-joint 0.941176470588, |ratio - 16/17| 2.2e-16
A2 stride mean of actions: min over offsets max width (stride 1 inside?)
1 41.5239 41.5239 0.0000 True
5 41.5146 41.5342 0.0196 True
64 41.2012 41.8238 0.6226 True
A3 retargeted truth vs FK on universal skeleton: max deviation 4.5e-13 mm
bias-only predictor: MPJPE 24.4336, UMPJPE max over actions 5.1e-14
full predictor: MPJPE 41.5239, UMPJPE 36.7937; UMPJPE < MPJPE in 15/15 actions, smallest margin 0.1447
A4 MPJAE wrapped 3.987631 deg (mean |noise| 3.987631, |diff| 0.0e+00); naive 4.043940 deg
angles across +-180: 534 of 3295620 (0.000162); naive - wrapped 0.056308, sum(360 - 2|n|)/N 0.056308 (|diff| 4.0e-16)
A5 MPJLE@t: t = 50 mm: 0.3195, t = 100 mm: 0.0094, t = 150 mm: 0.0000
integral over t in [0, 200) mm, step 0.1: 41.2391; mean of min(error, 200): 41.1862; difference 0.0529
Published tables. The transcribed Fig. 2(a) columns reproduce the printed totals, so the check on the transcription held, and the three columns sum to 3,578,080 poses. Weighting the paper’s Table 3 by the test counts lowers all eight rows, by 1.26 to 2.88 mm: by the identity above, the longer test scenarios tend to be the ones with lower errors for these baselines. These pooled values are our arithmetic; the paper reports Table 3 per scenario and gives no average for it.
The hypothesis that Martinez et al.’s “Avg” equals the unweighted mean of the 15 printed actions, within the 0.1 mm that rounding allows, held for nine of the eleven complete rows and failed for two: Park et al. (SA), 0.160 mm, and Pavlakos et al. (MA), 0.413 mm. The planning run had already printed these two values before the hypothesis was tested, and it was kept as written. The printed numbers show only that these two “Avg” values are not the mean of the per-action values in the same row. For the ECCV 2018 challenge rows, every “Avg” lies within 0.467 of the unweighted mean of the integers A1 to A15, inside the 1.0 that integer rounding allows: this hypothesis held, but it shows consistency only, since without the frame counts of the challenge test set a frame-weighted average cannot be excluded.
Pooled against per-action. The synthetic data are as constructed: 64,620 frames, bone lengths constant to mm, the root at the origin, and each walking action’s heading wrapping through ±180° (7 to 11 times): the data checks held. The pooled MPJPE is 0.34 mm below the action mean, and the identity reproduces the difference to mm: the identity check held. The sign, negative, matches the sign predicted from Fig. 2(a)’s validation counts and Table 3’s errors alone, so that hypothesis held; the predictor’s limb-length bias did not reverse it. The port of 3d-pose-baseline gives the action-wise “Average” within 0.0001 mm of the untruncated action mean, so the truncation of frames, at most 63 of more than 2,500 frames per action, changed it by far less than the 0.5 mm bound and that hypothesis held; the default branch’s single number is the pooled mean, up to its own truncation. Counting the root, the 17-joint mean is exactly 16/17 of the 16-joint mean (41.52 against 44.12 mm), and the check held. That convention moves the number by 2.6 mm here, more than the choice between pooled and per-action means.
Frame strides. Evaluating every 5th frame, the 10 fps that several papers use according to 3D Human Pose Estimation, moves the action mean by at most 0.02 mm over the five possible start frames; every 64th frame moves it over a range of 0.62 mm. Both ranges contain the all-frames value, the range is wider at stride 64, and both widths are below the planned bounds of 0.2 and 1 mm: the three stride hypotheses held. In our reading, with 64 times fewer frames the per-frame noise averages out less, and at stride 64 the effect of the start frame alone is larger than the difference between pooled and per-action means in this example.
UMPJPE. Retargeting the ground truth reproduces forward kinematics on the universal skeleton to mm, so the retargeting check held. A predictor whose only error is the limb-length bias has an MPJPE of 24.4 mm and a UMPJPE of mm: the analogue removes that bias exactly, and the hypothesis held. For the full predictor, UMPJPE is below MPJPE in all 15 actions (36.79 against 41.52 mm on average, the smallest margin 0.14 mm), and that hypothesis held. The drop, 4.7 mm, is much smaller than the bias-only predictor’s 24.4 mm. In our reading, the bias and the noise add as vectors, so removing the bias does not subtract its length from the total, and UMPJPE stays below MPJPE because fixing each bone’s length also discards the noise’s component along the bone.
MPJAE. With the wrap, MPJAE equals the mean absolute angle noise exactly, and the gap between the naive and the wrapped error equals the sum over crossings of divided by the number of angles, to : both hypotheses held, and the naive value is larger. Only 534 of 3.3 million angles crossed ±180°, so the naive error is only 0.056° higher; each crossing, though, contributes nearly 360° instead of a few degrees, so a method whose angles sit near ±180°, such as a global heading, can be dominated by such terms without the wrap. Rotation Representations shows the same discontinuity in interpolation.
MPJLE. Thirty-two percent of joints miss by at least 50 mm, under one percent by 100 mm, and a share that rounds to 0.0000 by 150 mm; the curve does not increase with , and that check held. Integrating MPJLE@ over gives exactly the mean of the errors clipped at 200 mm, since ; the 0.1-mm Riemann sum is 0.053 mm above it, within the step, and the check held. The clipped mean and the pooled MPJPE print the same value, 41.1862 mm: the paper’s integrated MPJLE is the mean joint error with each error capped at the upper limit of integration, which equals MPJPE when no error reaches that limit.
Every hypothesis and bar was written before any script ran, but before the pilot two planning scripts computed the values the bars would be compared with; the second is a byte copy of this one and printed the same values, so the example is an illustration of the aggregation rules, not an independent test. The script was run as planned, with no change after its first run, and two runs give identical output.
License
As of November 2026, the licence agreement page grants licences “free of charge … limited to academic use only”, to requests sent from an academic address, as a limited, non-exclusive, non-assignable and non-transferable licence; the dataset may not be rented, lent, sub-licensed or transferred. It disclaims warranties and liability, makes the user responsible for obtaining written permission and paying any fees for uses the agreement does not cover, and states that any use of the dataset and its software constitutes acceptance. Users must cite the TPAMI 2014 and ICCV 2011 papers in every publication that uses the data; the page states that citing the dataset URL instead would not be compliant. Commercial licensing is by contact with the authors. Access requires registration and login, which were not used for this article; the description, data, code and readme files behind the login were not read.
The evaluation code read for this article, 3d-pose-baseline, is under the MIT License (LICENCE at commit 666080d). VideoPose3D, the code behind the Procrustes-aligned error discussed in 3D Human Pose Estimation, is under CC BY-NC 4.0, and only its licence was read here.
Limitations and Bias
- A laboratory. Every sequence was recorded indoors in one capture space against the laboratory background, with four fixed cameras. The authors added mixed-reality test videos to cover moving cameras, complex backgrounds and occlusion, and describe them as “a taster” (§2.2).
- Few people. Eleven actors, with seven of them public in 3D, give limited body-shape and clothing variation; the paper speaks of a “moderate amount of body shape variability”. The actors wore their own clothes (§2.2).
- Scripted scenarios. The actors received tasks with visual examples and were left freedom to improvise; poses repeat within a scenario, and the paper notes that variability within a task such as eating is subtle (§1).
- Withheld data. S10’s images are withheld for privacy, and test results go through the server, so published work evaluates on S9 and S11 (see Splits).
- Aggregation. As the example shows, the same predictions give different headline numbers depending on whether frames or actions are averaged, which frames are kept, and whether the root counts; these choices are seldom all stated.
Common Uses
Human3.6M is used to train and evaluate monocular 3D pose estimators, 2D-to-3D lifting networks such as Martinez et al.’s baseline, multi-view triangulation methods, temporal models over pose sequences, and body-model fitting and regression methods such as those in Parametric Human Body Models. Its sequences also serve as training and test data for motion and video prediction.
Historical Milestones
- 2011: Ionescu, Li and Sminchisescu publish latent structured models for human pose estimation at ICCV; the licence pairs this paper with the dataset paper (not read here).
- 2014: the TPAMI paper describes the dataset and its baselines. On the general-model set-up, LinKDE reaches 127.92 mm MPJPE with bounding-box features and 137.98 mm with background subtraction, on the full test set including S10 (Table 4); activity-specific errors range from 93.01 mm (LinKDE, Eating, background subtraction) to 247.57 mm (kNN, Taking Photo, background subtraction) in Table 3.
- 2017: Martinez et al. report 62.9 mm under protocol #1 with fine-tuned Stacked Hourglass detections and one model for all actions, and 45.5 mm from ground-truth 2D joints (Table 2, S9 and S11, “Avg” column). In December 2017 the server begins scoring results on a subset of selected actions (news page).
- 2018: the 3D Human Pose Estimation Challenge at the ECCV 2018 PoseTrack Challenge, organized by A. Zanfir, E. Marinoiu, A.I. Popa, M. Zanfir, V. Olaru and C. Sminchisescu, uses CHALL_H80k, a subset of 80,000 poses of 10 actors (6 male, 4 female) in 15 scenarios. Training and validation data were pre-released on 9 July 2018, the benchmark and server opened on 22 July, and results were due on 18 August. The rules allowed one submission per week, forbade external 3D pose data, and allowed ImageNet-pretrained features and training on both training and validation data. The ranking page describes CHALL_H80K as the Human80K test set of 25K examples, and lists eight challenge entries with averages from 45 to 66 mm, the first by Istvan Sarandi (as of November 2026).
Related Datasets
HumanEva, the earlier synchronized video and motion-capture benchmark, is about 40,000 poses in the paper’s comparison, and Martinez et al. evaluate on it with a rigid alignment in every case. MPI-INF-3DHP and 3DPW, which add outdoor and in-the-wild scenes, are described in 3D Human Pose Estimation. The CMU motion-capture collection provides poses without synchronized images. MPII Human Pose is the 2D counterpart for images in the wild. The dataset page also announces newer large-scale motion-capture datasets for fitness, close human interactions and self-contact.
Resources
- Dataset website, licence agreement and ranking pages: vision.imar.ro/human3.6m (registration required for data and code).
- 3d-pose-baseline: github.com/una-dinosauria/3d-pose-baseline (MIT).
- 3D Human Pose Estimation for the protocols, PA-MPJPE and the other benchmarks.
Related
- Human Pose Estimation
Locating the body joints of every person in an image, such as shoulders, elbows, knees and ankles, to recover each person's 2D skeleton; keypoint definitions, part models, heatmap networks, top-down and bottom-up multi-person methods, and the PCKh and COCO keypoint metrics.
- Parametric Human Body Models
Statistical models that turn a small vector of shape and pose parameters into a full 3D body mesh; SCAPE and SMPL, linear blend skinning on a kinematic tree, shape and pose blend shapes, the hand, face and whole-body models MANO, FLAME and SMPL-X, fitting and regressing them from images, and their licence terms.
- Skeleton-Based Action Recognition
Recognizing human actions from sequences of body-joint coordinates instead of pixels; where skeletons come from, the NTU RGB+D and Kinetics-Skeleton benchmarks, recurrent and CNN models, spatial-temporal graph convolution from ST-GCN to adaptive graphs, heatmap-based 3D CNNs, and what skeletons cannot see.
- Video Prediction
The task of generating the future frames of a video from its past frames, optionally given the actions of an agent; why a deterministic predictor trained with squared error blurs uncertain futures, the pixel, motion, structure and adversarial predictors, the latent-variable models with a learned prior such as SVG, autoregressive, token and diffusion predictors, action-conditioned prediction for robot planning, the Moving MNIST, KTH, BAIR and Kinetics-600 benchmarks, and best-of-K evaluation with PSNR, SSIM, LPIPS and FVD.
- MPII Human Pose
A benchmark for 2D human pose estimation from the Max Planck Institute for Informatics, released in 2014, with about 25,000 YouTube frames and 40,000 people annotated with 16 body joints, collected through a taxonomy of everyday activities; its single-person track is scored with PCKh, a joint-distance measure normalized by head size, and its multi-person track with average precision over body joints.
- Rotation Representations
The ways a 3D rotation can be written down (rotation matrices, Euler angles, rotation vectors, unit quaternions, 6D vectors and the Lie algebra), how they convert into each other, and where each one fails.
References
- Ionescu, C., Papava, D., Olaru, V. & Sminchisescu, C. (2014). Human3.6M: Large Scale Datasets and Predictive Methods for 3D Human Sensing in Natural Environments. IEEE Transactions on Pattern Analysis and Machine Intelligence, 36(7), 1325–1339.
- Ionescu, C., Li, F. & Sminchisescu, C. (2011). Latent Structured Models for Human Pose Estimation. IEEE International Conference on Computer Vision (ICCV) (not read).
- Human3.6M dataset website: overview, subjects and scenarios, data, licence agreement, news, ECCV 2018 challenge and ranking pages (vision.imar.ro, as of November 2026).
- Martinez, J., Hossain, R., Romero, J. & Little, J. J. (2017). A Simple yet Effective Baseline for 3d Human Pose Estimation. IEEE International Conference on Computer Vision (ICCV).
- 3d-pose-baseline: code for Martinez et al. (2017), src/predict_3dpose.py, src/data_utils.py and src/linear_model.py at commit 666080d (GitHub repository una-dinosauria/3d-pose-baseline, MIT License, as of November 2026).
- VideoPose3D: code for Pavllo et al. (2019) (GitHub repository facebookresearch/VideoPose3D, CC BY-NC 4.0; licence read, code not read).