SmolVLA 高反光与透明物体抓取复现教程:方案总览与核心改造 · 第 5 阶段
基于双 奥比中光 相机、增强深度、B601-DM 主从臂和 SmolVLA,复现高反光桌面上透明瓶与香蕉模型的自主整理链路。
01 / DOCUMENTATION 内容概述
系列教程第 5/7 阶段:采集覆盖透明物体、高反光和多摆位的数据集,并处理断点续采、混合帧率、格式与 Arrow 合并问题。
02 / DOCUMENTATION 9.1 先编辑脚本顶部参数
主脚本:
examples/rebot_b601_smolvla_record/record_b601_smolvla_rgbd.sh重点修改:
DATASET_ROOT="/home/r/datasets/rebot_b601_banana_bottle_rgbd_1"DATASET_REPO_ID="local/rebot_b601_banana_bottle_rgbd_1"
FOLLOWER_PORT="/dev/ttyACM0"LEADER_PORT="/dev/ttyACM1"
TOP_SERIAL="你的 Gemini 335L 序列号"WRIST_SERIAL="你的 Gemini 305 序列号"ORBBEC_BRIDGE="${REPO_ROOT}/src/lerobot/cameras/orbbec/cpp/build/orbbec_rgbd_bridge"LINGBOT_MODEL="/home/r/ws/model.sm4"推荐保持:
640 × 48010 FPSdepth visualization: 250-1800 mmEnhancedDepthFilter confidence_threshold: 51H.264 encoders: serialvideo encoding threads: 1image writer: 1 process × 1 thread/cameradisplay_data: false03 / DOCUMENTATION 9.2 启动采集
bash examples/rebot_b601_smolvla_record/record_b601_smolvla_rgbd.sh脚本会记录:
| Feature | 形状/类型 | 用途 |
|---|---|---|
observation.state | (21,) float32 | 7 个电机的位置、速度、力矩 |
observation.images.wrist | (3,480,640) | 腕部 RGB |
observation.images.top | (3,480,640) | 全局 RGB |
observation.images.top_depth | (3,480,640) | 增强深度可视化,SmolVLA 输入 |
observation.depths.top | (480,640) uint16 | 增强深度毫米值,归档 |
action | (7,) float32 | 实际发送给从臂的 6 关节 + 夹爪目标 |
04 / DOCUMENTATION 9.3 数据要怎样才能学会“随机摆放也能归位”?
不要只在桌面中心重复同一条漂亮轨迹。尤其面对透明瓶和高反光桌面,训练集必须主动覆盖“反光关系变化”,而不只是物体坐标变化。建议给桌面划分位置网格,并按下面的场景矩阵有计划地采集:
| 变化维度 | 需要覆盖的情况 | 为什么重要 |
|---|---|---|
| 物体位置 | 左/中/右、近端/远端、目标区附近 | 防止模型记住固定抓取轨迹 |
| 透明瓶姿态 | 直立、横放、斜放、不同标签和瓶盖朝向 | 瓶壁、标签与背景形成的视觉线索不同 |
| 高光关系 | 桌面高光落在瓶前、瓶后、瓶侧,亮度在部署允许范围内变化 | 深度孔洞和 RGB 对比度会随角度、照明改变 |
| 遮挡关系 | 香蕉遮瓶、瓶遮香蕉、两者相邻或分离 | 训练模型利用全局与腕部视角互补 |
| 操作过程 | 顺利抓取、轻微偏位后修正、重新接近 | 学到闭环纠偏,而不是只复刻完美演示 |

