具身智能机器人数据格式 · 扫盲总结

一份给"入门者速查"的笔记。
覆盖 7 种主流格式:rosbag / MCAP / TFRecord / HDF5 / Zarr / Parquet / RLDS / LeRobot

主线:机器人数据要经历三段旅程——先被录下来,再存到硬盘,最后加上语义变成训练数据。七种格式,就分布在这三段上。


〇、最重要的一张图:三层模型

初学者最大的困惑是"这些格式怎么感觉是一类又不是一类"。真相是它们不在同一层

┌──────────────────────────────────────────────────────────────┐
│ ① 采集层   "记录时间上发生了什么"                             │
│            —— 像行车记录仪,按时间录下机器人的一切            │
│            rosbag、MCAP                                       │
└──────────────────────────────────────────────────────────────┘
                  │  转换(对齐时间、切分回合、定义动作)
                  ▼
┌──────────────────────────────────────────────────────────────┐
│ ② 存储层   "字节在硬盘上怎么摆"                               │
│            —— 像仓库货架,只管高效存取,不懂内容是什么        │
│            TFRecord、HDF5、Zarr、Parquet                      │
└──────────────────────────────────────────────────────────────┘
                  │  加语义(标注哪是观测、哪是动作、回合边界)
                  ▼
┌──────────────────────────────────────────────────────────────┐
│ ③ 语义层   "这些数据是什么意思,怎么喂给模型"                 │
│            —— 像贴好标签的成品,拿来就能训练                  │
│            RLDS、LeRobot                                      │
└──────────────────────────────────────────────────────────────┘

一句话类比

采集层是"摄像机拍的原始录像",存储层是"录像存进什么样的硬盘格式",语义层是"剪辑好、打上字幕、能直接用的成片"。

关键认知:语义层 = 存储层 + 一层约定。

  • RLDS = TFRecord(存储)+ RL 风格的字段约定
  • LeRobot = Parquet + MP4(存储)+ 目录规范

一、采集层:机器人的"行车记录仪"

机器人运行时,各个传感器(相机、关节编码器、力传感器…)不停产生数据。采集层就是把这些数据流按时间原样录下来

特点:它只知道"某时刻、某个数据通道、来了一条消息",不懂什么是"一次抓取"、什么是"动作"。是原材料,不是成品。

rosbag(ROS 1 的录像格式)

维度 说明
是什么 ROS 1 生态的标准录制格式,扩展名 .bag
怎么工作 把各个话题(topic)的消息按时间交错存进一个文件,攒一批压缩一次
优点 ROS 原生、工具链成熟、能快进回放
致命缺点 索引在文件末尾 → 录制中途崩溃/断电,索引没写成,整个文件基本报废
其他缺点 解析强依赖 ROS 环境;单文件太大后变慢;消息定义一改,旧文件可能读不了

科普比喻:像老式录像带——能录能放,但你得用配套的机器(ROS)才能读,而且录到一半停电,带子可能就废了。

MCAP(ROS 2 的现代录像格式)

维度 说明
是什么 ROS 2 推荐的录制格式,扩展名 .mcap,是 rosbag 的现代替代
核心改进 文件头尾都有标记,崩溃了也能抢救出已录数据 ②把"消息说明书"完整存进文件,脱离 ROS 也能读 ③支持消息格式升级
通用性 不绑定 ROS,能存任意数据(ROS 消息、protobuf、JSON 都行),是通用"时序黑匣子"

rosbag → MCAP 的进化,一句话

把 rosbag 里"隐式依赖 ROS 环境"的东西,全部显式写进文件本身,从"ROS 专用录像机"升级成"自带说明书、摔不坏的通用黑匣子"。

科普比喻:从录像带升级到"带元数据、能容错的数字视频文件"。


二、存储层:只管"怎么存字节"的通用仓库

采集完的原始数据,经过处理(时间对齐、切分回合、定义动作)后,需要存到硬盘反复用于训练。存储层就是"存字节的技术方案"。

核心:这一层的格式都不是机器人专用的——它们是通用工具,存图像、存文本、存表格都行,压根不知道"机器人"这回事。它们只关心一件事:字节怎么摆才能存得省、读得快。

四个成员,按"数据形状"分成两类:

存"多维数组"的(适合图像、张量)

