LingBot-Video 看输入管线、数据标注看动作对齐、评测看回放一致性

文章导读
只有原始视频,先别急着上模型。具身任务的数据链条通常分三段:输入管线决定模型看到什么,动作标注决定监督信号对不对,评测口径决定两次结果能不能比。这三段里,输入管线要盯住「每一步转换发生在哪、产出物落在哪」;动作标注要盯住「标注时间戳与视频帧是否同一基准」;评测要盯住「同一批输入重复跑,输出是否一致」。下面的做法都是可以在配置、日志、命令或页面行为上验证的,不预设特定 SDK。
📋 目录
  1. A 把从原始视频到模型输入的链路画出来
  2. B 检查动作标注与视频帧是否对齐
  3. C 用同一批数据跑一次一致性回放
  4. D 给输入字段写一份最小配置样例
  5. E 把标注与评测口径固化成清单
A A

只有原始视频,先别急着上模型。具身任务的数据链条通常分三段:输入管线决定模型看到什么,动作标注决定监督信号对不对,评测口径决定两次结果能不能比。这三段里,输入管线要盯住「每一步转换发生在哪、产出物落在哪」;动作标注要盯住「标注时间戳与视频帧是否同一基准」;评测要盯住「同一批输入重复跑,输出是否一致」。下面的做法都是可以在配置、日志、命令或页面行为上验证的,不预设特定 SDK。

拿到一批原始视频做具身相关任务时,建议先把链路拆成原始封装、抽帧、缩放、张量化四步并各自落盘留档,再确认动作标注的时间基准与帧号基准一致,最后用固定清单重复回放比对输出。能验证的是字段是否齐全、偏移是否超阈值、两次回放是否一致;不能验证的是模型精度好坏。偏移阈值和重复次数需要结合任务的时序容忍度确认,别默认沿用别处的口径。

把从原始视频到模型输入的链路画出来

建议在写代码前先把数据流写成一张图,明确每步转换发生的位置。典型顺序是:采集端产出原始封装文件 → 解码得到解码帧流 → 按采样帧率抽帧 → 按目标分辨率缩放任一色彩转换 → 归一化后拼成批张量 → 模型输入。转换发生在哪一步,决定了出问题时去哪一层看日志。

  • 采样帧率:抽帧这一层决定,写在管线配置里,例如 fps_sample: 10。抽帧后必须把「源帧号 ↔ 抽帧后帧号」的映射表落盘,否则后面标注对不上。
  • 分辨率缩放:抽帧之后做,尽量只做一次。缩放写在抽帧层还是张量层,要在配置里写清楚,避免两处都缩放。
  • 动作标注文件格式:建议用按行 JSON(jsonl),每行一条动作,至少含 timestamp、frame_index、action 三个阶段字段,与抽帧映射表使用同一套时间基准。
  • 产出物存放位置:建议固定目录约定,便于回放时取同一批文件。
data/raw/ep_0001.mp4          # 原始封装,不改动
 data/decode/ep_0001.frames    # 解码帧序号与源 PTS 映射
 data/frames/ep_0001/          # 抽帧结果,文件名含抽帧后帧号
 data/anno/ep_0001.actions.jsonl
 data/meta/ep_0001.pipeline.json  # 本次管线参数快照

每一步都留一份参数快照,是后面排查偏移和回放不一致时最省事的做法。

检查动作标注与视频帧是否对齐

对齐问题在训练和评测里的表现不一样:训练时是动作与观测错位,评测时是分数不可复现。建议先跑一遍自动抽查,再对超阈值样本人工看。

LingBot-Video 看输入管线、数据标注看动作对齐、评测看回放一致性
import json
fps = 30  # 与抽帧配置一致
mapping = {m["frame_index"]: m["source_pts"] for m in
           (json.loads(l) for l in open("data/decode/ep_0001.frames"))}
