在 motion tracking 的论文和工程项目里,HumanTracker 这个方向很容易被误解为“再做一个更精确的模型”。但名字里真正重要的其实是后半段:Benchmark。模型精度可以靠数据堆出来,评估口径如果混乱,堆多少数据都说不清效果。HumanTracker 指向的是一个老问题,即 human motion tracking 的评价体系还不够全面,也不够贴近人对“动得对不对、顺不顺、跟没跟住”的直觉判断。这篇记录就从评估基准的视角拆开讲:运动跟踪评估到底在评什么、常见指标有什么边界、我自己做最小评估脚本时会怎么写、以及数据和标注阶段最容易踩哪些坑。
人体运动跟踪与单帧姿态估计最大的不同在于输出对象多了一条时间轴。单帧任务只需要回答“这一帧里某人是什么姿态”,跟踪任务还必须回答“这个姿态属于谁、上一帧是谁、再往前是谁”。一帧一帧分别评估,即使每帧关节误差都不大,两帧之间的身份互换也可能把动作语义完全弄乱。HumanTracker 这类基准要解决的,正是这种“逐帧指标看起来不错,但整体行为不可用”的验收盲区。
1. 运动跟踪为什么需要独立基准,HumanTracker 在追问什么
1.1 运动跟踪不等于逐帧姿态估计
先做一个区分。姿态估计领域熟悉的关键点通常指 COCO、MPII、Human3.6M 这些数据集里定义的一批 2D 或 3D 关键点。单帧姿态估计输入是一张图片,输出是关节坐标,标签与图片一一对应。运动跟踪的输入则是一段视频或一段连续动作信号,输出通常要包含三样东西:
- 每帧的人体关键点位置;
- 每个关键点所属的人体实例 ID;
- 同一实例在连续帧之间的关联。
换句话说,输出不是散落的骨架,而是一整套带身份的骨架序列。评估这种序列时,单帧误差只是必要条件。如果同一个人的 ID 在第十帧跳到另一个人身上,后续所有关节误差即使都由单人姿态模型正确算出,从跟踪角度看仍然是错误的。
HumanTracker 这类工作把问题定义成“Human Motion Tracking Benchmark”,实际上是把跟踪任务从姿态模型里抽出来独立验收。它不是只关心一个模型能不能把某一帧骨架摆准,而是关心这个模型能不能把一段运动完整地跟住、对齐、并且让旁观者觉得动作关系符合常识。
1.2 单帧指标掩盖不了跟踪漂移
很多项目会在论文里列 MPJPE、PA-MPJPE、PCK 这些单帧指标。这些指标衡量的是“预测关节点和标注关节点之间的几何偏差”。单独看没有问题,但用于运动跟踪时有三类信息被丢掉了。
第一类是时间连续性。单帧指标不会惩罚骨架在第 5 帧到第 6 帧之间出现的不合理抖动。一个模型可以在每帧都足够准确,却产生频率明显不自然的高频抖动,这在运动重定向或动作质量评估场景里几乎不可用。
第二类是身份一致性。单帧指标把每个人分开算误差,不关心第 6 帧的“人 A”和第 7 帧的“人 A”是不是同一个实例。实际跟踪器如果发生 ID Switch,单帧关键点误差不会立刻放大,但骨架身份已经被换掉了。
第三类是人类感知。人的视觉对关节绝对坐标的细微偏差并不敏感,对脚是否踩地、手是否明显滑动、躯干是否扭成不可能姿态非常敏感。几何误差降低 5 毫米,很难说成“运动质量变好”。
1.3 “全面”和“人类一致”是两个不同问题
从 HumanTracker 的名称可以拆出两个验收维度。
“全面”是覆盖度问题。它要求评测集包含足够的姿态范围、遮挡程度、镜头角度、动作速度和人群密度。一个只有单人正面行走的数据集不能支撑通用跟踪结论,因为换到多人互相遮挡、部分关节长期不可见的场景时,所有误差都有可能出现数量级变化。
“人类一致”是度量方向问题。它要求误差的定义尽量靠近人对运动的判断。人在判断一段动作回放时,通常会先看骨骼比例是否稳定、肢体是否出现非生理角度、是否发生跳变、人物身份是否连贯,最后才关注到毫米级偏移。基准如果只保留关节点坐标差异,就会把人的主观质量压扁成一个很狭窄的几何信号。
因此在做基准或验收方案时,不能只问“误差降了多少”,要问清楚指标是否同时覆盖了单帧几何、时序平滑、身份一致和人类可读性。
2. 基准需要定义清楚的四类指标
2.1 单帧几何误差
单帧几何误差是运动跟踪评估的地基,最常用的是 MPJPE。
设第 t 帧有真实关节坐标向量集合 G_t 和预测关节点 P_t,则这一帧的关节误差可以写成:
MPJPE_t = mean over j of sqrt( (P_t[j].x - G_t[j].x)^2 + (P_t[j].y - G_t[j].y)^2 + (P_t[j].z - G_t[j].z)^2 )最终序列指标对全部帧求平均。这里的关键问题是坐标系。如果预测值来自相机坐标,真实值也是相机坐标,可以直接比较。如果预测的是世界坐标,而真实标签使用以骨盆为原点的局部坐标,必须先对齐,否则一条腿的长度误差会被全局位移放大。
PA-MPJPE 就是在这种背景下引入的。它在计算前使用 Procrustes 对齐或刚性变换,把预测骨架和真实骨架消除旋转、平移和部分缩放差异。这样做的目的是纠正相机标定噪声、人体朝向偏差和骨骼长度不一致带来的系统误差,但同时也抹掉了一部分真实失败信息。基准文档里必须写清楚是否使用缩放对齐,不能只写“PA”。
常见的单帧指标对比如下:
| 指标 | 是否对齐 | 主要用途 | 需要注意 |
|---|---|---|---|
| MPJPE | 通常先做根节点对齐 | 关节绝对位置误差 | 坐标分母必须一致 |
| PA-MPJPE | 做刚体或相似变换对齐 | 弱化相机与骨骼长度噪声 | 可能隐藏朝向错误 |
| PCK / AUC | 按躯干或头部尺寸归一化 | 2D 关键点检测 | 阈值选择影响结果 |
| OKS | 按检测尺度归一化 | 2D 实例姿态 | 需要数据库掩码信息 |
2.2 身份一致性与轨迹质量
跟踪必须有身份维度。最简单的是统计序列内发生了多少次 ID Switch,也就是跟踪器把当前帧目标分配给了错误历史轨迹。
如果项目需要更高区分度,可以使用多目标跟踪领域的指标:
- IDF1:把预测轨迹和真实轨迹作为图匹配问题,计算正确关联占比;
- HOTA:结合检测精度、关联精度和时序一致性;
- MOTA:更偏向检测数量误差,对遮挡场景波动较大。
运动跟踪基准里的“跟踪错误”不一定体现在关节误差上。“关节很准但 ID 全乱了”是真实存在的失败模式,只有当报告里同时列出单帧误差和身份指标时才看得清楚。
2.3 时序运动质量
几何误差只能告诉你每一帧位置偏差,不告诉你运动是否自然。简单的补救是对速度或差分误差做逐帧统计。
对于第 t 帧,关节点的速度v[j][t] = p[j][t] - p[j][t-1]。速度误差是预测速度和真实速度的差。更细致一点可以统计加速度不一致、关节角度在帧间突变、末端关节在接触面产生滑动等。很多现实项目不要求模型输出自然动作,但如果基准名叫运动跟踪,时序流畅性就应当成为显式维度。
运动质量指标建议在关节数少的简化骨架上计算。骨架越密集,末端关节的速度噪声越大,直接统计全部关键点的速度误差会引入大量与真实动作无关的抖动。
2.4 与人类判断对齐
“人类一致”听起来主观,但工程上可以设计成可重复的对比实验。最简单的方法是让一组评估人员看随机排序的两段结果,选择哪一段更符合真实动作语义。为了避免个人偏好,评估人员只报告二选一,不负责解释原因。
更细的做法是先列出失败类型,再请评估人员标注失败类型:
- 左右手混淆;
- 大幅关节角度穿模;
- 人物身份跳动;
- 脚步滑行;
- 运动节奏明显异常。
然后统计每种失败类型出现的比例。这种人工评测不一定要跑上千段视频,随机抽取并保证置信区间即可。它不能替代几何指标,但能把几何指标忽略的质量损失补回来。
3. 从代码起步:搭建一个最小可复现评估脚本
3.1 先定义数据格式和关节顺序
运动跟踪评估的很多坑不在算法,而在脚本输入格式不一致。先约定统一 JSON 结构,能避免多数对齐问题。
{ "sequence": "livingroom_cam01", "fps": 30, "joint_names": [ "pelvis", "spine", "chest", "neck", "nose", "left_shoulder", "left_elbow", "left_wrist", "right_shoulder", "right_elbow", "right_wrist", "left_hip", "left_knee", "left_ankle", "right_hip", "right_knee", "right_ankle" ], "frames": [ { "frame_id": 0, "gt_3d": [ [0.12, 0.30, 0.01], [0.11, 0.45, 0.02], [0.10, 0.42, 0.03] ], "pred_3d": [ [0.14, 0.31, 0.01], [0.13, 0.44, 0.04], [0.12, 0.40, 0.03] ], "track_id_pred": 1, "track_id_gt": 1 } ] }这里面joint_names尤其关键。CT、MRI 等医学项目里关节命名必须对应到固定解剖位置;在运动跟踪里关节索引对应错误,会造成误差突然变大但脚本不报错。建议关节顺序写死并且采用一套统一命名,不许不同转换脚本各自改顺序。
3.2 Python 评估实现
下面这段代码不是完整竞赛实现,而是一个最小可跑通的评估骨架。它假设三维坐标的单位是毫米,并且所有坐标系已经转换到同一参考系。代码只做一件事:逐帧计算根节点对齐后的 MPJPE。
import json import numpy as np def load_sequence(path): with open(path, "r", encoding="utf-8") as f: data = json.load(f) return data def root_aligned_mpjpe_sequence(pred_frames, gt_frames): per_frame_errors = [] for pred, gt in zip(pred_frames, gt_frames): p = np.array(pred["pred_3d"], dtype=np.float64) g = np.array(pred.get("gt_3d") if "gt_3d" in pred else gt["gt_3d"], dtype=np.float64) # 这里使用骨盆根节点对齐,根节点索引取 0 p = p - p[0] g = g - g[0] diff = p - g frame_error = np.sqrt(np.mean(np.sum(diff * diff, axis=1))) per_frame_errors.append(frame_error) if len(per_frame_errors) == 0: return float("nan") return float(np.mean(per_frame_errors)) if __name__ == "__main__": data = load_sequence("sequence.json") pred_frames = data["frames"] gt_frames = data["frames"] # 实际项目中从真实标签单独读取 # 注意:上面的 gt_frames 只是演示占位,真实脚本应该从 gt.json 读取 print("root_aligned_mpjpe:", root_aligned_mpjpe_sequence(pred_frames, gt_frames))真实评测不能只依赖上面的实现,因为还缺少三块逻辑:
- 对
None关节或不可见关节做遮罩处理; - 对预测点比真实点多的情况做离群过滤;
- 对
track_id_pred做时间轴身份匹配。
如果需要 PA-MPJPE,可以使用scipy.spatial.procrustes做刚性相似变换,再计算变换后点对之间的均方误差。但要注意,SciPy 的 procrustes 函数会做中心化和缩放,得到的是归一化空间中的误差。要在工程报告里还原为毫米级数值,必须把缩放宽转换回原尺度。
3.3 评估脚本的输出与验收清单
一段最小脚本应当输出四层信息:
sequence: livingroom_cam01 frames_total: 1200 frames_valid: 1186 mpjpe_root_aligned_mm: 62.5 pa_mpjpe_approx_mm: 41.2 id_switches: 3 speed_mm_per_frame_error: 18.3只输出一个浮点分数无法定位问题。至少要有:
frames_valid:有多少帧真正参与了求平均;- 每帧误差直方图;如果误差集中在某几帧,说明是跟踪失败而不是整体偏差;
- 身份切换发生位置的列表。
把这些结果写进 report,可以在调模型时快速确认是哪里变差。
4. 做基准数据和标注时,需要提前堵住的坑
4.1 三维相机坐标必须做时间同步
Human motion tracking 基准通常依赖多视角相机重建三维关键点。只要有一路视频时间戳偏差 30 毫秒,骨骼位置在快速动作下就可能偏出几个厘米。先检查相机时间戳对齐,再做三角化,而不是先标完关节再补救。
三维数据最好保留两个层级:第一层是每个视角的二维关键点原图坐标,第二层是三角化后的三维坐标。后续如果发现标定误差,可以只改标定参数重新三角化,不用从头标注。
4.2 遮挡关节要区分“不可见”和“不存在”
很多标注工具会允许标注者把遮挡关节放在推断位置,这等于把模型要解决的任务提前替它做了。更好的协议是让标注者明白:“不可见”就填None,或者生成一个独立的可见性掩码。
掩码的价值在训练和评估阶段完全不同。评估阶段缺少掩码时,模型在遮挡严重的关节上出现误差,会直接被计入总误差;如果这些遮挡关节标注本身可信度低,指标会被噪声主导。没有掩码,两个模型本来有效精度相同,也会因为谁更冒险多预测了遮挡点而出现分数差异。
4.3 跟踪 ID 不连续是重大质量事故
逐帧标注中,人体离开画面再出现时,人员 ID 有可能被标成新 ID。如果这是测试标签的真实情况,跟踪器会被误判为 ID Switch。如果是标签问题,应该先做 ID 指向合并。
在多人场景里,建议在标注协议里加入规则:
- 人被完全遮挡不超过 N 帧,再次出现时必须沿用原 ID;
- N 的数值必须在协议文档里写死;
- 超过 N 帧后重新出现,允许成为新轨迹,但要单独记录事件类型。
这样才能区分“标注人员忘了延续 ID”和“跟踪器真的丢了目标”。
4.4 质检清单要具体到可执行
下面是一份可复用的运动标注质检清单:
| 检查项 | 失败现象 | 处理方式 |
|---|---|---|
| 时间戳同步 | 快速挥手时左右手误差异常大 | 重新检查相机偏移并同步 |
| 关节顺序一致 | 左右肩互换导致误差虽小但方向错误 | 固定 joint_names 与索引映射 |
| 遮挡掩码 | 大量遮挡帧无 None 标记 | 回到标注阶段重新标记可见性 |
| ID 连续性 | 单人短时被遮挡后 ID 改变 | 合并轨迹或标记事件 |
| 坐标单位 | 数值量级突增约 1000 倍 | 统一为毫米并复查单位转换 |
| 根节点定义 | 根对齐后躯干仍漂移 | 确认骨盆中心点索引不是髋中心点 |
5. 常见错误和排查路径
5.1 关节索引和坐标系不一致
现象:单帧误差看起来在合理区间,但按动作片段画图后发现,预测骨架某些关节与真实骨架贴不上,尤其是左右手接近时。
排查顺序:
- 先检查
joint_names顺序是否一致; - 再检查三维坐标的坐标系方向,尤其是 Z 轴朝前还是朝上;
- 检查训练时是否对坐标做了归一化,评估时忘记反归一化;
- 输出一到两帧的原始数组,直接比较对应索引的坐标值。
处理方式是把关节索引表单独放到配置文件中,并在加载时校验名称。不要只依赖整型索引。
5.2 遮挡帧的 None 值被零填充
现象:遮挡越多的测试片段,模型跨不同版本指标波动越大。
原因:某些数据处理流程把无法标注的null写成[0,0,0],模型预测值接近[0,0,0]时可能被误判为正确,或者离群值被当成超大误差平均进总分。
处理方式:评估脚本在读取 JSON 后先检测是否存在None,并把对应点剔除后再计算。输出frames_valid,让读者知道样本总数中有多少真正进入计算。
5.3 用视频级 MPJPE 代替跟踪级指标
现象:两个模型逐帧姿态误差差不多,但实际应用里一个能稳定跟随,另一个频繁跳 ID。
原因:视频级误差没有与身份关联,ID Switch 不产生额外关节误差。
处理方式:在真实运动跟踪项目中增加至少一个身份指标。不一定要做完整 HOTA,最简单的是计算轨迹数组发生 ID 跳变的次数。第一次跑评估时如果 ID 完全没进入脚本里的目标函数,后面的所有调优都是盲目的。
5.4 Procrustes 对齐过度,隐藏真实错误
现象:PA-MPJPE 看起来很好,但换到实际应用时,相机坐标下的方向完全错误。
原因:PA 对齐允许骨架整体旋转和平移,甚至缩放。一些左右关系严重颠倒的错误也会在对齐后被极限修正。
处理方式:报告分两层。一是做对齐后的指标,用于消除标注和相机噪声;二是一定要报告未对齐或只做根节点平移对齐的指标。两种都报,比对数字更诚实,也更容易让复现者判断问题出在姿态估计还是坐标系转换。
6. 让基准验收结果经得起复现和比较
6.1 环境锁定和随机性控制
Motion tracking 评估虽然不像训练那样依赖随机种子,但数据加载顺序、非确定性后处理、GPU 算子差异都会导致微小误差。一个基准脚本应当把以下内容写进版本控制:
- 数据集版本和镜像信息;
- Python、PyTorch、CUDA 版本;
- 模型权重文件哈希值;
- 后处理是否开启;
- 是否启用 TensorRT 等推理优化;
- 随机种子。
报告结果时,同一配置至少跑三次取均值和标准差。这不是训练指标,只要运行环境一致,均值应当稳定到毫米级以下。如果两次运行差距超过 2 毫米,优先怀疑非确定性后处理或测试集抽样逻辑。
6.2 测试集泄露要提前隔离
如果 HumanTracker 这类基准是为了公开对比,参与方绝不能用测试序列做微调或模型选择。实际中常见的泄露方式是数据清洗阶段人工看了测试视频,并据此决定哪些模型架构更适合。视觉上看似无害,但会让所有实验结果不复现。
正确的顺序是:
- 训练集和验证集用于开发;
- 开发完成后只跑一次测试集;
- 测试集结果写入报告后不再修改;
- 发现测试集残缺时,只能新增正式评估轮次,不能回改旧结论。
6.3 发布结果时应当附上的最小信息
一篇基准报告要想被其他团队复现,至少附上下面这些信息:
| 信息 | 作用 |
|---|---|
| 数据集的官方版本号 | 避免不同年份子集混用 |
| 训练与测试划分文件 | 保证使用完全一致的分组 |
| 关节顺序文件 | 防止 keypoint 映射错位 |
| 坐标单位说明 | 区分米、毫米、归一化坐标 |
| 遮挡掩码文件 | 让不同模型对不可见点处理可比 |
| 运行环境配置 | 锁定依赖与加速版本 |
| 模型推理方式 | 是否多尺度、是否时序平滑后处理 |
很多复现问题并非模型代码不同,而是标记口径混乱。把关节顺序、坐标单位、可见性掩码和 ID 处理规则放到同一个配置文件中,比散落在论文文字补充里更不容易出错。
6.4 从几何指标走向人类一致性评价
基准的价值最后还是要落到“指标提升是否能被使用场景里的人感知”。工程上可以按三个阶段推进:
第一阶段只做几何误差和 ID 指标,用于算法迭代; 第二阶段加入时序差分、速度与加速度误差,捕捉明显跳变; 第三阶段做小型人工对比,随机抽出部分片段,让人评价预测骨架是否更像真实动作。
这个顺序适合大多数资源有限的团队逐步投入。过早做人工评价成本高,过晚做则容易在错误指标上调出自我感觉良好的模型。把三层全部沉淀为脚本和记录,之后任何新的运动跟踪模型都能在比较清楚的验收线上判断好坏,而不是靠一张 MPJPE 效果图说明能力。