图像天生是多维数组(高×宽×通道),关节序列也是(帧×关节)。这类数据用下面两个:

HDF5
维度 说明
是什么 经典科学计算格式(20+ 年历史),扩展名 .h5
本质 把一个文件系统塞进单个文件——文件内部有"文件夹"(Group)和"文件"(Dataset),像个微型硬盘
优点 自带索引(随机读快)、自描述(形状类型都在文件里)、单文件好搬运、原生支持多维数组
缺点 不能多进程同时写云存储(S3)上很慢、文件头坏了可能整个文件打不开
谁在用 RoboMimic、ALOHA/ACT 等大量学术数据集

科普比喻:一个"自带目录索引的超级文件夹",但整个装在一个盒子里——本地用很爽,搬到云上就笨重。

Zarr
维度 说明
是什么 HDF5 的"云原生版本",扩展名是个 .zarr/ 文件夹
本质 和 HDF5 一样是"数组容器",但把数据切成小块(chunk),每块存成一个独立文件
优点 多进程能并行读写(各写各的块)、云存储友好(每块是独立对象,按需下载)、坏一块只丢一块
缺点 小文件爆炸(一个大数据集可能几百万个小文件,拷贝/备份痛苦)、生态比 HDF5 年轻

HDF5 vs Zarr 一句话

同样是存多维数组。HDF5 = 装进一个文件(本地单机爽),Zarr = 摊开成一个文件夹、每块一个文件(云端、多进程爽)。

科普比喻:HDF5 是"一整本书",Zarr 是"活页夹,每页单独抽取"——多人同时看、放云端,活页夹更方便。

存"表格"的(适合状态、动作、数值)

关节角度、动作指令、时间戳、奖励——这些是"一行一行的结构化数值",更像 Excel 表。用下面这个:

Parquet
维度 说明
是什么 大数据领域的表格标准格式,扩展名 .parquet
灵魂特性 列式存储——同一列的数据存在一起(而不是同一行存在一起)
为什么强 只读你要的列(训练只要 action?其他列碰都不碰,省 I/O)②压缩率极高(同列同类型,好压)③能被 pandas/SQL 直接分析
关键约定 图像不放 Parquet(会破坏列式优势),图像另存视频,用 frame_index 列关联
谁在用 LeRobot 的数值部分就是 Parquet

"列式存储"是唯一需要理解的概念,科普可以这样讲:

传统表格按"行"存(一个人的所有信息挨在一起)。Parquet 按"列"存(所有人的年龄挨在一起、所有人的身高挨在一起)。好处:你只想统计"平均年龄",只需读"年龄"这一列,不用把每个人的全部信息都翻出来。

TFRecord(存"线性记录流",比较特殊)

维度 说明
是什么 Google 为 TensorFlow 造的通用序列化格式,扩展名 .tfrecord
本质 一串首尾相接的记录,每条 = 一段带校验的字节,简单粗暴
特点 适合顺序流式读、天然可分片并行;但无内建索引(随机读慢)、只支持 3 种数据类型
地位 它是 RLDS 的地基——RLDS 就是在 TFRecord 上加语义盖起来的

科普比喻:像一长串首尾相连的"数据信封",一个接一个拆——适合从头到尾顺着读,不适合"直接翻到第 500 个"。

存储层四格式速查表

格式 数据模型 最擅长 落盘方式 机器人里存什么
TFRecord 线性记录流 顺序读、分片 单/多文件 整条样本打包
HDF5 单文件树+数组 本地、自包含 单文件 图像+状态一锅端
Zarr 文件夹+数组 云端、并行 多文件夹 大规模图像/张量
Parquet 列式表格 选择性读列、分析 单文件 数值(状态/动作)

三、语义层:贴好标签、能直接训练的"成品"

存储层只是把字节存下来,但它不知道"哪个字段是观测、哪个是动作、一个回合从哪到哪、这个任务是什么"。语义层就是在存储层之上,加一套约定,把裸数据变成有意义、能直接喂给模型的训练数据集

两条路线,哲学相反:

RLDS(Google 路线)

维度 说明
是什么 Reinforcement Learning Datasets,Google 的规范 + 加载库
建在谁上 TFRecord
数据模型 嵌套:数据集 → 一串回合(episode) → 每回合一串步(step)
回合边界 靠每帧的 is_first/is_last 标记位来"逻辑地"切分
图像存法 每帧 JPEG 塞进记录里(无跨帧压缩)
生态 TensorFlow / JAX,适合超大规模、TPU 训练
血统 强化学习(自带折扣因子等 RL 字段)
代表 OXE(Open X-Embodiment)、RT-1、RT-2