for line in open("data/anno/ep_0001.actions.jsonl"):
    a = json.loads(line)
    expected = round(a["timestamp"] * fps)
    if a["frame_index"] not in mapping:
        print("NO_FRAME", a["frame_index"])
    elif abs(expected - a["frame_index"]) > 2:
        print("MISALIGN", a["frame_index"], expected)
  • 允许的偏移范围:常见做法是先按 ±1 帧作为正常、±2 帧以上进入人工复核的初筛线;抓取类任务对时序更敏感,具体阈值需要结合任务的动作频率和采集帧率确认,不要照搬。
  • 人工抽查方法:从标注文件里等间隔抽若干条,把对应帧导出成图,看动作起始帧与标注帧是否吻合,重点挑动作边界和动作切换处。
  • 发现偏移后的修正步骤:先确认时间基准(源 PTS 还是解码后帧计数),再确认抽帧是否丢帧,然后重算 frame_index,最后重新导出一份新的标注版本并记录版本号,不要就地覆盖原文件。

用同一批数据跑一次一致性回放

先把「同一批」定义清楚:写一份 manifest,列出视频 ID、抽帧目录、标注文件版本、管线参数快照路径,回放时只读这份清单里的东西。

  • 重复次数:建议同一份清单连续跑 3 次,够看出大部分非确定因素;次数可以按耗时调整,但每次都要记录。
  • 比对指标:输出动作序列的逐帧差异、分段边界位置、以及输出张量的摘要值(如哈希)。只看最终分数容易掩盖中间差异。
  • 结果不一致时优先排查:抽帧顺序是否稳定、解码后端是否一致、随机种子与推理时的采样是否固定、批内样本拼接顺序、以及时间戳取的是不是同一基准。按这个顺序查通常比乱翻日志快。

给输入字段写一份最小配置样例

自建管线可以先定义一份最小字段集,字段名和取值范围自己定,但要在文档里写死。

LingBot-Video 看输入管线、数据标注看动作对齐、评测看回放一致性
{
  "video_id": "ep_0001",
  "video_path": "data/raw/ep_0001.mp4",
  "fps_sample": 10,
  "resize": [224, 224],
  "color_order": "rgb",
  "action_file": "data/anno/ep_0001.actions.jsonl",
  "action_space": "joint_delta",
  "frame_index_base": 0
}
  • 字段缺失时的默认行为:fps_sample 缺失时建议直接拒绝启动,而不是默认按源帧率不抽帧,否则显存和耗时都会失控;resize 缺失同样建议拒绝;color_order 缺失可以默认 rgb,但要在日志里打印一行说明用了默认值;frame_index_base 缺失默认 0 并记录。
  • 字段写错时的报错:下面的文案是自建校验脚本里建议抛出的,不是某个官方工具的原文:
[cfg] unknown field "resize_w" for video_id=ep_0001, did you mean "resize"?
[cfg] missing required field "action_file" for video_id=ep_0001
[cfg] action_file not found: data/anno/ep_0001.actions.jsonl

把这三类报错分开,排查时能立刻区分是字段名写错、字段漏填还是路径不存在。

把标注与评测口径固化成清单

让不同人做出的结果能互相比较,靠的是清单而不是口头约定。

  • 标注规范条目:动作起点与终点的判定规则、连续动作是否合并、无效帧如何标记、时间戳以哪个基准填写、帧号从 0 还是 1 开始、标注文件的命名与版本号规则。
  • 评测记录表字段:manifest 路径、管线参数快照哈希、标注文件版本号、回放次数、每次的输出摘要、比对结论、执行人、执行时间。
  • 口径变更留痕:改动抽帧率、偏移阈值、动作空间定义这类会影响可比性的参数时,新开一个口径版本号,在变更日志里写清改了什么、为什么改、哪些旧结果不再与新结果直接比较。旧版本的清单和记录表保留,不要覆盖。

这三段里,只要管线的每一步落盘、标注与帧号同源、评测用固定清单重复比对,多人数、多轮次之间就有可对照的基线。