动作识别数据标注工具升级:从逐帧手标到yolo26s-pose预标注复核

发布时间:2026/10/21 14:41:44
动作识别数据标注工具升级:从逐帧手标到yolo26s-pose预标注复核
做动作识别项目做到现在我越来越相信一句话数据标注工具才是整个管线的隐形天花板。前阵子要把 yolo26s-pose 和 PoseC3D 这套骨架动作识别链路搭起来满以为最大的阻碍会出在模型部署或者训练调参上结果绕了两天弯发现卡得最死的是手里那套标关键点的老工具。于是花了两周把标注工具从 1.0 直接推到 2.0这篇文章就把这次升级的来龙去脉和具体实现讲清楚。这一篇是系列第 12 篇也是 yolo26s-pose PoseC3D 这条线正式开篇的第 1 篇我不打算先讲模型结构而是把标注工具升级的整个背景、设计和实现讲透。如果你正在做基于骨架的动作识别项目手头缺一套能批量处理多人时序数据的标注流程那这篇的内容大概率能帮你省掉几周的返工时间。注数据这件事看起来是体力活实际上大部分坑都藏在工具设计里而不是鼠标点得准不准上。所以这篇我会尽量把每次取舍背后的原因也讲清楚不只是甩给你一个工具说明。1. 升级标注工具之前动作识别数据集的特殊麻烦1.1 帧级时序数据让标框思维彻底失效动作识别任务吃的不是单帧图像而是一段连续时间里关键点的运动模式。目标检测的标注可以容忍少量边缘框不精确因为最终输出是某个物体类别和位置动作识别不一样模型输入是时序关键点序列关键点位置对运动速度和方向极其敏感。如果某一帧的手腕点标错位置相当于在训练数据里注入了一个错误速度向量模型遇到类似场景时很容易犹豫甚至错判。这一点在 PoseC3D 这类把关键点编码成 3D 热图的方案里更明显——热图编码会把坐标误差扩散到周围网格单点偏差对后续卷积特征的影响比坐标回归模型略小但错误会顺着时间维度堆叠扩散几十帧之后整条轨迹都会变形。做检测数据出身的人很容易低估这个问题。检测框稍微偏两个像素通常不影响 IoU 指标但关键点偏了两个像素在快速动作段里会被模型解读成这个关节在高速移动和真实的运动趋势完全相反。换句话说动作识别标注需要同时维护三个维度的信息空间维度每个关键点在哪、时间维度这一帧处于什么动作阶段、身份维度这些关键点属于画面里的第几个人。只把标注当成把点放在关节上的做法很快就会在维度一致性上栽跟头。我在项目里最深的体会是帧不是孤立样本它是序列的一个切片。标注工具如果只支持上一帧/下一帧翻页没有意义必须支持关键点级的时间漂移检测。这个需求刚开始大家意识不到等训练结果反复出问题才发现。比如某段挥臂动作中间连续 10 帧的肘关节点都偏移到了上臂外侧单看任意一帧都只是有点偏但把 10 帧连起来看模型会学出一条完全不符合人体运动学的关节轨迹。这种错误在纯手工逐帧标注的流程里几乎无法避免因为人的注意力会随疲劳程度波动每一帧都点准的代价极高。1.2 上一版标注工具的三个核心问题先交代背景1.0 版本是一套浏览器里的标注界面视频暂停后人工逐个点出 17 个关键点然后导出一份 JSON。听起来很常规实际用起来却处处受限。认真复盘之后我把问题收敛成下面三点。第一是单人假设非常致命。项目里有大量交互动作比如两人握手、双人搏击、互助拉伸1.0 工具每次只支持一个身份遇到第二个人就只能跳过该帧。结果是导出数据里同一个动作类别有时只有单人的运动轨迹有时又夹带另一个人的半截轨迹训练时模型看到同一类别内部的输入结构完全不齐。PoseC3D 这类模型对输入人物数量的一致性很敏感这种脏数据直接导致一部分 batch 在数据装配阶段就报错另一部分则默默学到错误模式。第二是逐帧标注且无插值导致大量重复劳动。动作缓慢的片段里相邻帧之间的关键点位移可能只有一两个像素但 1.0 仍然要求逐帧点完。动作快速的片段里因为来不及点反而更容易点错。时间成本都没花在刀刃上慢动作浪费人力快动作堆积错误。整个标注过程像是一个既费时又费精力的低效闭环越标越累越累越容易出错。第三是导出格式与训练脚本之间纯靠手工对接。1.0 每人导出一份 JSON然后我在训练前处理时再写脚本转一次字段名、归一化方式、帧索引的偏移只要任何一边没对齐整批训练数据就报废。我踩过两次坑一次是帧号从 0 开始还是从 1 开始没对齐一次是归一化时用的图像尺寸和实际视频分辨率不一致都是训练跑到一半才暴露。这类问题最磨人因为报错信息往往指向数据装配阶段但真正的原因在标注工具和训练脚本的协作契约上。2. 2.0版本的设计思路让模型先当第一标注员2.1 核心转变从人标机器学到机器先标人复核2.0 的设计起点是一个朴素想法既然最终要用 yolo26s-pose 产出关键点而且它单帧质量已经相当稳定为什么不在标注阶段先跑一遍让人去改错的点而不是从零去创造点于是整个流程从人标机器学翻转成机器先标人复核。这不只是效率工具更是数据质量的抓手。机器给出的预标注在位姿一致性上有天然优势帧间关节位置不会突然跳到离谱的位置人工复核时的主要精力集中在语义层面的错误上比如左右手互换、跨身份串点而不是基础坐标结构崩溃。中间有个细节特别重要预标注结果只是底稿底稿质量再好也必须保留完整的修改轨迹。2.0 里每次人工调整都记录原值、新值、操作人和帧号方便后面追溯某个动作类别下人工修正最多的是哪些关键点。这条自省机制直接告诉我们模型在哪个部位最容易出错数据管线会自然变成模型改进的情报源。1.0 完全没有这种能力这也是我坚持升级到 2.0 的重要原因之一。分组协作上2.0 支持多人同时标注不同 clip互不干扰。因为预标注已经把大部分坐标填充好标注员不太需要从头理解一段视频再决定点什么而更像是在做 QA 验收。从项目管理角度这大大降低了新人上手标注任务的心理门槛。2.2 为什么选 yolo26s-pose 做预标注而不是单独一个关键点模型选型背后的逻辑值得展开说。第一个原因是 yolo26s-pose 同时输出检测框和 17 个关键点。多人动作识别里身份维度很关键检测框可以直接喂给跟踪策略跟踪器给不同人物分配 tracklet ID这样预标注就能一次性把人物 ID、关键点、前后帧关联起来。如果选一个只出关键点的模型还得额外做行人检测来补框反而多一层适配工作。第二个原因是训练侧和标注侧用同一个位姿模型能让难度分布对齐。预标注时如果发现某种姿态经常失败大概率训练数据里这种姿态也是稀缺样本。标注工具 2.0 因此顺手变成了困难样本发现器我们可以根据修点频率高低优先补充对应类别的数据而不是靠感觉去猜数据短缺在哪。第三个原因是 yolo26s-pose 的推理速度足够撑起批处理。以我们标注视频的量级来看单机把几千个 clip 全部跑一遍预标注几小时内能完成工具侧等待时间完全可接受。而且小模型对显存占用不高标注机上往往还同时开着训练任务资源冲突不明显。需要强调预标注模型不追求 100% 正确更不追求直接拿来当训练标签。它是把标注员从从零画点变成验收员的角色转换器。抱着这个心态去评估预标注效果才不会因为部分帧效果差而否定整个方案。实测下来哪怕预标注只有七成可靠整体标注效率也能提升 3 倍以上。2.3 模块划分与目录组织2.0 的代码结构按职责拆成五个部分。我习惯把工具本身和训练代码分开避免在同一个仓库里混放标注脚本和数据预处理脚本否则版本管理会很痛苦。下面是当时规划的目录结构toolkit/ auto_predict/ 预标注脚本负责批量跑 yolo26s-pose editor_gui/ 复核修正界面的服务端与前端 tracklet/ 多人 ID 追踪、合并与分离操作 check/ 运动学校验脚本包括骨长比例和帧间速度检查 export/ PoseC3D 数据导出与预处理各模块职责如下表模块主要职责产出auto_predict对视频批量推理生成关键点底稿底稿 JSONeditor_gui人工复核、拖拽修正、标记可疑帧修订日志tracklet分配人物 ID处理合并与分离tracklet 变更记录check校验骨长比例、帧间速度、置信度校验报告export把标注结果转成训练可直接读的文件keypoints.npz 等整体交互主流程是打开某个 clip自动加载预标注底稿按置信度着色显示当前帧空格播放快捷键跳到低置信度帧选中关键点拖动修正保存并记录修订日志全部完成后触发校验脚本最后导出训练格式。这套流程把人的注意力集中在真正需要判断的帧上而不是每一帧都平均用力。3. 预标注、复核与插值标注工具2.0的关键实现3.1 yolo26s-pose 推理结果转成标注底稿预标注脚本要做的事是把 yolo26s-pose 对每一帧的输出整理成统一的底稿结构。关键点坐标保留原始未归一化的像素值单位统一为 float置信度单独存图像尺寸写进 meta 信息。归一化不在标注这一步做目的是让标注工具和训练前处理解耦两边各自维护自己的逻辑避免一改训练脚本就得连带改标注工具。核心转换逻辑类似下面这段def prediction_to_annotation(result, frame_index, tracklets): record { video_id: result.video_id, frame_index: frame_index, tracklets: [] } for tid, obj in tracklets.items(): kpts obj.kpt # 形状为 (17, 3)分别是 x、y、置信度 record[tracklets].append({ track_id: tid, box: obj.xyxy, keypoints: kpts[:, :2].round(1).tolist(), confidences: kpts[:, 2].tolist() }) return record注意一点bbox 和 keypoints 是同一个人物实例的存储时 track_id 必须对应上。多人场景里如果 track_id 混乱后面的复核和导出都会跟着乱所以我在写入底稿之前还会做一次 ID 唯一性校验发现同一个 ID 在同一帧出现两次就直接报错。预标注阶段还需要处理一个问题yolo26s-pose 偶尔会漏检导致某一帧没有任何关键点。处理策略是把它标成该帧缺人不强行插一个空列表进去而是记录一个 pending 状态。复核界面对这类帧会强制要求人工处理不能跳过。3.2 置信度阈值、低置信度帧定位与自动插值预标注输出里有 17 个置信度值但置信度并不直接决定这一帧要不要复检直接按单帧阈值卡会漏掉很多语义错误。我的做法是先做整条 tracklet 的置信度统计再据此分层处理平均置信度不低于 0.85且没有任何单点置信度低于 0.3标记为自动通过帧。存在 1 到 3 个低置信度点标记为待修正帧进入人工抽查池。连续待修正帧超过 5 帧触发插值策略用前后锚点帧生成候选点位。连续低置信度帧超过 15 帧插值不再适用强制要求人工标注起止两个关键帧。插值实现本身不难取该 tracklet 中上一个高置信度帧和下一个高置信度帧的坐标按帧号做线性插值for k in range(17): ratio (t - t_start) / (t_end - t_start) x_interp x_start[k] (x_end[k] - x_start[k]) * ratio y_interp y_start[k] (y_end[k] - y_start[k]) * ratio但线性插值有两个明显缺陷一是在动作端点附近速度方向突变插值结果会明显偏离真实轨迹二是快速弧线运动时插值容易把轨迹拉成直线比如手臂快速甩动时肘关节的真实路径是一条弧线插值出来的却是直线穿透。所以整个 2.0 里有一个硬性约定插值输出永远不进自动通过只能作为可能正确的候选点进入人工抽查池。宁可让人多看一眼也不能让插值结果直接污染训练数据。另外我加了一个实用策略如果前后锚点帧跨度超过 15 帧就放弃插值直接要求人工标注两端帧。因为超过这个跨度后线性假设基本不可信强行插值只会给复核员挖坑。3.3 多人交互场景tracklet ID 的生成与合并多人动作是标注工具升级的重头戏。预标注阶段我用基于 IoU 和卡尔曼滤波的轻量跟踪策略把不同人物串成 tracklet画面中的每个人有自己的 ID 和时间段。跟踪器不是万能的真实世界里两大问题必须由人工兜底。第一个问题是 ID 切换。演员被遮挡后重新出现跟踪器经常会给他分配一个新的 tracklet ID但实际还是同一个人。如果不管这个问题训练数据里这个动作会被拆成两段模型学到的时序上下文不完整。编辑器中直接提供了一个tracklet 合并按钮选定两个 tracklet 后完成合并。合并时会做自动帧区间校验检查两个人的时间范围是否真的连续避免出现重叠或者空洞。校验不通过就弹出调整界面不强制写入。第二个问题是重叠身体导致的关键点串线。两人交叉动作里预标注经常把其中一人的左手腕接到另一个人身上这类错误属于关键点与人物 ID 混串比单纯坐标偏移更难发现。工具里针对这种情况做了一个辅助功能可以一键删除该人物的错误关键点然后从相邻高置信度帧插值补回再用运动学校验确认结果是否合理。一般来说两三个人交叉 3 到 5 帧以内的串线插值补回比手工重标效率高得多。3.4 遮挡帧的关键点可见性约定动作识别里经常出现关键点被遮挡的情况比如双手交叉时手腕被身体挡住或者转身时手臂被躯干遮住。2.0 引入可见性三态visible、occluded、absent。三者语义完全不同状态语义导出处理visible关键点实际可见坐标准确保留原坐标occluded关键点被遮挡但可以推断大概位置保留估计坐标并标记遮挡absent关键点不在画面内坐标填 0标记为缺失为什么一定要区分 occluded 和 absent因为训练前处理对两种状态的处理逻辑不同。对 occluded 的点坐标可以参与热图生成但需要压低监督权重对 absent 的点不能生成热图响应否则模型会学着把不存在的关键点也预测出来。PoseC3D 这类模型对输入 mask 比较敏感不做好区分会破坏整个训练逻辑。在标注界面里我设计了三种颜色的关键点正常绿色、遮挡黄色、缺失灰色。标注员不用点按键切换状态直接用快捷键切换选中点的可见性即可。一开始我考虑过自动判断可见性但发现预标注的 occluded 判断准确率并不稳定最后还是交给人工确认。这个妥协非常值得因为可见性标注一旦出错后续所有数据增强和损失计算都会跟着出问题。4. 与PoseC3D训练流程的数据对接4.1 PoseC3D 的输入结构为什么依赖标注规范这里需要简单交代一下 PoseC3D 的思路它先把每帧的 2D 关键点转换成关键点热图再把一段固定帧数的热图堆叠起来形成一个三维体最后用 3D 卷积网络从中提取时空特征并分类。也就是说模型输入不是坐标数列而是热图张量这个设计对标注数据提出了三个硬性约束。第一个硬约束是每个 clip 采样的帧数必须固定。我的做法是每个动作 clip 统一固定到 64 帧不足部分通过重复边缘帧补齐超出部分做等间隔采样。标注工具导出时就要按这个约定做帧数标准化不能把全长视频直接丢给训练脚本。第二个硬约束是每个 clip 中人物数量必须固定。PoseC3D 在训练时通常只关注主要人物或者统一设置一个 max person 数量并对空缺做填充。我在导出时默认保留主人物 tracklet其他人物丢弃并通过 meta 信息记录该决策方便以后调整策略。第三个硬约束是关键点顺序必须和模型训练时用的关节点方案一致。我使用的是 COCO 17 点顺序工具导出时严格按这个顺序重排不允许混入自定义顺序。这个细节最容易被忽略一旦顺序错了模型的 loss 会一直居高不下而且不容易从报错里看出来。4.2 归一化方式的选型与实现标注工具导出的坐标是原始像素坐标真正喂进 PoseC3D 之前还要做归一化。我整理过三种常用方式各有适用场景归一化方式做法优点缺点全图像归一化坐标直接除以图像宽高实现最简单解码链路短人物尺幅变化大时同一动作数值差异大bbox 中心 尺度归一化以检测框中心为原点按框的边长做缩放对人物尺度变化鲁棒适合单人动作对跟踪框稳定性要求高骨骼长度归一化以躯干骨长度为尺度基准与人体实际比例解耦动作幅度归一化好主干点被遮挡时容易失效我最终选用 bbox 中心 尺度归一化理由很实际项目里大部分视频是固定机位人物距离忽远忽近全图像归一化会让同一动作的数值范围差异过大。而躯干归一化依赖肩髋两点稳定可见在肢体交叉场景下恰恰是这两点经常被遮挡。bbox 中心 尺度归一化是折中方案跟踪策略已经提供了相对稳定的检测框这个方案在单人和多人场景下都能兼容。具体实现里归一化参数会写进 meta 文件的 normalize_version 字段。为什么要单独加这个字段因为一旦升级归一化脚本旧数据和新数据混在一起最容易出事故。版本号至少能保证出问题时快速追溯不至于重新排查一遍所有 clip。4.3 导出目录与版本化管理每个动作 clip 最终导出为一个独立目录目录组织如下dataset/ push_up/ clip_0001/ keypoints.npz meta.json review_log.jsonl clip_0002/ ... squat/ ...meta.json 里存关键信息{ video_id: clip_0001, fps: 25, duration_frames: 100, action_id: 12, tracklet_id: 0, normalize_version: bbox_center_v2, keypoint_scheme: coco17 }review_log.jsonl 记录每个修订动作的原值、新值、操作人和帧号。它的价值在后续排查阶段非常明显如果某个类别训练效果特别差我直接按关键点统计人工修正频率快速定位到是肘关节还是膝盖最不稳定再针对性补数据。5. 实测效率与踩坑记录5.1 标注效率对比从 1.0 到 2.0工具升级完成后我让两组标注员在相同的视频集上各做一轮对比测试。数据集包含单人常规动作 clip 约 2000 段、多人交互动作 clip 约 600 段。统计口径是每分钟能产出的有效帧数以及人均每日能完成的 clip 数。场景标注方式每分钟有效帧数人均每日完成 clip 数单人常规动作1.0 手工逐帧8 到 12约 30单人常规动作2.0 预标注 复核40 到 60约 150多人交互动作2.0 预标注 复核20 到 25约 60这个测试结果说明预标注 复核的模式在单人场景下提升接近 5 倍多人交互场景下提升约 2 倍。多人场景提升小是符合预期的因为身份 ID 判断和串线修正占了大量人工时间。整体项目标注时间从预估的 21 人日降到 7 人日左右这还没算返工次数的减少。1.0 时代那种标完一批发现帧号偏移全部重来的事故在 2.0 里基本绝迹了。5.2 三个值得展开的踩坑记录先说第一个坑高置信度假象。预标注在快速肢体动作和肢体与躯干重叠时经常给出 0.9 以上的置信度关键点位置却完全错位。典型场景是手掌快速贴近脸部时置信度虚高但关键点其实贴在下巴上。解决办法是复核界面增加最近 3 帧外推位置参考线当预标注点的位置与外推位置差距超过设定阈值时自动打上可疑标记。置信度可以骗人运动学一致性不容易骗人。第二个坑是线性插值在动作端点漂移。插值会不自觉地缩小动作幅度手臂快速甩动时中间帧的肘关节位置被拉成直线与真实弧线出入很大。后来我规定首尾锚点帧强制人工标注插值只填中间帧并且插值结果必须经过骨长比例校验校验不过就不给通过。这个组合策略在实际使用中效果稳定。第三个坑是 tracklet 合并后时序断裂。多人交互场景很容易把一个长动作切成很多小段人工合并后有时帧号范围对不上训练采样时就会超出视频边界。解决的方案是做合并前自动帧区间校验校验内容包括区间连续性、交叠判断、总时长核对任何一项不过都弹出调整界面不强制写入。宁可多花一次人工操作也不能留一个坏数据在路上。5.3 质量校验三板斧导出前的质量校验是我最后坚持要加的一环三个检查手段看起来简单在实际项目中救过很多次场。第一板斧是骨长比例检查。同一 tracklet 内大腿骨、上臂骨这类相对刚性的骨架区域长度比例漂移超过 15% 就标为可疑。为什么用比例而不是绝对长度因为不同人的骨架尺寸差异大绝对长度没法一概而论但同一个人的短期视频里骨长应当基本恒定。这个指标对判断关键点跳到了别人身上非常有效。第二板斧是帧间速度检查。把关键点位移按帧率换算成实际速度手腕这类末梢关节瞬时速度超过合理阈值就标记。它抓的是插值和预标注最容易犯的突兀跳动型错误和骨长比例检查互为补充。第三板斧是类别抽样复核。每类动作随机抽 10% 的 clip 做整段人工复核重点看时序一致性和身份一致性而不是单帧坐标精度。这个复核主要用来发现体系性问题比如某种动作类型下预标注的系统性偏移如果发现就立即回炉重新处理这一类数据而不是逐帧打补丁。做这套 2.0 版本让我最意外的是工具升级顺带把数据质量的治理也解决了很大一部分。以前我总觉得标注工具只是搬砖界面谁写都一样真正做完这轮重构才发现标数据这件事已经不是用鼠标把点移到位置那么简单它牵涉到模型训练、身份跟踪、时序一致性、导出规范一整条链路。用大白话总结我的体会标注工具的上限决定了模型迭代的下限。下一次我会接着讲 yolo26s-pose 预训练权重在这条数据管线上怎么微调以及 PoseC3D 训练过程中实际踩到的坑如果你也正在搭建类似的动作识别数据管线可以先照着这个思路把标注工具理顺再来追模型部分。