LeRobot(HuggingFace 路线)

维度 说明
是什么 HuggingFace 主推的机器人数据集规范
建在谁上 Parquet(数值)+ MP4(图像)+ JSONL(元数据)
数据模型 扁平:一行一帧,一个回合一个 Parquet 文件
回合边界 靠文件切分(换文件=换回合),直观
图像存法 编码成 MP4 视频(利用帧间相似性,更省空间)
贴心设计 内置归一化统计量(stats)、fps 等,拿来即训
可读性 JSON/Parquet 能直接打开看、视频能直接播放,调试友好
生态 PyTorch / HuggingFace,社区上手快

LeRobot 的目录结构(科普时可展示)

franka_pick_dataset/
├── meta/          ← 说明书:有哪些字段、每回合多长、任务是什么、归一化统计量
├── data/          ← 数值(Parquet):状态、动作、奖励、时间戳
└── videos/        ← 图像(MP4):相机画面

三个分区一目了然:说明书 + 数值 + 画面,用 frame_index 把数值和画面对齐。

RLDS vs LeRobot 终极对照

RLDS LeRobot
存储地基 TFRecord Parquet + MP4 + JSONL
数据模型 嵌套(回合套步) 扁平(一行一帧)
回合边界 is_first/is_last 标记 文件切分
图像 JPEG 塞记录(无跨帧压缩) MP4 视频(跨帧压缩,更省)
可读性 ❌ 二进制,要写代码 ✅ 直接看,可视化方便
归一化 ❌ 自己算 ✅ 内置
生态 TensorFlow/JAX PyTorch/HuggingFace
血统 强化学习 模仿学习/监督学习
代表 OXE、RT 系列 HuggingFace 生态、众多开源 VLA

一句话总结两条路线

RLDS 走"Google/TensorFlow/大规模"路线(OXE 用它);LeRobot 走"HuggingFace/PyTorch/可读易上手"路线(社区新宠)。如今常把 OXE 数据转成 LeRobot 用 PyTorch 训——两者实践中会互转。


四、贯穿始终的两个"共同智慧"(科普加分点)

讲完七个格式,有两个设计思想反复出现,点出来能让科普更有深度:

1. “自描述”——把说明书装进数据本身

好格式都在做同一件事:不依赖外部文档,把"这数据长什么样"写进文件自己

  • MCAP 内嵌消息 schema、HDF5 内嵌形状类型、Zarr 有 zarr.json、Parquet 有 Footer、RLDS 有 dataset_info、LeRobot 有 info.json

科普讲法:好的数据格式像"自带说明书的家具"——不用另找图纸,拆开箱子说明书就在里面。

2. “chunk”(分块)——但两层含义不同,别混

"chunk(块)"这个词在两层都出现,但意思不一样,这是初学者最容易混的点:

采集层(rosbag/MCAP) 的 chunk = 一批消息打包   (沿"时间"切)
存储层(HDF5/Zarr)   的 chunk = 大数组切成瓷砖 (沿"数据维度"切)

记忆口诀:采集层的块沿时间切消息,存储层的块沿维度切数组。


五、实用速查:我该用哪个?

按场景选

你的场景 推荐
ROS 机器人实时采集 MCAP(ROS 2)/ rosbag(ROS 1)
PyTorch 训练、想快速上手、要常看数据 LeRobot
TensorFlow/JAX、超大规模、混合多数据集 RLDS
本地单机、数据不太大、要方便拷贝 HDF5
云端(S3/GCS)、多进程、超大规模 Zarr
只存数值做分析/统计 Parquet

后训练实习生的"够用就好"版认知

做后训练,你 90% 的时间面对的是 LeRobotRLDS(因为你拿到的是别人处理好的训练数据)。

  • 看到 RLDS → 想到"OXE 那套、TensorFlow、for episode: for step 读”
  • 看到 LeRobot → 想到"HuggingFace 那套、PyTorch、meta/ + data/ + videos/ 目录、dataset[i] 取帧"
  • 底层的 TFRecord/Parquet/HDF5/Zarr 知道是什么、谁是谁的地基即可,一般不用手动碰。
  • rosbag/MCAP 是采集端的事,除非你要从原始数据做转换,否则接触不多。

