
最近在调研人体运动分析与视频理解方向时被 HumanTracker 这个题目吸引了。它并不是又一个姿态估计模型而是一类更值得仔细拆解的工作Motion Tracking Benchmark。放在当前的研究节奏里其实很多团队的模型精度已经不低真正难的是如何定义“跟踪得好”如何让评价标准与人类对运动的理解保持一致。本文就围绕 HumanTracker 题目中的两个关键限定词 “Comprehensive” 和 “Human-Aligned”拆解运动跟踪基准的设计动机、数据与评测流程并给出一套可以复用的通用评估代码框架。适合正在做姿态估计、行为识别、多目标跟踪或者准备参加运动理解类比赛的同学阅读。需要提前说明的是HumanTracker 的具体数据统计、标注口径与榜单结果请以论文正式版或项目主页为准。本文重点是整理这类基准的通用设计思想并把评估环节中反复用到的技术点讲清楚。1. 为什么要关注 Human-Aligned 运动跟踪基准1.1 从视频运动理解说起“看懂视频里的人在做什么”这件事在计算机视觉里被拆成了很多子任务人体检测、姿态估计、动作识别、行人重识别、多目标跟踪等等。早期的动作识别把视频当成图片序列用 CNN 提取帧级特征再聚合得到动作类别后来引入了光流、时序建模、图卷积等本质上都是想让模型理解“运动”。运动跟踪比动作识别更进一步。动作识别回答的是“这是什么动作”而运动跟踪要回答的是“这个人的动作在连续帧里是如何变化的肢体轨迹是怎样的多个人的交互是否被正确保持”。在体育分析、自动驾驶、人机交互、医疗康复这些场景里用户不仅关心分类正确还关心不同身体部位的运动轨迹是否合理。因此运动跟踪类基准的评估指标设计会比单纯分类任务的 Top-1 Accuracy 复杂得多。1.2 人体运动跟踪与动作识别、姿态估计的边界很多读者会把姿态估计、动作识别、运动跟踪混在一起这里先做一个简单的边界划分任务输入输出关键难点人体姿态估计Pose Estimation单帧图像关节点坐标遮挡、复杂背景动作识别Action Recognition视频片段动作类别时序建模、长尾分布多目标跟踪MOT视频流行人轨迹ID 切换、遮挡匹配运动跟踪Motion Tracking视频流骨骼/关节点连续轨迹时序一致性、精细运动建模HumanTracker 强调的 motion tracking 更偏向“运动本身”既要定位人物、又要持续输出人体的结构化运动表达例如 2D/3D 关键点序列而且需要保证动作类别与运动轨迹同时可信。它与经典 MOT 的区别在于MOT 通常只给一个框和 ID而运动跟踪还需要回答“他在做什么动作”“他的四肢运动是否连贯”。1.3 现有评估基准的核心痛点目前该领域常用数据集大多聚焦于某一环节一些数据集侧重动作分类但对多人交叠、相机视角变化、细粒度动作覆盖不足。另一些数据集侧重行人跟踪却没有结构化的人体骨架和动作语义标注。评价指标也各说各话。动作识别看 top-1/top-5多目标跟踪看 MOTA、IDF1姿态估计看 PCK、OKS。当任务变成“带动作语义的多人运动跟踪”后上述单一指标很难同时反映定位、轨迹和动作三个层面的质量。HumanTracker 标题中的 “Comprehensive” 直译是“全面的”。放在这个语境下它至少包含两层意思类别覆盖要全面不能只包含走路、跑步这类大动作场景与评价维度也要全面不能只在干净环境里有效。而 “Human-Aligned” 则更耐人寻味它强调的是评估方式应该与人类对“跟踪效果好”的主观判断保持对齐而不是只看数字指标。2. HumanTracker 的核心设计思路2.1 “Comprehensive” 的具体含义一个真正 comprehensive 的运动跟踪基准在数据层面通常要思考三个问题。第一是动作类别的广度。健身动作、体育动作、日常动作、交互动作之间的差异非常大如果训练集里全是跑步和走路模型很难评价一个“挥拍”动作是否标准。第二是场景多样性。固定摄像头与移动摄像头、单人视角与多人视角、室内与室外会导致尺度、遮挡、视角等变量完全不同。第三是评价维度的完整性。以动作为核心的任务如果只按跟踪框的 IoU 来打分就会忽略动作语义错误。在论文与项目实际设计中HumanTracker 很可能采用了一套分层标注结构人级别轨迹、部位级别轨迹、动作标签、交互关系。这种结构能让我们在评估时区分三种失败模式人丢了、动作错了、轨迹断了。这也是 “comprehensive” 相比传统 MOT benchmark 的核心差异。2.2 “Human-Aligned” 的含义与背景“Human-Aligned” 并不是说让模型的表征去对齐 Embedding而是指评估指标与人类主观判断对齐。想象一个典型场景两个人在画面中交叉走过跟踪器把两个人的 ID 互换了。MOTA 指标可能因为检测框重合度还不错只扣少量分但在人类看来两个人从左边的人变成了右边的人这种错误非常明显。反过来某个动作的关节角度存在 10 度以内的误差机器指标可能会扣分但人类观察者其实分辨不出来。Human-Aligned 的设计目标就是让指标权重接近人的视觉感知。要实现这种对齐通常有两种做法。一种是人工偏好标注让标注员对同一段结果的多种跟踪输出排序再把排序结果作为训练数据学习一个偏好奖励模型。另一种是可解释指标把动作类别正确率和轨迹连续率分开报告让研究者能直观看到具体瓶颈。HumanTracker 这类基准会在追求人机一致性评估上做大量标注层面的工作。2.3 HumanTracker 的整体框架从研究思路看这类基准项目通常包含四个组成部分数据采集与标注体系、模型推理接口、评测指标集、可视化分析工具。数据采集环节会尽量覆盖长尾动作标注体系会采用骨架点加动作标签的双层标注模型接口会标准化成“输入视频输出带动作类别的人体轨迹”指标集则包含基础跟踪指标和动作语义指标可视化工具用于输出错误样例方便人工核验结果是否符合直觉。这些组件叠加在一起才是完整的 benchmark而不是简单提供一个数据集下载链接。如果读者想复现这类工作前期可以先用小规模视频集合验证标注和评估流程再逐步扩大规模。下面我们从更落地的技术栈出发讲解如何搭建一个运动跟踪任务的评估系统。3. 人体运动跟踪系统的典型技术栈3.1 骨架检测与姿态估计运动跟踪的底层依赖于骨架估计结果。目前主流方案可以分为两阶段和单阶段两阶段方法先用检测器得到人体框再对每个框做关键点回归例如经典的 Top-Down 结构单阶段方法直接在全图上回归所有人体的关键点例如使用 CenterNet 思路的 Bottom-Up 方法。在代码实现上不管使用哪种结构最终都应该统一输出一个包含关键点坐标、置信度以及时间戳的数据结构。为便于统一评测常见的输出格式为{ frame_id: 1, track_id: 3, keypoints: [ {name: nose, x: 421.5, y: 88.3, score: 0.95}, {name: left_shoulder, x: 402.1, y: 155.7, score: 0.92} ], action: jumping_jack, action_score: 0.87 }这个结构的好处是每一帧、每一条轨迹都有独立的动作标签方便后续把轨迹质量与动作分类质量分离开来评估。3.2 时空特征提取在拿到逐帧骨架序列后需要把时间维度上的运动信息编码成特征。传统做法是堆叠相邻帧的关节点坐标差得到速度与加速度特征深度学习方法则常用图卷积网络GCN或 Transformer 对骨架序列建模。以图卷积为例人体骨架可以看成一张图节点是关节点边是骨骼连接。每一帧有一个人体图连续帧之间再连上时序边就得到了时空图。训练时输入骨架序列输出每个时间窗口的动作类别。这个过程需要注意三个细节输入序列长度需要固定通常取 32 帧到 64 帧左右长度不够时做循环填充。关键点坐标要归一化到人体尺度否则远处的人运动幅度会被模型低估。多人场景中每一条轨迹单独建模不要跨人混叠特征。3.3 运动轨迹生成运动轨迹生成本质上是一个数据关联问题。基于检测到的骨架或者人体框需要将相邻帧的同一身份关联起来。简单场景下可以用 IoU 匹配加匈牙利算法复杂场景下需要结合外观特征与运动特征。在运动跟踪基准中轨迹质量直接影响动作识别的效果。比如一个动作是“抬右手”如果跟踪器把左手和右手搞混那么后续所有动作特征都是错的。因此评测时不能只看动作分类准确率还要单独统计关键点轨迹在时间上的平滑度和左右对称性。3.4 基准评测的基本流程一个标准的运动跟踪评测流程可以概括成以下步骤读取视频序列或预提取帧。对每一帧运行人体检测与姿态估计。使用跟踪算法关联轨迹得到每个人的 Tracklet。在每个 Tracklet 上运行动作识别模型。将结果保存成统一格式的 JSON/CSV 文件。运行官方评估脚本输出轨迹指标、动作指标与综合指标。这里要特别强调的是评测脚本必须保持统一的数据协议。不同模型的检测置信度阈值、关键点缺失策略、动作类别字典如果不一致结果就没有可比性。这也是为什么像 HumanTracker 这类基准项目会同时发布“评测工具”的原因。4. 搭建一个可复现的评估流水线4.1 创建项目结构为了方便大家理解并复现这里给出一套最小可运行的评估项目结构。先创建目录motion_tracking_eval/ ├── configs/ │ └── demo_config.yaml ├── data/ │ ├── predictions/ │ │ └── model_a_result.json │ └── ground_truth/ │ └── seq_01_gt.json ├── eval/ │ ├── __init__.py │ ├── metrics.py │ └── evaluate.py ├── utils/ │ ├── __init__.py │ └── io_utils.py └── requirements.txt这个结构把配置、数据、评估代码分开后续接入不同模型时只需要把模型输出转换成相同格式放入 predictions 目录即可。4.2 添加依赖创建一个简单的requirements.txtnumpy1.21.0 scipy1.7.0 pyyaml5.4.1这些依赖足够支撑数据解析、匹配计算与配置读取。如果后续需要可视化关键点可以再加opencv-python与matplotlib。4.3 数据解析模块模型预测结果和真值标签都必须统一格式下面实现一个数据加载函数。核心逻辑是递归读取 JSON 文件并建立(frame_id, track_id)的索引。# 文件路径utils/io_utils.py import json from collections import defaultdict def load_tracking_json(filepath): 加载运动跟踪结果。 预期 JSON 结构为: [ { frame_id: 0, track_id: 1, bbox: [x1, y1, x2, y2], keypoints: [[x1, y1, score], ...], action: walk, action_score: 0.9 } ] with open(filepath, r, encodingutf-8) as f: raw_data json.load(f) records defaultdict(list) for item in raw_data: frame_id int(item[frame_id]) track_id int(item[track_id]) records[(frame_id, track_id)].append(item) return records def load_action_gt(filepath): 加载动作真值。如果文件不存在则返回 None。 import os if not os.path.exists(filepath): return None with open(filepath, r, encodingutf-8) as f: return json.load(f)这里的records以(frame_id, track_id)为键后续计算轨迹匹配时就能快速查找。4.4 核心评估指标实现在多目标跟踪中常用 MOTA 描述整体跟踪准确度。MOTA 的计算公式是MOTA 1 - (FN FP IDSW) / GT其中 FN 是漏检数FP 是误检数IDSW 是身份切换次数。为了计算这些值我们需要先对预测轨迹和真值轨迹做关联。下面给出一个简化版的 MOTA 计算函数适用于帧级别 bbox 匹配。# 文件路径eval/metrics.py import numpy as np from scipy.optimize import linear_sum_assignment def compute_iou_matrix(pred_boxes, gt_boxes): 计算两组 bbox 的 IoU 矩阵。 bbox 格式为 [x1, y1, x2, y2]。 pred_boxes np.asarray(pred_boxes, dtypenp.float64) gt_boxes np.asarray(gt_boxes, dtypenp.float64) if len(pred_boxes) 0 or len(gt_boxes) 0: return np.zeros((len(pred_boxes), len(gt_boxes))) # 计算交集区域 x1 np.maximum(pred_boxes[:, 0][:, None], gt_boxes[:, 0][None, :]) y1 np.maximum(pred_boxes[:, 1][:, None], gt_boxes[:, 1][None, :]) x2 np.minimum(pred_boxes[:, 2][:, None], gt_boxes[:, 2][None, :]) y2 np.minimum(pred_boxes[:, 3][:, None], gt_boxes[:, 3][None, :]) inter_area np.maximum(0.0, x2 - x1) * np.maximum(0.0, y2 - y1) pred_area (pred_boxes[:, 2] - pred_boxes[:, 0]) * \ (pred_boxes[:, 3] - pred_boxes[:, 1]) gt_area (gt_boxes[:, 2] - gt_boxes[:, 0]) * \ (gt_boxes[:, 3] - gt_boxes[:, 1]) union_area pred_area[:, None] gt_area[None, :] - inter_area iou inter_area / np.maximum(union_area, 1e-6) return iou def compute_mota_per_frame(pred_boxes, gt_boxes, iou_threshold0.5): 单帧 MOTA 统计。 返回 (fp, fn, idsw, gt_num, matched_indices)。 其中 matched_indices 用于跨帧判断 ID 切换。 gt_num len(gt_boxes) pred_num len(pred_boxes) if gt_num 0: return len(pred_boxes), 0, 0, 0, [] if pred_num 0: return 0, gt_num, 0, gt_num, [] iou_matrix compute_iou_matrix(pred_boxes, gt_boxes) # 低于阈值的匹配无效 iou_matrix[iou_matrix iou_threshold] 0.0 # 使用匈牙利算法求最大权重匹配 row_ind, col_ind linear_sum_assignment(-iou_matrix) matched_pairs [] for r, c in zip(row_ind, col_ind): if iou_matrix[r, c] 0: matched_pairs.append((r, c)) matched_pred_ids {r for r, _ in matched_pairs} matched_gt_ids {c for _, c in matched_pairs} fp pred_num - len(matched_pairs) fn gt_num - len(matched_pairs) return fp, fn, 0, gt_num, matched_pairs上面的代码只是一个帧级匹配函数完整的 MOTA 还需要跨帧追踪 ID 切换次数。为便于阅读这里再给出一个简化版的多帧 MOTA 汇总示例。# 文件路径eval/evaluate.py import json from collections import defaultdict from utils.io_utils import load_tracking_json from eval.metrics import compute_mota_per_frame def aggregate_mota(pred_records, gt_records, iou_threshold0.5): frame_ids sorted(set([key[0] for key in gt_records.keys()]) | set([key[0] for key in pred_records.keys()])) total_gt 0 total_fp 0 total_fn 0 total_idsw 0 for frame_id in frame_ids: gt_frame {} for (fid, tid), items in gt_records.items(): if fid frame_id: gt_frame[tid] items[0][bbox] pred_frame {} for (fid, tid), items in pred_records.items(): if fid frame_id: pred_frame[tid] items[0][bbox] # 简化处理只保留当前帧出现一次的结果 gt_ids list(gt_frame.keys()) pred_ids list(pred_frame.keys()) gt_boxes [gt_frame[tid] for tid in gt_ids] pred_boxes [pred_frame[tid] for tid in pred_ids] fp, fn, idsw, gt_num, _ compute_mota_per_frame( pred_boxes, gt_boxes, iou_threshold ) total_fp fp total_fn fn total_idsw idsw total_gt gt_num if total_gt 0: return 1.0 mota 1.0 - (total_fn total_fp total_idsw) / total_gt return max(0.0, mota) if __name__ __main__: pred load_tracking_json(data/predictions/model_a_result.json) gt load_tracking_json(data/ground_truth/seq_01_gt.json) score aggregate_mota(pred, gt) print(fMOTA {score:.4f})需要注意这只是一个方便学习的最小实现。真实的 HumanTracker 官方评估脚本还会处理目标被遮挡后的临时消失、轨迹分段重连、关键点缺失、动作类别对齐等细节代码规模会大很多。4.5 动作语义指标除了位置层面的 MOTA运动跟踪基准还会关心动作标签是否正确。可以定义一个简单的动作语义指标Action Accuracy在成功匹配到真值轨迹且对应 IoU 大于阈值的前提下统计预测动作与真值动作一致的帧数占匹配帧总数的比例。def compute_action_accuracy(pred_file, gt_file): pred load_tracking_json(pred_file) gt load_tracking_json(gt_file) correct 0 total 0 for key in gt: if key not in pred: continue pred_items pred[key] gt_items gt[key] if not pred_items or not gt_items: continue # 取第一条记录做简化判断 if pred_items[0][action] gt_items[0][action]: correct 1 total 1 if total 0: return 0.0 return correct / total这个函数虽然粗糙但它体现了一个重要设计评估运动跟踪不能只看框还要看动作语义。更完善的方案会考虑动作边界偏移例如允许预测动作与真值动作之间存在几个帧的偏移仍然算正确。5. 如何接入 HumanTracker 这类新基准5.1 数据格式与标签规范不同的 benchmark 往往会定义自己的提交格式。接入之前第一步要仔细阅读任务描述中的 JSON schema 或 CSV 列名。以运动跟踪任务为例通常至少需要提交以下字段视频或序列标识符seq_id。帧号frame_id。轨迹 IDtrack_id。人体框坐标bbox。关键点坐标及其置信度可选。动作类别action与置信度action_score。如果你的模型没有输出动作类别可以先用一个独立的动作分类器从裁剪的人体序列中补充预测。千万不要在提交前临时修改测试集上的类别分布这会带来严重的评估误差。5.2 模型推理结果对齐很多模型在推理时使用了自己的类别名称或骨架定义提交前必须映射到基准定义的统一类别字典。常见错误包括左右关节点顺序不一致COCO17 与 MPII 的顺序不同。动作类别拼写不一致比如将jumping_jack写成jumpingjack。轨迹 ID 未从 0 或 1 开始连续编号导致官方评估脚本误解。为了避免上述问题可以写一个简单的校验脚本检查提交文件中的动作类别集合是否与官方字典一致。例如# 文件路径utils/schema_check.py import json ACTION_DICT [walk, run, jump, wave, sit, stand] def check_action_dict(filepath): with open(filepath, r, encodingutf-8) as f: data json.load(f) actions set() for item in data: actions.add(item[action]) unknown actions - set(ACTION_DICT) if unknown: print(警告发现未定义动作类别:, unknown) else: print(动作类别校验通过共包含, len(actions), 种类别)5.3 可视化与人工核验基准评测不仅看数字还要看可视化结果。模型提交后建议抽帧保存带轨迹框和关键点的视频再由人类观察者对随机抽样的视频片段进行主观评分。这里给出一个 OpenCV 可视化关键代码片段import cv2 def draw_keypoints(frame, keypoints, color(0, 255, 0)): for kp in keypoints: x, y, score kp if score 0.3: cv2.circle(frame, (int(x), int(y)), 3, color, -1) return frame这一环节很重要因为 Human-Aligned 的核心思想之一就是“机器分数不能完全代替人的判断”。当你用人工查看结果时会很快发现模型是在“数值上正确”还是在“运动上真正正确”。5.4 提交测试的注意事项如果 HumanTracker 采用在线评测平台提交前尤其要注意确认提交文件是否包含全部测试序列。确认动作分类的帧对齐口径不同评测脚本可能对动作边界的容错不同。如果评测需要随机种子或确定性后处理请在文档中说明。注意单次提交次数限制避免把测试集当作调参集反复使用。这里要特别提醒一点基准测试的目的是发现模型短板而不是刷一个更高分。只有认真分析失败案例比如在什么动作、什么遮挡程度下发生 ID 切换才能反哺模型设计。6. 常见问题与排查思路在开发运动跟踪评估系统时会遇到很多五花八门的报错和误差。下面整理成一张速查表方便大家排查。问题现象常见原因解决思路MOTA 为负数检测器误检太多或 GT 很小提高检测置信度阈值过滤边框宽高异常的框ID 频繁切换匹配算法未使用外观特征引入 ReID 特征或关键点空间相似度动作准确率低但跟踪准确率高动作分类输入帧裁剪过小扩大输入区域按轨迹长度重采样提交格式校验失败列表字段与官方 schema 不一致打印官方示例字段逐字段核对类型左右关键点混淆模型输出顺序与基准不一致建立关键点名称与索引的映射表关键点缺失导致 NaN分数为 0 或坐标为空设置无效点掩码不参与 loss 与指标计算多线程推理内存溢出视频帧缓存过多使用生成器逐帧读取控制批次大小结果不可复现后处理中使用了随机性固定随机种子关闭 cudnn benchmark下面单独解释一个高频问题ID Switch 和 Action Error 同时出现时的排查顺序。当 MOTA 和动作准确率同时下降建议先可视化轨迹如果同一 track_id 下动作标签在多个类别间跳动大概率是数据关联错误而不是动作分类器错误。这时优先修正匹配算法等轨迹稳定后再重新训练动作分类器。反过来如果轨迹稳定但动作准确率低问题就出在动作分类器本身可以从数据平衡和时序建模两个方向改进。7. 最佳实践与工程建议7.1 在提交基准前保持合理的数据划分做任何运动跟踪评测都需要严格区分训练集、验证集和测试集。以 HumanTracker 这类 benchmark 为例官方会公开训练集和验证集测试集通常留作在线评测。不要使用测试集的伪标签进行训练更不要尝试通过多次提交反推测试集标注这种做法既不规范也容易污染研究方向。7.2 设计分层评测报告很多团队在评测运动跟踪时只看一行 MOTA 或者一行 Action Accuracy这会掩盖关键问题。更推荐的做法是输出分层评测报告按动作类别分别统计准确率。按人员尺度分近景、中景、远景统计 MOTA。按遮挡程度分无遮挡、部分遮挡、严重遮挡统计 ID Switch。输出速度与方向误差用于判断运动轨迹是否平滑。这样做的好处是模型在哪个环节退化一目了然。Human-Aligned 的思想在这里同样适用只有细化到人的视觉感知维度评测结论才有指导意义。7.3 工程实现中的异常与鲁棒性处理在工程落地时评测代码要具备很强的鲁棒性。例如某一段 gt 文件缺失不能直接让整个评测崩溃而应该跳过并记录 warning某条轨迹只有一帧也应该纳入统计但不参与时序指标关键点坐标允许为空但后续特征提取时要有 mask 处理。建议所有 I/O 操作用 try-except 包裹日志定期输出进度。同时还要注意性能优化。如果视频很长且帧数达到十万级逐帧读取全部 JSON 会占用大量内存建议改为 SQLite 或 Parquet 格式存储中间数据并只加载当前所需帧。7.4 把 Human-Aligned 落实到数据标注中对于希望自建运动跟踪训练集或评测集的团队要真正落实 Human-Aligned最好引入人类偏好标注。标注员除了标注动作标签还可以对同一段模型输出的两个候选轨迹进行成对比较哪个在视觉上更连贯哪个人更像是“同一个人”这类偏好数据可以训练一个小型评价模型辅助自动化评测。虽然前期成本较高但对追求高质量运动理解产品的团队来说这套投入是值得的。8. 总结与后续学习建议围绕 HumanTracker 的研究方向本文重点说明了三个层面的内容第一Comprehensive 的核心是覆盖动作类别与评价维度的完整性第二Human-Aligned 的立足点是让评估结果与人的视觉感受和主观判断一致第三运动跟踪基准的实现依赖于统一的数据协议、可靠的轨迹匹配指标以及动作语义维度的独立评估。对于读者来说下一步可以沿着“姿态估计 - 多目标跟踪 - 动作识别 - 综合运动理解”的顺序不断深化。先把单个模块的基础打牢再尝试将多个任务串联成一套完整的运动理解系统。如果想参与到 HumanTracker 的基准工作中可以从阅读官方数据标注说明开始跑一遍 baseline 模型并与 leaderboard 对比然后重点分析自己的模型在哪个动作类别或遮挡场景上退步最明显这种“由评测驱动改进”的思路会比盲目调参高效得多。建议在实际动手时从小规模验证集入手先确认指标计算逻辑正确再扩展到完整数据集。毕竟评测脚本一旦有 bug后面所有模型对比都会失去意义。