采集机器人示教数据时,相机朝向是固定的,场景光照是可控的,机械臂的动作是重复的。这些“好”的性质让视频看起来天然可靠——直到你打开第 12,000 个 episode 的第 3 号相机文件,发现整整 4 秒画面全黑。
视频里没有 token 可以统计。 这是具身智能数据质检和 NLP 数据质检的根本区别:你不能靠采样发现一段黑屏,你必须解码每一帧。而一旦解码每一帧,就有了做更深的校验的可能——比如发现两路腕部相机接反了。
这个专题分成两篇:
(一)帧级检测器的实现与标定
四层质检模型、一次解码七个指标、黑屏/冻结/模糊/曝光四个检测器,以及阈值怎么定。设计稿里的默认值在真实数据上精确率只有 0.71——本文给出实测标定值。
(二)跨模态摄像头配对校验
左右腕相机接反了,帧级质检完全看不出来。本文用腕部相机画面运动与关节运动的相关性矩阵检出它,并量化“缺失帧不能补零”这个陷阱的后果。
平台侧怎么把这些检查装进 DDL、版本化对象路径和 Airflow 调度,见 Sparrow Data Platform:从 DDL 到真实具身数据质检。
数据来源
两篇文章用的是同一份数据:allenai/MolmoAct2-BimanualYAM-Dataset(HuggingFace 官方仓库)。它不是“随手拍的双臂视频合集”,而是一份按 LeRobot v3 格式打包、有明确字段定义和许可的研究数据集。先把“它是什么”说清楚,后面的质检才有对象。
这是什么数据集
- 发布者与用途:Allen Institute for AI(Ai2)发布,作为 MolmoAct2(动作推理模型)的训练数据之一。dataset card 写的是 “more than 720 hours of training demonstrations spanning diverse tabletop manipulation tasks”;论文进一步说明这是 720 小时遥操作(teleoperated)YAM 轨迹,覆盖桌面与家居任务,并称其为最大的开源双臂数据集。
- 平台形态:
robot_type = bi_yam_follower,即一对 YAM follower 机械臂。字段名显示每臂 6 个关节 + 1 个夹爪(left_joint_0..5.pos加left_gripper.pos,右侧同理),合起来 14 维;典型桌面作业场景。 - 相机:三路,
observation.images.left/.right/.top。card 里只有键名,没有写安装位置;第二篇 3.3 节用跨模态相关性验证了 left / right 是腕部相机、top 是俯视相机。 - 规模与 split:32,246 个 episode、76,046,658 帧、34 条任务描述、30 fps;只有一个 split,
train: 0:32246(meta/info.json)。Hugging Face 页面标注仓库总大小约 2.35 TB。 - 许可:Apache-2.0(dataset card 元数据)。
- 采集方式:论文写的是遥操作轨迹;card 只说 “training demonstrations”,没有展开遥操作设备、操作员人数或采集流程。
实测规格(读自 meta/info.json)
| 项 | 值 |
|---|---|
| Episode 数 | 32,246 |
| 总帧数 | 76,046,658 |
| 帧率 | 30 fps |
| 相机 | 3 路 — observation.images.left / .right / .top |
| 编码 | AV1,640×360,yuv420p |
| 状态向量 | 14 维(前 7 维左臂含夹爪,后 7 维右臂) |
| 任务数 | 34 |
本文实际下载并解码了 4 个 mp4,覆盖 31,115 帧、1037.17 秒。这是 file-000.mp4(三路相机各一份),完整包含 episode 0–15。
所有数值都来自实际运行。 每一段代码都可以在
examples/robot-video-qc/下直接跑通;每一个阈值都附带标定时的精确率与召回率。没有任何一个数字是估计出来的。
一条 episode 由什么组成
一条 episode 不是一个 mp4 加一个 state 文件,而是同一段示教在四类文件里的切片:
| 组成 | 位置 | 粒度 | 关键字段 |
|---|---|---|---|
| 逐帧低维数据 | data/chunk-XXX/file-XXX.parquet |
一行 = 一帧 | action、observation.state、timestamp、frame_index、episode_index、index、task_index |
| 视频 | videos/observation.images.*/chunk-XXX/file-XXX.mp4 |
一个 mp4 含多个 episode | episode 的 from_timestamp / to_timestamp |
| episode 元数据 | meta/episodes/chunk-000/file-000.parquet |
一行 = 一个 episode | length、tasks、dataset_from_index / dataset_to_index、data 与各相机视频的 chunk/file index、每路相机的 from_timestamp / to_timestamp、逐特征 stats/* |
| 任务文本 | meta/tasks.parquet、meta/tasks_annotated.parquet |
任务级 / episode 级 | task_index → task;annotated 每行一个 episode 的完整指令 |
| 全局声明 | meta/info.json、meta/stats.json |
数据集级 | 特征名/形状/fps/路径模板;全局统计量 |
逐项解释(以数据集实际文件结构为准):
observation.state:14 个 float32,前 7 维左臂(6 关节 + 夹爪),后 7 维右臂。card 没有给出单位——角度还是弧度、夹爪量程是多少,都未声明,只给了关节名与维度。action:同样 14 维 float32,关节名与state完全一致。card 没有说明它是目标位置、遥操作指令还是别的量;能确定的是它与state同维度、同时间轴、每帧一条。timestamp:float32,按 LeRobot v3 的时间轴以秒为单位,每个 episode 从 0 重新开始(实测 episode 0、1 的首帧都是0.0)。实测相邻帧差为 0.0333 s,正好是 30 fps 的步长。frame_index:每个 episode 从 0 开始的帧号;index是跨 episode 连续的行号(episode 1 的首行是 1732);episode_index是全局 episode id;task_index指向meta/tasks.parquet里的任务字符串。length:该 episode 的帧数。dataset_from_index/dataset_to_index是它在全局行号上的区间,差值应等于length。- 视频时间区间:
videos/observation.images.*/from_timestamp与to_timestamp是这条 episode 在该 mp4 里的秒偏移。episode 0 的length = 1732,to_timestamp − from_timestamp = 57.733333,乘 30 正好是 1732——元数据、低维行数、视频时长三者必须闭合。 - 采样与对齐:三路视频都是 30 fps;同一帧的
state/action/timestamp在 parquet 的同一行里,天然对齐。相机与关节流的时间对应靠 episode 的 from/to 区间与帧序号。card 没有说明硬件级同步方式(是否硬触发、时间戳来自哪里)。
另外,meta/episodes 的每一行还带 stats/ 下的逐特征统计量(count、min / max / mean / std 与 q01 / q10 / q50 / q90 / q99 分位数),是 episode 级的分布摘要;meta/stats.json 则是全局的。这两层统计量可以直接用来做粗筛。
这些结构对质检意味着什么
字段和文件结构决定了两类检查:一类可以写成确定性规则,另一类必须交给人工或模型。
| 确定性检查 | 用哪些字段 | 失败意味着 |
|---|---|---|
| 行数与帧数一致 | length ↔ data 行数 ↔ dataset_to_index − dataset_from_index |
截断、丢帧、元数据写错 |
| 时间轴单调 | timestamp:首帧 0、步长 1/30、末帧约为 (length−1)/30 |
时钟漂移、掉帧、拼文件出错 |
| 视频区间闭合 | to_timestamp − from_timestamp 乘 fps 约等于 length,且解码帧数等于 length |
视频被截断、episode 边界错位 |
| 维度与数值合法 | action / state 固定 14 维、无 NaN/Inf |
录制中断、序列化损坏 |
| 任务可解析 | task_index 命中 tasks.parquet;tasks_annotated 每个 episode 恰好一行 |
指令缺失或错配 |
| 三路相机齐全 | 每个 episode 在每个 videos/* 下都有 file index 与区间 |
某路相机漏录 |
要注意:length、行数、dataset_to_index 三者一致只是必要条件,不是充分条件。如果三路视频里有一路少了 5 帧,低维数据仍然可以完全自洽——视频帧数与 length 的比对必须真的解码视频才能做。
必须人工或模型判断的部分:
- 指令语义。
task_index只能证明“有指令”,不能证明“指令描述了画面里实际发生的事”。 - 任务是否成功。动作是否合理、夹爪是否真的夹住物体,这些不在字段里。
- 相机装法是否正确。三路相机的安装位置 card 里都没写,需要跨模态证据来验证(第二篇 3.3 节)。
- 文本质量。34 条任务描述里存在近重复和不一致:
Clothes Folding与Folding CLothes,Sort waste ... dishwasher rack与只差一个句号的版本。规则只能标出候选,取舍要人看。 - 遮挡、标定漂移、光照变化。画面可能每一帧都“正常”,但语义已经不可用。
和质检流程怎么对接
一条 episode 送进质检工具时,平台至少要提供:三路视频(或可解码的帧)、逐帧 parquet 行、episode 元数据、任务文本。工具按成本从低到高跑:
- 结构校验(不解码):维度、NaN、
length/index/timestamp闭合、任务映射。 - 帧级指标(解码,最贵):三路相机逐帧提取指标,产出
frame_metrics.parquet。 - 画面检测(微秒级):黑屏、冻结、模糊、曝光,产出
quality_summary.json。 - 跨模态校验:两路腕相机与两臂的相关性,产出
ALIGNED/CROSSED/UNKNOWN。
产出至少要回答三个问题:这条 episode 能不能用(PASS / WARN / FAIL / UNKNOWN / INCOMPLETE)、哪几路相机哪个时间段坏了(半开区间)、左右腕相机有没有接反(配对判定)。
如果平台只能给出一个 mp4 和一个 state 数组、没有 episode 元数据,工具必须先重建 episode 边界(用 length / timestamp / fps 对齐),并把重建结果写进输出。否则 episode 一旦切错,后面的区间统计和相关性校验都建立在错误的帧上。
核心结论速览
| 结论 | 证据 |
|---|---|
| 设计稿的默认阈值在真实数据上不成立 | lap_var = 30.0 精确率 0.71,mad = 0.20 精确率 0.40 |
| 标定后的四个检测器精确命中真值区间 | 五个样本上全部命中 [300, 419],零误报零漏报 |
| 评测框架必须是无损的 | 有损编码把冻结帧的 MAD 从 0.0 变成 0.1088 |
| 腕部相机看得见自己的手臂 | 16 个 episode 的对角相关全为正(+0.62~+0.86),非对角全为负(−0.66~−0.14) |
| 交换相机后判定必须翻转 | 16/16 全部翻转 |
| 缺失帧不能补零 | 40% 停摆时漏掉 2/16 个真实互换,且把负相关翻成正相关 |
代码
robot_video_qc/
├── metrics.py # 指标提取(stage 1,唯一昂贵的一段)
├── detectors.py # 四个检测器 + 所有权仲裁
├── intervals.py # 半开纳秒区间
├── checks.py # L1 文件检查、L2 时间检查
├── summary.py # L4 可用性判定
├── contract.py # Parquet / JSON 契约
├── calibrate.py # 阈值扫描
├── inject.py # 缺陷注入(无损 FFV1)
├── pairing.py # 跨模态配对
└── pipeline.py # 三段式流水线
run_qc.py # 命令行入口依赖:opencv-python、pyarrow、numpy、matplotlib。实测环境 Python 3.x / OpenCV 4.10.0 / PyArrow 22.0.0 / NumPy 2.2.6 / Matplotlib 3.9.2。
pip install opencv-python pyarrow numpy matplotlib
cd examples/robot-video-qc
python run_qc.py path/to/video.mp4 --camera left --episode 7 --out out/参考
- 数据集主页(dataset card):allenai/MolmoAct2-BimanualYAM-Dataset
- 数据集文件树(
meta/、data/、videos/):Files and versions - LeRobot v3.0 格式说明(
meta/episodes、from_timestamp、任务映射等约定):LeRobotDataset v3.0 - 论文:MolmoAct2: Action Reasoning Models for Real-world Deployment(arXiv:2605.02881)