六、七格式一句话总表(终极速查)

格式 一句话
① 采集 rosbag ROS 1 录像带,能录能放,但摔了(崩溃)容易废
① 采集 MCAP ROS 2 数字黑匣子,自带说明书、摔不坏、还通用
② 存储 TFRecord 一串数据信封,顺着拆,RLDS 的地基
② 存储 HDF5 装进一个盒子的"带索引超级文件夹",本地爽
② 存储 Zarr 摊成活页夹的 HDF5,云端、多进程爽
② 存储 Parquet 按列存的 Excel,只读你要的列,LeRobot 的地基
③ 语义 RLDS TensorFlow 路线,嵌套结构,OXE 用它
③ 语义 LeRobot PyTorch 路线,目录清晰、可读易上手,社区新宠

附:科普讲解建议的叙事线

如果做一期科普视频/文章,推荐这条线索(由浅入深、有故事感):

  1. 抛问题:机器人是怎么"学会"干活的?靠喂给它海量"看到什么→做什么"的数据。那这些数据长什么样、存哪儿?
  2. 讲旅程:一条数据要走三步——录下来(采集)→ 存起来(存储)→ 贴标签变教材(语义)。(用三层图)
  3. 每层挑代表讲:采集讲 rosbag→MCAP 的"录像带进化数字黑匣子";存储讲"数组仓库(HDF5/Zarr) vs 表格仓库(Parquet)";语义讲 RLDS vs LeRobot 两条路线之争。
  4. 升华:好格式的两个共同智慧——"自带说明书(自描述)“和"化整为零(分块)”。
  5. 落地:如果观众也想入门具身智能,看到 LeRobot / OXE(RLDS) 这两个词,心里就有底了。

核心心法:别陷进字节细节。科普的价值是"建立地图和直觉",不是"背技术手册"。每个格式记住"它像什么、解决什么问题、谁在用"就足够打动大众了。


七、实战篇:同一份 Franka 数据,七种格式长什么样

前面讲的是"概念地图"。这一节把地图落到具体数据——用同一个例子,看每种格式的真实排布和关键参数含义。适合初学者对着"摸清每个字段到底是什么"。

统一实验设定

任务:Franka Panda 机械臂从桌面抓起一个红色方块。
录制:相机 30 FPS,我们截取 3 帧(一次抓取的开始/中间/抓住)。

这 3 帧的原始数据(后面所有格式都基于它,请记住这张表):

帧   时间(s)  7个关节角度(rad)                        夹爪宽度(m)  8维动作                              奖励
──────────────────────────────────────────────────────────────────────────────────────────────────────
帧0   0.000   [0.10,-0.30, 0.00,-1.20, 0.00,1.50,0.80]   0.08      [ 0.01,-0.02,-0.01,0,0,0, 0.0,0]    0.0
帧1   0.033   [0.15,-0.35,-0.05,-1.10, 0.02,1.45,0.75]   0.08      [ 0.00, 0.00,-0.02,0,0,0,-0.03,0]    0.0
帧2   0.066   [0.20,-0.40,-0.10,-0.90, 0.05,1.40,0.70]   0.02      [ 0.00, 0.05, 0.10,0,0,0, 0.0,0]    1.0
                                                          ↑抓住了                                        ↑成功给奖励

每个字段/参数的物理含义(这是初学者最该搞懂的部分):

字段 形状 含义 关键点
图像 image (480,640,3) 相机 RGB 画面,高480×宽640×3通道 每个像素 0~255 的 uint8;这是模型的"眼睛"
关节角度 joint_positions (7,) Franka 有 7 个可转动关节,每个的当前角度(弧度) Franka 是 7 自由度机械臂,所以是 7 个数
夹爪宽度 gripper_width (1,) 两指张开的距离(米) 0.08=全张开,0.02=夹住了方块
动作 action (8,) 告诉机器人"下一步怎么动" 见下方拆解 ↓
奖励 reward (1,) 这一步做得好不好(RL 用) 抓住瞬间给 1.0,其余 0.0
时间戳 timestamp (1,) 这一帧的时刻(秒) 用于对齐图像和数值