-
香蕉的左、中、右、近端、远端位置。
-
香蕉不同弯曲朝向和旋转角度。
-
透明瓶直立、横放、斜放及不同标签朝向。
-
两物体互相靠近、部分遮挡和交换相对位置。
-
部署范围内不同强度和位置的桌面高光,以及透明瓶前后背景对比变化。
-
“先香蕉后瓶子”和“先瓶子后香蕉”两种成功顺序。
-
从轻微偏差中修正抓取的示范,而不只是每次完美直线接近。
建议把高难样本单独做标记或至少记录 episode 清单,例如“瓶身与高光重叠”“瓶子横放且标签背向相机”“瓶子和香蕉部分遮挡”。后续模型失败时,才能判断缺的是哪类演示,而不是笼统地再采几十条相似数据。
同时保持这些条件稳定:
-
相机安装位置和内外参。
-
目标归位区域。
-
任务文本。
-
深度滤波模型、阈值和 250-1800 mm 可视化范围。
-
采集 FPS 与关节校准。
这里的“保持稳定”不等于把灯光永久锁死。相机位置、滤波链路和任务定义应固定;照明和反光变化则应在实际部署可能出现的范围内受控采样。不要在采集时全部使用柔光哑光背景,却要求模型部署后突然面对镜面高光和透明瓶。
43 个 episode 足以完成一次基线训练,但对于两种物体随机摆放,尤其是透明瓶与高光关系不断变化的泛化仍偏少。第一轮实机评估后,建议根据失败区域补充 40-80 个有针对性的成功 episode,而不是只把旧数据训练更多轮。新增数据应优先补齐失败场景,避免数量增加了,视觉分布却几乎没有变。
05 / DOCUMENTATION 9.4 中断后继续采集
读取真实已保存数量:
cat /你的数据集/meta/info.json | python -m json.tool假设目标 30 条,已经保存 7 条:
remaining=$((30 - 7))bash examples/rebot_b601_smolvla_record/record_b601_smolvla_rgbd.sh --num-episodes "${remaining}" --extra --resume=true这里的 num_episodes 是“本次还要新增多少条”,不是最终总数。必须使用完全相同的 dataset root、repo id、FPS 和 feature schema。
06 / DOCUMENTATION 9.5 为什么每次保存完 episode,下一轮反而报错?
episode 边界会执行数据映射和 H.264 编码。早期 libx264 自动选择多个工作线程,可能造成 145-190 ms 的命令 gap;更严重时超过 0.5 秒。旧代码可能在下一次 _mark_episode_start_pose() 才报告上一轮已经锁定的 stream fault,看起来像“新 episode 一开始就坏了”。
当前解决方案:
-
PARALLEL_VIDEO_ENCODING=false -
LEROBOT_VIDEO_ENCODING_THREADS=1 -
H.264 编码
-
图像写入放到 1 个独立进程
-
500 Hz MotorBridge 独立线程
-
分离 50 ms 告警与 500 ms 硬故障阈值
07 / DOCUMENTATION 9.6 空 episode 不再让程序崩溃
如果操作者立即结束或安全故障发生在第一帧之前,旧代码会对空 buffer 调用 save_episode(),报:
You must add one or several frames with add_frame before calling add_episode当前记录流程会检测 buffer 大小,跳过空 episode,并清理临时图像。
08 / DOCUMENTATION 9.7 PyAV 的canonicalname 兼容问题
部分 ARM64 PyAV 版本的 codec 对象没有 canonical_name。当前代码按以下顺序兼容获取:
canonical_name → name → long_name → str(codec)因此不会再因为视频元数据字段差异让已经采完的 episode 在保存阶段失败。
09 / DOCUMENTATION 10.1 推荐分批采集
把不同批次保存到不同目录,先检查、删除明显失败的数据,再合并:
bash examples/rebot_b601_smolvla_record/merge_b601_datasets.sh编辑脚本中的:
SOURCE_ROOTSSOURCE_REPO_IDSOUTPUT_ROOTOUTPUT_REPO_ID所有源数据必须具有相同 FPS、robot type、feature 名称、形状和 dtype。
10 / DOCUMENTATION 10.2 混合 15 FPS 与 10 FPS 不能只改 JSON
aggregate_datasets 会拒绝不同 FPS。不要只把 meta/info.json 的 15 改成 10,否则图像、状态、动作和时间戳会错位。
正确做法:
python examples/rebot_b601_smolvla_record/resample_b601_dataset.py --source-root /home/r/datasets/rebot_b601_banana_bottle_rgbd_1 --source-repo-id local/rebot_b601_banana_bottle_rgbd_1 --output-root /home/r/datasets/rebot_b601_banana_bottle_rgbd_1_fps10 --output-repo-id local/rebot_b601_banana_bottle_rgbd_1_fps10 --target-fps 10转换器会对腕部 RGB、全局 RGB、深度可视化、原始深度、状态和动作使用同一个源帧索引。
如果看到:
No accelerated colorspace conversion found from yuv420p to rgb24通常只是 FFmpeg 退回 CPU 颜色转换的提示,不代表转换失败。看最后是否生成完整数据和 metadata。
11 / DOCUMENTATION 10.3 图像 CHW/HWC 与深度 dtype
PyAV/TorchVision 可能返回 (3,H,W) float 图像,而数据集期望 (H,W,3);Hugging Face 载入深度时也可能临时表现为 int64。当前 resample 工具会:
-
将图像恢复为元数据声明的 channel-last 格式再写入。
-
将深度安全转换回
uint16。
12 / DOCUMENTATION 10.4ArrowTypeError: array[uint16]
原始深度是 Array2D(uint16) Parquet 列,不是视频。合并代码必须使用完整 schema 恢复这个扩展类型。当前实现通过显式 feature schema 和直接 Parquet I/O 处理,避免 PyArrow 把 NumPy dtype 误当成不支持的对象。
失败的 merge 会留下不完整输出目录。确认路径后移走或删除不完整目标,再重跑;源数据不会被修改。
13 / DOCUMENTATION 10.5 Hugging Face cache 把系统盘吃满
旧合并方法使用 Dataset.from_parquet() 时,可能在:
~/.cache/huggingface/datasets为每个源 Parquet 生成重复 Arrow 缓存,几十 GB 很快就没了。当前合并读取绕过这类重复缓存。
仍建议把 cache 放到数据盘:
export HF_HOME=/你的数据盘/huggingfaceexport HF_DATASETS_CACHE=${HF_HOME}/datasetsmkdir -p "${HF_DATASETS_CACHE}"清理前先确认没有训练、合并或可视化进程使用缓存。不要用未经检查的递归删除命令指向变量路径。
硬件
软件应用和在线服务
实现步骤
配置采集参数
设置数据集 ID、相机键、深度格式、任务说明、episode 数量和保存路径
启动并监控采集
同时检查主从臂状态、RGB、增强深度、帧率、磁盘写入和安全告警
设计场景变化
覆盖物体位置、姿态、高光关系、遮挡关系、抓取顺序和轻微偏差后的纠正

验证断点续采
中断后检查已有 episode、索引和元数据,再从正确编号继续
清理无效 episode
移除空 episode、处理保存后下一轮报错并修复 PyAV canonical_name 兼容问题
分批合并数据
合并多批数据并处理 15/10 FPS 混合、episode 索引与时间戳
统一深度格式
修复 CHW/HWC、dtype、shape 和 ArrowTypeError,保持 uint16 深度语义
验证最终数据集
随机回放 episode,核对两路 RGB、深度、关节、夹爪和 action 对齐
项目结果

得到可用于 SmolVLA 的合并 RGB-D 数据集,覆盖透明瓶、高光、遮挡、摆位和纠偏变化;完成断点续采、空 episode、混合帧率、CHW/HWC、dtype 和 Arrow 数据类型问题的修复。