启用视频存储后,LeRobot 会将每个相机流存储为一个 MP4 文件,而不是为每个时间步保存一个图像文件。视频编码跨时间进行压缩,与一大堆 PNG 相比通常能减少 dataset 大小和 I/O,同时保持 MP4 —— 一种每个播放器和加载器都能理解的格式。
将帧编码为 MP4 是一个完整的 FFmpeg 流水线:编码器选择、像素格式、GOP/关键帧、质量与速度的权衡,以及可选的额外编码器标志。其中大多数参数都可以通过 rgb_encoder 进行用户调整,这是一个嵌套的 RGBEncoderConfig(lerobot.configs.video.RGBEncoderConfig),通过 PyAV 传递。
你可以通过 CLI 使用 --dataset.rgb_encoder.<field> 设置这些参数(例如配合 lerobot-record 或 lerobot-rollout)。同一个配置块会应用于该次运行中的每个相机视频流。
必须开启视频存储,
rgb_encoder才会生效 —— 在 Python API 中使用use_videos=True,或在 CLI 上使用--dataset.video=true( 录制默认值)。关闭视频后,输入保持为图像,rgb_encoder会 被忽略。
关于帧何时写入与编码(流式 vs. episode 后)、队列以及其他顶层 --dataset.* 开关的详细信息,请参阅流式视频编码。关于编码参数对比和实验,请参阅 video-benchmark Space。
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/tty.usbmodem58760431541 \
--robot.cameras="{laptop: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
--robot.id=black \
--teleop.type=so100_leader \
--teleop.port=/dev/tty.usbmodem58760431551 \
--teleop.id=blue \
--dataset.repo_id=<my_username>/<my_dataset_name> \
--dataset.num_episodes=2 \
--dataset.single_task="Grab the cube" \
--dataset.streaming_encoding=true \
--dataset.encoder_threads=2 \
--dataset.rgb_encoder.vcodec=h264 \
--dataset.rgb_encoder.preset=fast \
--dataset.rgb_encoder.extra_options={"tune": "film", "profile:v": "high", "bf": 2} \
--display_data=true默认值经过调优,以在典型机器人 dataset 上平衡压缩率、视觉质量和解码/定位速度。更改它们可能会影响录制(CPU 负载、丢帧)和训练(解码吞吐量、图像质量)。
只有在有明确理由时才覆盖这些参数,并在依赖新设置之前衡量其对流水线的影响。
以下所有标志在 CLI 上都以 --dataset.rgb_encoder. 为前缀。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
vcodec | str | "libsvtav1" | 视频编解码器名称。"auto" 会从固定的偏好列表中选取第一个可用的硬件编码器,否则回退到 libsvtav1。 |
pix_fmt | str | "yuv420p" | 输出像素格式。必须为你 FFmpeg 构建中所选编解码器所支持。 |
g | int | 2 | GOP 大小 —— 每 g 帧一个关键帧。作为 FFmpeg 选项 g 发出。 |
crf | int 或 float | 30 | 抽象质量值,按编解码器映射(见映射)。在映射单调的情况下,越低 → 质量越高 / 输出越大。 |
preset | int 或 str | 12 * | 编码器速度预设;含义取决于编解码器。 * 未设置且 vcodec=libsvtav1 时,LeRobot 默认为 12。 |
fast_decode | int | 0 | libsvtav1:0–2,通过 svtav1-params 传递。h264 / hevc(软件):如果 >0,设置 tune=fastdecode。其他编解码器:通常不使用。 |
video_backend | str | "pyav" | 目前视频编码仅实现了 "pyav"。 |
extra_options | dict | {} | 在上面的结构化字段之后合并的额外 FFmpeg 或编解码器特定选项。不能覆盖那些字段已设置的键。 |
深度图(Intel RealSense、Reachy 2)作为独立的视频流与 RGB 流一起存储。原始深度(uint16 毫米或 float32 米)无法通过 8 位编解码器保存,因此 LeRobot 会将每个深度图量化为 12 位编码([0, 4095])—— 默认采用对数方式,以匹配深度传感器的 1/depth 误差特性 —— 然后将其打包为高位深像素格式(gray12le),并使用 12 位编解码器进行编码。
通过一个并行的 depth_encoder 块(DepthEncoderConfig)配置深度流水线。它共享每个 RGBEncoderConfig 字段(vcodec、pix_fmt、crf……),并新增四个量化器参数,通过 --dataset.depth_encoder.<field> 设置:
lerobot-record \
... \
--dataset.depth_encoder.vcodec=hevc \
--dataset.depth_encoder.depth_min=0.05 \
--dataset.depth_encoder.depth_max=5.0 \
--dataset.depth_encoder.use_log=true| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
vcodec | str | "hevc" | HEVC Main 12(支持 12 位的编解码器,兼容 MP4)。 |
extra_options | dict | {"x265-params": "lossless=1"} | 深度默认无损(精确往返);crf 会被忽略。传入 extra_options={} 并设置 crf 可获得更小的有损流。 |
pix_fmt | str | "gray12le" | 用于承载量化编码的单通道 12 位像素格式。 |
depth_min | float | 0.01 | 以米为单位的深度映射到量子 0。低于该值的值在解码时会被裁剪。 |
depth_max | float | 10.0 | 以米为单位的深度映射到量子 4095。高于该值的值在解码时会被裁剪。 |
shift | float | 3.5 | 用于对数量化以在零附近保持数值稳定性的对数前偏移(米)。必须满足 depth_min + shift > 0。 |
use_log | bool | True | 如果为 true,则在对数空间中进行量化(推荐用于典型深度传感器)。设为 false 则进行均匀/线性量化。 |
无论输入深度的单位是什么,
depth_min、depth_max和shift始终以米解释。输入会自动检测:整数数组(例如直接来自 RealSense 的uint16毫米)按毫米处理,浮点数组按米处理。 选择depth_min/depth_max以覆盖传感器的实际工作范围 —— 该范围之外的量子会饱和,这可能会压碎边界处的细节。
深度特征在 meta/info.json 中用 "is_depth_map": true 标记,其量化器设置(video.depth_min、video.depth_max、video.shift、video.use_log)会被持久化 —— 这正是让深度在加载时能够反量化回物理单位的原因。
depth_encoder 是录制时的关注点。深度图在加载时(例如训练期间)反量化后所采用的单位,由读取时标志 --dataset.depth_output_unit 单独设置:
lerobot-train \
--dataset.repo_id=<my_username>/<my_dataset_name> \
--dataset.depth_output_unit=m \
--policy.type=act| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
depth_output_unit | str | "mm" | 深度图在加载时反量化后所采用的物理单位:"mm"(毫米)或 "m"(米)。 |
这纯粹是解码时的呈现选择 —— 它不会改变存储的视频或其元数据,因此同一 dataset 可以作为
mm或m读取而无需重新编码。它对没有深度相机的 dataset 没有影响。
视频流的第一个 episode 编码后,编码器配置会被持久化到 dataset 元数据(meta/info.json)中每个视频特征下,与从文件本身探测得到的值并列。对于视频特征 observation.images.<camera>,info.json 中的布局为:
{
"features": {
"observation.images.laptop": {
"dtype": "video",
"shape": [480, 640, 3],
"info": {
"video.height": 480,
"video.width": 640,
"video.codec": "h264",
"video.pix_fmt": "yuv420p",
"video.fps": 30,
"video.channels": 3,
"is_depth_map": false,
"video.g": 2,
"video.crf": 30,
"video.preset": "fast",
"video.fast_decode": 0,
"video.video_backend": "pyav",
"video.extra_options": { "tune": "film", "profile:v": "high", "bf": 2 }
}
}
}
}有两个来源为 info 块提供内容:
| 来源 | 来源位置 | 字段 |
|---|---|---|
| 流派生 | 使用 PyAV 从编码后的 MP4 读回。 | video.height, video.width, video.codec, video.pix_fmt, video.fps, video.channels, is_depth_map, audio.* |
| 编码器派生 | 取自 RGBEncoderConfig / DepthEncoderConfig。 | video.g, video.crf, video.preset, video.fast_decode, video.video_backend, video.extra_options |
该块仅从第一个episode 填充一次。它假设 dataset 中的每个 episode 都使用相同的
rgb_encoder编码。不支持在录制过程中 更改编码器设置 ——info.json只会反映第一个 episode 所使用的参数。
使用 merge_datasets 聚合 dataset 时,视频文件会按原样拼接(不重新编码),info.json 中的编码器字段按每个键合并:
| 合并规则 | 字段 | 行为 |
|---|---|---|
| 必须匹配 | video.codec, video.pix_fmt, video.height, video.width, video.fps | 流派生字段必须在各来源之间匹配,否则 FFmpeg 的 concat 解复用器会失败。 |
| 宽松合并 | video.g, video.crf, video.preset, video.fast_decode, video.extra_options | 编码器调优字段。如果所有来源一致,则保留该值;如果不一致,则设为 null(对于 video.extra_options 则为 {}),并记录一条警告。 |