8 维动作 action 拆解(初学者常问"这 8 个数是啥"):

action = [ dx,  dy,  dz,  droll, dpitch, dyaw, gripper, pad ]
           └───┬────┘    └────────┬─────────┘    └──┬──┘  └┬┘
          末端位置增量        末端姿态增量        夹爪指令  补位
          (往哪移动,米)     (怎么转动,弧度)    (开/合)   (凑够8维)

例: 帧2的 [0, 0.05, 0.10, 0,0,0, 0.0, 0]
   = 末端往 y 方向移 0.05m、往 z 方向(上)提 0.10m(把方块提起来)、姿态不变

概念补充:为什么动作是"增量(delta)“而不是"绝对位置”?
大多数具身策略输出的是"相对当前位置移动多少"(如 dz=0.10 表示"往上抬 10cm"),而不是"移动到坐标 (0.3, 0.2, 0.5)“。因为相对量更好学、更泛化。这类动作空间叫 末端笛卡尔增量控制 (delta end-effector control),是 OXE、LeRobot 里最常见的一种。你也会遇到"绝对关节角度”"绝对末端位姿"等其他动作空间——看数据第一件事就是搞清楚 action 的每一维是什么


逐格式看排布

① rosbag / MCAP(采集层)——按时间交错的消息流

采集层不认识"帧"“动作”,它只有"某时刻、某话题、一条消息"。同样这 3 帧,在 MCAP 里是这样按时间铺开的:

pick.mcap  (话题: /camera 图像, /joint_states 关节)

时间轴 →
t=0.000  /camera       → 一帧图像(480×640×3)
t=0.000  /joint_states → position=[0.10,-0.30,0.00,-1.20,0.00,1.50,0.80]
t=0.033  /camera       → 一帧图像
t=0.033  /joint_states → position=[0.15,-0.35,-0.05,-1.10,0.02,1.45,0.75]
t=0.066  /camera       → 一帧图像
t=0.066  /joint_states → position=[0.20,-0.40,-0.10,-0.90,0.05,1.40,0.70]

关键观察 / 参数含义

概念 含义 初学者注意
topic(话题) 一个数据通道,如 /camera/joint_states 不同传感器 = 不同 topic
消息按时间交错 图像和关节不是分开存,是按时间戳穿插 和"整齐的表格"完全不同
这里没有 action! 采集时只有"状态",没有"动作" 动作是后期算出来的:如"下一帧关节 − 当前帧关节",或记录遥操作指令
时间戳可能不齐 相机 30Hz、关节可能 1000Hz,频率不同 转成训练数据时要做时间对齐,给每帧图像配上最近的关节

这就是"采集层→存储层"要做的转换:把交错的消息流,对齐时间、切好回合、算出动作,整理成下面那种整齐的结构。


② HDF5(存储层)——装进一个文件的树

3 帧数据在 HDF5 里,按"树形"组织(一个 episode 一个 Group):

franka_pick.h5
│  【根属性 Attribute】
│  @robot_type = "franka_emika_panda"
│  @fps = 30
│
└─ episode_0/                        ← Group(这次抓取)
   │  @task = "pick up the red block"
   │  @success = true
   │
   ├─ observations    形状(3, 480, 640, 3)  类型uint8   ← 3帧图像
   ├─ joint_positions 形状(3, 7)            类型float32 ← 3帧×7关节
   ├─ gripper_width   形状(3,)              类型float32 ← [0.08, 0.08, 0.02]
   └─ actions         形状(3, 8)            类型float32 ← 3帧×8维动作

关键参数含义

参数 含义
形状 shape 如 (3, 7) 第一维 3=帧数,第二维 7=关节数。读懂 shape 就读懂了数据维度
类型 dtype uint8 / float32 图像用 uint8(0~255省空间),数值用 float32
Group episode_0 相当于文件夹,一个回合一个
Attribute(@) @fps=30 挂在树节点上的小标签,存元信息
chunk(分块) 常设 (1,480,640,3) 一帧一块,随机读某帧时只解压那一块

读法:file["episode_0/actions"][2] → 直接拿到帧2的动作 [0,0.05,0.10,...]


③ Zarr(存储层)——摊成文件夹,每块一个文件

同样的数据,Zarr 把每个数组切块存成独立文件

franka_pick.zarr/
├── zarr.json                       ← 根说明书(JSON纯文本,可直接打开)
│      {"attributes": {"robot":"franka", "fps":30}}
│
├── observations/
│   ├── zarr.json                   ← 这个数组的说明书 ↓
│   │     {"shape":[3,480,640,3], "data_type":"uint8",
│   │      "chunk_grid":{"chunk_shape":[1,480,640,3]},   ← 一帧一块
│   │      "codecs":[{"name":"blosc"}]}                  ← 压缩方式
│   └── c/
│       ├── 0/0/0/0   ← 帧0的图像(独立文件)
│       ├── 1/0/0/0   ← 帧1
│       └── 2/0/0/0   ← 帧2
│
├── joint_positions/  (shape[3,7])   c/0/0, c/1/0, c/2/0
├── actions/          (shape[3,8])   c/0/0, c/1/0, c/2/0
└── gripper_width/    (shape[3])     c/0

关键参数含义

参数 含义 和 HDF5 的区别
zarr.json 每个数组的说明书,JSON 明文 HDF5 藏在二进制里,Zarr 直接可读
shape / data_type 同 HDF5:形状 + 类型 一样
chunk_shape [1,480,640,3] 每块的大小=一帧 决定切成几个文件
codecs: blosc 压缩算法(blosc 很快)
文件名 c/2/0/0/0 chunk 的坐标:第2帧、其余维第0块 每块是独立文件,这是 Zarr 的灵魂

④ Parquet(存储层)——列式表格(注意:不存图像)

数值部分(状态/动作/奖励)用 Parquet,是一张表。图像不在这(另存视频):

franka_pick.parquet  →  逻辑上就是一张表:

 timestamp  frame_index  observation.state          action                      next.reward
 0.000      0            [0.10,-0.30,...,0.80]      [0.01,-0.02,...,0.0]        0.0
 0.033      1            [0.15,-0.35,...,0.75]      [0.00, 0.00,...,-0.03]      0.0
 0.066      2            [0.20,-0.40,...,0.70]      [0.00, 0.05,...,0.0]        1.0

物理上按"列"存(列式):
  timestamp列:    [0.000, 0.033, 0.066]        ← 挨在一起
  action列:       [[...],[...],[...]]           ← 挨在一起
  reward列:       [0.0, 0.0, 1.0]               ← 挨在一起

关键参数含义

参数 含义 为什么重要
列式存储 同列数据物理相邻 训练只要 action 列时,其他列不用读
observation.state 就是 7 个关节角度 LeRobot 里习惯叫 observation.state
frame_index 帧号 0,1,2 关键:用它去视频里找对应帧
无图像列 图像另存 MP4 图像放表里会毁掉列式压缩优势

⑤ LeRobot(语义层)——Parquet + MP4 + JSONL 拼成完整数据集

这是初学者实际最常拿到的格式。完整目录:

franka_pick_dataset/
│
├── meta/                                    ← 说明书区
│   ├── info.json          ← schema:有哪些字段、形状、fps
│   ├── episodes.jsonl     ← 回合索引:每回合多长、什么任务
│   ├── tasks.jsonl        ← 任务表:语言描述
│   └── stats.json         ← 归一化统计量:mean/std
│
├── data/chunk-000/
│   └── episode_000000.parquet   ← 数值(上面④那张表)
│
└── videos/chunk-000/observation.images.cam_high/
    └── episode_000000.mp4        ← 3帧图像编码成的视频

四个 meta 文件的实际内容 + 参数含义

info.json(schema 说明书):

{
  "robot_type": "franka_emika_panda",
  "fps": 30,
  "features": {
    "observation.state": {"dtype":"float32", "shape":[7],
                          "names":["joint_1",...,"joint_7"]},7关节,每维有名字
    "action":            {"dtype":"float32", "shape":[8]},8维动作
    "observation.images.cam_high": {"dtype":"video",           ← 注意是"video"!
                          "shape":[480,640,3]}                 ← 表示去MP4}
}

episodes.jsonl(每行一个回合):

{"episode_index": 0, "tasks": ["pick up the red block"], "length": 3}

length:3 = 这回合 3 帧;episode_index:0 对应 episode_000000.* 文件。

stats.json(归一化统计,训练直接用):

{
  "observation.state": {
    "mean": [0.15,-0.35,-0.05,-1.07,0.02,1.45,0.75],7关节各自的均值
    "std":  [0.04, 0.04, 0.04, 0.12,0.02,0.04,0.04]   ← 各自的标准差
  }
}

→ 训练时 归一化后 = (原值 − mean) / std,让数据分布规整,模型好学。

一帧是怎么被"拼出来"的(初学者关键):

想要"帧2"的完整数据:
  ① 从 episode_000000.parquet 第2行读:
       state=[0.20,...], action=[0,0.05,0.10,...], reward=1.0, frame_index=2
  ② info.json说 cam_high 是"video" → 去 episode_000000.mp4 解码第2帧 → 得到图像
  ③ 拼成: {观测图像 + observation.state + action}  → 喂给模型
        └─ 图像来自MP4,数值来自Parquet,靠 frame_index=2 对齐

关键参数速查

参数 含义
fps 帧率,30 表示每秒 30 帧
features 数据集的 schema,声明每个字段的 dtype/shape
dtype: “video” 该字段图像存在 MP4 里,不在 Parquet
length 一个回合有多少帧
mean / std 归一化用的均值和标准差
frame_index 缝合 Parquet 数值 和 MP4 画面 的帧号

⑥ RLDS(语义层)——嵌套结构,OXE 那套

同样数据在 RLDS 里,是"回合套步"的嵌套结构,一个"步(step)"长这样:

# 一个 episode = 3 个 step 的序列;下面是帧0的 step
step_0 = {
    "observation": {
        "image":            <(480,640,3) uint8>,          # 相机
        "joint_positions":  [0.10,-0.30,0.00,-1.20,0.00,1.50,0.80],
        "gripper_width":    0.08,
    },
    "action":   [0.01,-0.02,-0.01, 0,0,0, 0.0, 0],        # 8维动作
    "reward":   0.0,
    "discount": 1.0,                                       # RL折扣因子(RLDS特有)
    "is_first": True,   ← 这是回合第一帧          ┐ 回合边界
    "is_last":  False,  ← 不是最后一帧            ┘ 靠标记位表示
    "language_instruction": "pick up the red block",
}

关键参数含义(重点看和 LeRobot 的差异):

参数 含义 和 LeRobot 对比
observation(嵌套字典) 观测里再分 image/joint/gripper LeRobot 是扁平的一行
is_first / is_last 标记回合的第一帧/最后一帧 RLDS 靠标记位切回合;LeRobot 靠文件切
discount RL 折扣因子(未来奖励打折) LeRobot 没有(它偏模仿学习)
图像 JPEG 直接塞进记录 LeRobot 用 MP4(跨帧压缩更省)
读法 for episode: for step: 双层循环 LeRobot 是 dataset[i] 取帧

七格式实例总览(同一份 Franka 数据)

格式 3帧数据的组织形态 action 在哪 图像在哪 回合边界怎么表示
MCAP 按时间交错的消息流 ❌ 采集时没有,后期算 /camera 话题 无(采集层不分回合)
HDF5 树: episode_0/{obs,joints,actions} actions 数组(3,8) observations 数组 Group 分组
Zarr 文件夹: 每数组切块成文件 actions/c/* observations/c/* Group 目录
Parquet 一张表(列式) action 列 ❌ 不存(另存视频) (单文件=单回合)
LeRobot meta+data(parquet)+videos(mp4) Parquet 的 action 列 MP4 视频 一回合一组文件
RLDS 嵌套: episode→steps 每个 step 的 action JPEG 塞进 step is_first/is_last 标记

给初学者的三条实操建议

  1. 拿到任何数据,先看三样东西action 每一维是什么(增量?绝对?)、observation 有哪些(几个相机?有没有本体状态?)、回合怎么切分。搞懂这三样,数据就"通"了。
  2. shape 是你的路标:看到 (T, 7) 就知道 T 帧、7 个关节;(T,480,640,3) 就知道是图像序列。读 shape = 读结构。
  3. 归一化统计量(mean/std)别忽略:训练前几乎都要归一化,LeRobot 直接给了(stats.json),RLDS/裸 HDF5 可能要自己算。数据量纲不统一,模型很难训。

总结这一节:概念地图 + 具体实例 = 真正"看懂数据"。下次你打开一个 LeRobot 或 OXE 数据集,对照这里的 Franka 例子,每个字段、每个参数就都对得上号了。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