
基于 Seeed 官方 LeRobot 仓库改造:在高反光实验环境中,使用 Orbbec Gemini 305 + Gemini 335L、轻量化蚂蚁灵波(LingBot-Depth)EnhancedDepthFilter、B601-DM 同型号主从臂遥操、云端 RTX 4090 训练与 Jetson Orin NX 边缘推理,挑战透明塑料可乐瓶与香蕉模型的自主整理任务。🔗GitHub
> 📌 项目阶段说明(截至 2026-08-17):本文已经实机完成相机与增强深度接入、设备许可校验、重力补偿遥操、RGB-D 数据采集与合并、RTX 4090 云端训练、checkpoint 回传,以及 Jetson Orin NX 上的在线推理和安全限幅验证。目前工作停留在模型部署参数微调阶段,尚未在高反光桌面上稳定、可重复地完成透明塑料可乐瓶抓取与归位。因此,本文是一份可运行、可诊断的阶段性工程复现记录,不是一份宣称透明物体抓取已经达到稳定成功率的最终结果报告。
想象一下这样的画面:灯光在桌面上形成明显高光,透明塑料可乐瓶的瓶壁几乎与背景融在一起,黄色香蕉模型和瓶子每次又都被随意摆放。机械臂需要先观察全局,再靠近、抓取,最后把两件物体送回各自的固定位置。它不依赖提前写死的坐标,也不是靠脆弱的颜色阈值脚本硬撑,而是从真人遥操演示中学习整套操作策略。✨
这正是本项目希望最终完成的事情,也是当前仍在继续攻克的最后一段路。
> 🔍 本项目没有刻意选择“好拍、好识别”的哑光测试台。 实验场景存在较强镜面反射,被操作物之一还是透明塑料可乐瓶。这两项条件会同时考验 RGB 语义识别、深度边缘稳定性和抓取策略泛化,也是本文引入增强深度链路的直接原因。
我们从 Seeed 官方 LeRobot 仓库 出发,让两台同型号的 reBot Arm B601-DM 分别担任主臂和从臂;给主臂加入重力补偿和夹爪微力辅助;给从臂加入 MotorBridge 500 Hz 独立命令流、故障保持、夹爪限力闭合和 episode 恢复;再将腕部 Gemini 305 的近景 RGB、全局 Gemini 335L 的 RGB,以及经过奥比中光 EnhancedDepthFilter 处理的深度可视化图一起交给 SmolVLA。RGB 提供透明瓶的语义与外观线索,增强深度提供更稳定的空间线索,两者互补,而不是让某一路传感器独自完成全部工作。
本文不仅给出“能跑”的命令,也会认真交代一路踩过的坑。很多机器人问题并不浪漫:一个错误的 /dev/video* 节点可以让彩色图变成绿色噪点,一次 H.264 编码尖峰可以让电机命令流断档,一根被关节压住的数据线甚至可能让从臂突然失能。好消息是,这些问题都能被拆开、记录、验证,再逐个解决。🔧
1. 项目要解决什么问题?🎯
目标任务是:
Arrange the banana model and the transparent plastic cola bottle back to their assigned places on the desktop
也就是让机械臂学习一条可以泛化的桌面整理策略:
- 香蕉和透明塑料可乐瓶的起始位置、朝向可以变化。
- 两件物体最终对应的位置保持明确、稳定。
- 机械臂能够通过视觉判断物体位置,而不是记忆某一条固定轨迹。
- 在桌面高光、局部镜面反射和透明瓶深度不完整的情况下,策略仍能利用多路视觉完成操作。
- 训练、采集和推理使用同一套视觉分布,尤其是同一种深度滤波模型、阈值与可视化范围。
这比普通哑光物体抓取更难:透明瓶会让深度出现背景穿透、边缘缺失或跳变,高反光桌面又会带来局部孔洞与飞点。项目的关键问题因此不只是“能否接入深度相机”,而是“能否把同一套经过验证的增强深度处理稳定地贯穿采集、训练和推理”。
本项目的第一阶段采用模仿学习:操作者拖动 B601-DM 主臂,从臂同步执行动作,系统同时记录相机、关节状态与实际下发动作,最后用 SmolVLA 学习“看见什么、当前机械臂是什么状态、下一步该怎么动”。
1.1 数据流总览

2. 这个项目改进了什么?🌟
这些改动不是简单地“多接一台相机、再加一个模型”。它们围绕同一个目标展开:让系统在透明物体、高反光背景和边缘设备算力有限的现实条件下,仍能稳定采集可学习的数据,并把完全一致的输入送到 SmolVLA 推理端。
2.1 同型号 B601-DM 主从臂适配
官方示例更多围绕特定 leader 或基础 follower 插件展开。本项目将 B601-DM 同时适配成:
rebot_b601_leader:读取 6 个关节和夹爪,并运行重力补偿、阻抗或零力矩辅助。
rebot_b601_follower:接收 7 维位置目标,运行关节限位、相对目标限幅、状态检查和安全恢复。
rebot_motorbridge.py:按 Seeed DM 配置维护高频 MIT 命令流与完整反馈诊断。
主从臂都采用 DM 电机,默认通过 Seeed MotorBridge 串口设备 /dev/ttyACM* 通信,而不是把 MotorBridge 错当成板载 can0。
2.2 主臂重力补偿遥操

单纯关闭主臂电机虽然轻,但机械臂会坠落;使用刚性位置控制又很难拖动。项目复用了 Seeed reBot-DevArm-Grasp 中的动力学模型,为主臂 6 个关节提供重力前馈,并保留轻微阻尼。
经过实机调试后采用的核心参数是:
gravity_comp_torque_scale = 0.95
gravity_comp_torque_limit = 8.0
gravity_comp_kp = 1.9
gravity_comp_kd = 0.75
update_hz = 100
夹爪不施加“重力补偿”,而是使用独立的 force_assist:给张开方向一个很小的 0.09 Nm 偏置,软件上限为 0.15 Nm,帮助克服上电后的机械阻力,同时避免夹爪突然自行弹开。
2.3 双 Orbbec 相机不再“都当普通摄像头”
两台相机分工明确:
| 相机 | 安装位置 | 训练输入 | 说明 |
|---|---|---|---|
| Gemini 305 | 机械臂腕部 | observation.images.wrist |
只取 RGB,提供近距离抓取视角 |
| Gemini 335L | 桌面全局视角 | observation.images.top |
全局 RGB,观察物体与目标位置 |
| Gemini 335L | 同上 | observation.images.top_depth |
经 EnhancedDepthFilter 处理后的三通道深度可视化,重点提供透明瓶边界、桌面和目标区域的空间线索 |
| Gemini 335L | 同上 | observation.depths.top |
滤波后、未伪彩化的 uint16 毫米深度,保留孔洞和真实测量结果,供复核与后续研究 |
腕部和全局相机都通过 OrbbecSDK v2 的 C++ bridge 读取。这样做不是为了“显得高级”,而是因为 Orbbec RGB-D 相机会暴露多个 UVC 节点,其中一些是原始深度、红外或元数据流。把这些节点交给 OpenCV 当 BGR 图像读取,就会得到绿色密集噪点,而不是正常彩色画面。
2.4 蚂蚁灵波轻量模型:为透明瓶与高反光场景补强深度线索

这是本项目最具辨识度的视觉改进。透明塑料可乐瓶会透射和折射部分光线,深度图中常见瓶壁缺失、背景深度“穿过”瓶身、轮廓跳变;高反光桌面则可能产生局部无效值、飞点和随观察角度变化的闪烁。直接把这样的原始深度彩色化后送进模型,既可能增加噪声,也可能让策略学到不稳定的伪特征。
需要先讲清楚一件事:任何深度滤波模型都不能凭空恢复传感器从未测到的全部透明表面。 本项目使用蚂蚁灵波轻量模型的目标,是在 Jetson 可承担的计算量下改善有效区域的连续性、抑制随机噪声并稳定物体和桌面边缘,再与两路 RGB 共同提供信息,而不是把透明物体检测完全押在深度图上。
本项目使用奥比中光 SDK 中的 EnhancedDepthFilter 与裁剪模型 model.sm4。它没有作为离线数据清洗脚本附加在采集之后,而是直接进入 OrbbecSDK v2 C++ bridge:
- Gemini 335L 同步输出 color + depth
FrameSet。
- 深度先对齐到彩色图。
- SDK 创建
ob::EnhancedDepthFilter(device, model_path)。
- 调用官方 filter 处理整个
FrameSet。
- Python 只接收滤波后的深度;启用增强模式后若 filter 或许可失败,立即报错,不允许悄悄退回原始深度。
- 同一份滤波深度同时生成未伪彩化的
uint16深度和三通道top_depth。
原版 SmolVLA 使用视觉编码器处理三通道图像,因此当前方案让 top_depth 作为第三路视觉输入;滤波后的 uint16 深度继续归档,但由 SmolVLARawDepthFilterProcessor 在进入 GPU 前移除,避免把大尺寸整数深度误送进视觉模型。最终形成互补关系:
top RGB:识别透明瓶的瓶盖、标签、轮廓和目标区域等语义线索。
wrist RGB:在接近和抓取阶段观察局部边缘、夹爪与物体相对位置。
top_depth:提供经增强后的桌面几何、距离层次和可用瓶体边界线索。
uint16 depth:忠实归档增强后的测量值,用于质量检查和未来深度网络研究。
更重要的是,采集和推理均在设备侧运行同一套 C++ filter、同一个 model.sm4、相同置信度阈值和相同深度可视化范围。这样训练看到的“深度语言”与部署时完全一致,避免出现云端离线处理很漂亮、Jetson 实时输入却是另一种分布的情况。
2.5 数据采集不再和电机保活“抢一条线程”

RGB-D 等帧、PNG 写盘、H.264 编码和数据集映射都可能卡住 Python 主循环。DM 电机如果长时间收不到命令,可能进入 watchdog 或保护状态。项目为主臂和从臂都增加了独立的 500 Hz MIT 命令刷新线程:上层采集循环只更新目标,高频线程持续重复最近一次完整的 7 电机命令。
同时增加:
- 50 ms 命令间隔告警阈值。
- 500 ms 硬故障阈值。
- 连续 5 次发送失败锁定故障。
- 电机状态从
ENABLED变为DISABLED时立即中止。
- 连续反馈丢失保护。
- 程序异常后最多 15 秒保持当前姿态,给操作者托住机械臂或按急停。
- episode 起始姿态记录、安全中止、从臂低速回起点和等待主臂回起点。
2.6 夹爪控制从“硬顶位置”升级为“限力闭合”
早期方案让从臂夹爪一直用较高 kp 追逐完全闭合位置。夹到物体后仍存在巨大位置误差,电机会持续加载,严重时可能单独变为 DISABLED。
现在采用更接近 Seeed 抓取示例的控制方式:
- 张开和空载移动仍使用位置控制。
- 闭合时使用最大
1.0 Nm的有界前馈力矩和阻尼。
- 至少闭合移动
17°后,才允许根据低速度连续样本判断接触。
- 接触后切换到
kp=5、kd=1、0.30 Nm的柔和保持。
- 收到明确张开命令后解除接触保持。
- 所有夹爪力矩受
1.5 Nm软件上限约束。
此外,遥操时需要“主臂夹爪坐标 → 从臂夹爪坐标”映射;推理时模型已经输出从臂坐标,因此必须关闭二次映射。项目用 gripper_map_leader_to_follower 显式区分这两种场景。
3. 硬件与软件准备 📦
3.1 硬件清单
| 类别 | 本项目配置 |
|---|---|
| 主臂 | reBot Arm B601-DM,6 轴 + DM4310 夹爪 |
| 从臂 | reBot Arm B601-DM,6 轴 + DM4310 夹爪 |
| 边缘计算 | Jetson Orin NX 16GB |
| 腕部相机 | Orbbec Gemini 305,RGB only |
| 全局相机 | Orbbec Gemini 335L,RGB + depth |
| 云端训练 | NVIDIA RTX 4090 24GB |
| 任务物体 | 黄色香蕉模型、透明塑料可乐瓶、两个固定目标位置 |
| 测试环境 | 存在明显灯光高光和局部镜面反射的桌面,不使用专门降低难度的全哑光背景 |
| 安全设备 | 急停、稳定底座、独立可靠电源、机械臂支撑措施 |
3.2 物理连接建议
推荐优先级如下:
- 两台 Orbbec 相机分别接 Jetson 的独立 USB 3 高速路径。
- 如果必须使用扩展坞,选择外接供电的 USB 3 Hub,不要让两台 RGB-D 相机和大量低速设备挤在同一条不稳定链路上。
- 两台机械臂电源接到可靠、彼此独立的供电插座或电源支路。
- MotorBridge 串口线、机械臂内部 CAN/数据线保留完整活动余量,不能被底座、关节或桌沿压住。
- 无论软件多么完善,从臂首次测试时都要有物理支撑和随手可按的急停。
使用以下命令检查 USB 拓扑和串口归属:
lsusb -t
ls -l /dev/serial/by-id/
udevadm info -q path -n /dev/ttyACM0
udevadm info -q path -n /dev/ttyACM1
sudo fuser -v /dev/ttyACM0 /dev/ttyACM1
/dev/ttyACM0 和 /dev/ttyACM1 的编号可能在重启或重新插拔后交换。正式运行建议用 /dev/serial/by-id/ 下的稳定链接,至少也要在每次开机后确认主从臂端口。
3.3 软件版本原则
本项目实际工作流使用 Python 3.10。最重要的不是盲目追求“最新版”,而是保证以下组件彼此匹配:
- 当前修改版 LeRobot 源码。
- PyTorch、TorchVision 与 Jetson CUDA 环境。
- OrbbecSDK v2、对应扩展库、
model.sm4、CUDA/TensorRT 运行时。
- 写入相机设备的 LingBot-Depth 许可。
EnhancedDepthFilter 对 SDK、扩展、模型、TensorRT 与许可组合非常敏感。不要把其他版本的 model.sm4 随手替换进来,也不要把公开 Python 版 LingBot-Depth 权重当作 SDK 私有 filter 的直接替代品。
4. 获取代码与建立环境 🛠️
4.1 认识代码来源
本项目基于:
如果你正在本项目仓库中阅读本文,修改版代码已经包含在当前 checkout 中。不要只拉取 Seeed 官方原仓库后直接运行本文脚本,因为原仓库不包含本文所述的 Orbbec C++ bridge、同型号 B601 leader、深度数据格式修复和安全状态机。
如需从官方基线开始对比:
mkdir -p ~/ws/rebot_lerobot
cd ~/ws/rebot_lerobot
git clone https://github.com/Seeed-Projects/lerobot.git
4.2 安装 LeRobot 环境
conda create -y -n lerobot python=3.10
conda activate lerobot
cd ~/ws/rebot_lerobot/lerobot
python -m pip install --upgrade pip
python -m pip install -e ".[smolvla]"
conda install -y ffmpeg -c conda-forge
不要用无版本约束的 pip install transformers 临时补包。当前仓库要求 transformers>=4.57.1,<5.0.0 和 huggingface-hub>=0.34.2,<0.36.0;直接安装最新主版本可能顺带升级 Hub 与 tokenizer,导致已经能采集数据的环境出现新的依赖冲突。若 Jetson 只缺 SmolVLA 推理栈,又不希望重装整套 PyTorch/CUDA,可按仓库约束修复:
python -m pip install --upgrade
'transformers>=4.57.1,<5.0.0'
'huggingface-hub[cli,hf-transfer]>=0.34.2,<0.36.0'
'accelerate>=1.10.0,<2.0.0'
'num2words>=0.5.14,<0.6.0'
'safetensors>=0.4.3,<1.0.0'
始终确认当前终端加载的是当前 checkout:
python -c 'import lerobot; print(lerobot.__file__)'
输出路径必须位于当前仓库。如果它指向旧目录,重新执行 editable install。
4.3 拉取 Seeed 重力补偿依赖
cd ~/ws
git clone https://github.com/Seeed-Projects/reBot-DevArm-Grasp.git rebot_grasp
默认期望路径:
/home/r/ws/rebot_grasp/sdk/reBotArm_control_py
如果放在其他目录,需要在配置中指定:
--teleop.gravity_comp_sdk_root=/你的路径/rebot_grasp/sdk/reBotArm_control_py
5. 编译 OrbbecSDK v2 C++ Bridge 📷
5.1 准备 OrbbecSDK v2
cd ~/ws
git clone https://github.com/orbbec/OrbbecSDK_v2.git
先根据 OrbbecSDK 自己的 README 完成 ARM64 编译,并确认至少存在:
/home/r/ws/OrbbecSDK_v2/include/libobsensor/ObSensor.hpp
/home/r/ws/OrbbecSDK_v2/build/src/generated/Export.h
/home/r/ws/OrbbecSDK_v2/build/linux_arm64/lib/libOrbbecSDK.so
5.2 正确的 bridge 编译命令
cd ~/ws/rebot_lerobot/lerobot/src/lerobot/cameras/orbbec/cpp
rm -rf build
cmake -S . -B build
-DORBBECSDK_ROOT=/home/r/ws/OrbbecSDK_v2
-DORBBECSDK_LIBRARY=/home/r/ws/OrbbecSDK_v2/build/linux_arm64/lib/libOrbbecSDK.so
-DORBBECSDK_INCLUDE_DIR=/home/r/ws/OrbbecSDK_v2/include
-DORBBECSDK_GENERATED_INCLUDE_DIR=/home/r/ws/OrbbecSDK_v2/build/src/generated
cmake --build build --config Release
检查动态库:
ldd build/orbbec_rgbd_bridge | grep -E 'Orbbec|not found'
5.3 第一个坑:OrbbecSDK_DIR 不是 .so 文件
错误写法:
-DOrbbecSDK_DIR=/path/to/libOrbbecSDK.so
OrbbecSDK_DIR 应该指向含 OrbbecSDKConfig.cmake 的目录,而源码 build tree 往往没有安装好的 package config。当前 CMakeLists 同时支持 package config 和“头文件 + 动态库”两种方式,因此源码编译场景直接传 ORBBECSDK_ROOT、ORBBECSDK_INCLUDE_DIR 和 ORBBECSDK_LIBRARY 最清楚。
5.4 第二个坑:Export.h 在 generated 目录
如果出现:
fatal error: Export.h: No such file or directory
不要复制粘贴这个头文件到 SDK include 目录。正确做法是传入:
-DORBBECSDK_GENERATED_INCLUDE_DIR=/home/r/ws/OrbbecSDK_v2/build/src/generated
5.5 第三个坑:SDK v2 没有Config::enableDevice
某些旧示例会写:
config->enableDevice(serial);
当前 SDK v2 的 ob::Config 不提供这个接口。本项目改为:
ob::Context::queryDeviceList()枚举设备。
- 优先使用
getDeviceBySN()。
- 如果 SDK 明明列出序列号却直接查找失败,再遍历列表比较序列号并使用
getDevice(index)。
- 用选中的
ob::Device构造ob::Pipeline(device)。
这也解决了“双相机都能被枚举,但指定序列号时偶尔提示找不到”的尴尬情况。
5.6 枚举相机
./build/orbbec_rgbd_bridge --list-devices
记录 Gemini 305 与 Gemini 335L 的 OrbbecSDK 序列号。本文示例是:
Gemini 305 : CV2TC5100075
Gemini 335L: CP3L44P0001N
你的设备序列号一定要以实际输出为准。
6. 先把相机单独跑稳 🔍
6.1 为什么/dev/video2 看起来像绿色噪声?
Orbbec RGB-D 相机在 Linux 下可能生成多个 /dev/video*:RGB、深度、IR、元数据都有可能出现。LeRobot/OpenCV 能“打开”一个节点,只能说明它是 V4L2 输入,不能说明数据是普通 BGR。
本项目的 bridge 会根据 SDK profile 选择彩色流,并使用 FormatConvertFilter 将 MJPG、YUYV、UYVY 或 I420 转成真正 RGB。请用 SDK 序列号,不要把 /dev/video2 或 /dev/video4 填进 Orbbec 相机配置。
6.2 先测试腕部 Gemini 305 RGB
python examples/rebot_b601_smolvla_record/debug_orbbec_camera.py
--serial CV2TC5100075
--bridge src/lerobot/cameras/orbbec/cpp/build/orbbec_rgbd_bridge
--width 640
--height 480
--fps 10
--warmup-s 15
--timeout-ms 15000
--frames 100
6.3 再测试 Gemini 335L RGB + 增强深度
python examples/rebot_b601_smolvla_record/debug_orbbec_camera.py
--serial CP3L44P0001N
--bridge src/lerobot/cameras/orbbec/cpp/build/orbbec_rgbd_bridge
--width 640
--height 480
--fps 10
--warmup-s 25
--timeout-ms 25000
--frames 100
--depth
--enhanced-depth
--enhanced-depth-model /home/r/ws/model.sm4
--align-depth-to-color
不要只确认“程序没有报错”,还要把透明瓶放到真实高反光桌面上做动态检查:
- 缓慢旋转和横移透明瓶,观察瓶身深度是否整片反复消失或跳到背景距离。
- 改变瓶子与桌面高光的相对位置,观察桌面是否出现大面积随帧闪烁的孔洞。
- 对比瓶体边缘、瓶底接触区和香蕉轮廓在连续帧中的稳定性。
- 分别测试无瓶、只有透明瓶、只有香蕉和两者部分遮挡,排除 filter 只在单一构图下看起来正常。
- 检查
top RGB与top_depth的空间对齐;边缘整体错位比少量深度孔洞更危险。
推荐先运行一小段未启用增强滤波的对照测试,再以完全相同的相机姿态启用 EnhancedDepthFilter;如需留档,可分别采集短测试 episode。评价重点是无效像素比例、轮廓连续性和帧间抖动,而不是伪彩深度图是否“更鲜艳”。透明表面仍有局部无效值是正常现象,增强结果过度平滑、把瓶子和桌面粘成同一平面同样不可取。
6.4 最后测试双相机同时工作
python examples/rebot_b601_smolvla_record/debug_orbbec_camera.py
--pair
--bridge src/lerobot/cameras/orbbec/cpp/build/orbbec_rgbd_bridge
--wrist-serial CV2TC5100075
--top-serial CP3L44P0001N
--fps 10
--frames 200
--enhanced-depth
--enhanced-depth-model /home/r/ws/model.sm4
6.5 许可保存在设备里,还要不要外部校验工具?
本项目保留了官方 SDK 的设备许可校验链路。若许可已经写入 Gemini 335L,可以不配置外部 enhanced_depth_license_check_command,SDK 会在创建和运行 EnhancedDepthFilter 时读取设备许可。
但是 Jetson 系统时间必须正确。设备断网冷启动后如果时间回到 1970 年,SDK 可能报:
lic_license_verify_from_memory failed: 2003 (License not yet valid)
解决方法:
date -Is
timedatectl status
sudo timedatectl set-timezone Asia/Shanghai
sudo timedatectl set-ntp true
离线时先关闭 NTP,再手动设置真实时间。采集和推理脚本会在连接相机前拒绝早于 2024-01-01 的系统时间。
6.6bridge protocol magic mismatch: b'[08/'
bridge 的标准输出是 Python 读取的二进制帧协议。如果 OrbbecSDK 日志也写进 stdout,日志中的 [08/12 ...] 会被 Python 当作包头,于是出现 magic mismatch。
当前 bridge 将 SDK 日志和诊断输出重定向到 stderr,stdout 只保留二进制包。若自行改 bridge,务必守住这条边界。
6.7 相机超时与setXu failed
依次检查:
- OrbbecViewer、旧 bridge、ROS 相机节点是否仍占用设备。
- 两个脚本是否错误地同时打开同一序列号。
- USB 是否确实运行在
5000M,而不是退化到 USB 2。
- 两台相机是否挤在无供电扩展坞上。
- 是否给 Gemini 335L 足够的 25 秒 warmup 与 25 秒 timeout。
- SDK、固件、扩展库和模型是否兼容。
先单相机、再双相机,永远比直接启动整套采集更省时间。🙂
7. 调通 B601-DM 主从臂 🤝
7.1 MotorBridge 不是can0
最初使用 SocketCAN 扫描时,can0 即使处于 ERROR-ACTIVE,也不代表 MotorBridge 后面的 7 个 DM 电机能够通过该接口直接响应。若机械臂使用 Seeed MotorBridge USB 串口,应该配置:
transport = motorbridge
port = /dev/ttyACM0 或 /dev/ttyACM1
baudrate = 921600
如果握手提示所有电机都不响应,优先检查端口、24 V、电源、急停、MotorBridge 和线束,而不是继续扩大 SocketCAN ID 扫描范围。
7.2 校准主臂和从臂
将两条机械臂放到尽可能一致的安全零位:
lerobot-calibrate
--robot.type=rebot_b601_follower
--robot.port=/dev/ttyACM0
--robot.transport=motorbridge
--robot.id=b601_follower
lerobot-calibrate
--teleop.type=rebot_b601_leader
--teleop.port=/dev/ttyACM1
--teleop.transport=motorbridge
--teleop.id=b601_leader
如果遥操刚开始就频繁出现相对目标限幅,先比较两条机械臂的零位、关节方向和初始姿态。不要第一反应就把 max_relative_target 调得很大。
7.3 重力补偿必须逐级测试
第一步只计算,不使能力矩:
python examples/rebot_b601_smolvla_record/debug_leader_gravity_comp.py
--port /dev/ttyACM1
--id b601_leader
--no-calibrate
--dry-run
--duration-s 5
第二步只开一个承重关节,并从小倍率开始。确认方向正确后,才运行整臂参数:
python examples/rebot_b601_smolvla_record/debug_leader_gravity_comp.py
--port /dev/ttyACM1
--id b601_leader
--no-calibrate
--enabled-joints joint_1,joint_2,joint_3,joint_4,joint_5,joint_6
--torque-scale 0.95
--torque-limit 8.0
--kp 1.9
--kd 0.75
--hz 100
如果抬起后缓慢下坠,可按以下顺序调试:
- 先确认重力方向和关节角零点正确。
- 小幅提高
torque_scale,不要越过已验证范围。
kp用于减小姿态漂移,过高会变得难拖。
kd用于抑制晃动,过高会有明显黏滞感。
torque_limit是安全上限,不是越大越有力越好。
7.4 先不接相机做主从臂遥操
lerobot-teleoperate
--robot.type=rebot_b601_follower
--robot.port=/dev/ttyACM0
--robot.transport=motorbridge
--robot.id=b601_follower
--teleop.type=rebot_b601_leader
--teleop.port=/dev/ttyACM1
--teleop.transport=motorbridge
--teleop.id=b601_leader
--teleop.manual_control_mode=gravity_comp
--fps=30
--teleop_time_s=60
当前默认参数已经包含:
- 主臂关节重力补偿
0.95 / 8.0 / 1.9 / 0.75。
- 主臂夹爪张开偏置
0.09 Nm,总上限0.15 Nm。
- 主臂夹爪
kp=0、kd=0,避免位置弹簧与人手对抗。
- 主臂端点
5 → -310映射到从臂端点10 → -320。
- 从臂夹爪物理范围
-330 → 10。
7.5 夹爪映射不对怎么办?
先直接测试从臂,不经过主臂映射:
python examples/rebot_b601_smolvla_record/debug_follower_gripper_command.py
--port /dev/ttyACM0
--target -320
--min-pos -330
--max-pos 10
再测试闭合和软物体接触:
python examples/rebot_b601_smolvla_record/debug_follower_gripper_command.py
--port /dev/ttyACM0
--target 10
--duration-s 6
--control-mode torque_limited_close
--close-torque 1.0
--hold-torque 0.30
如果直接命令能到达全行程,而遥操不能,问题是映射或相对限幅;如果直接命令也到不了,检查机械限位、方向、线束、增益和电机状态。
8. 为什么从臂曾经会突然失能坠落?⚠️

这是整个项目最值得认真记录的一段。
8.1 不要把所有失能都叫“奇异点”
主从臂姿态差异不大时,从臂仍可能失能。后来通过我的调试日志证明,问题可能来自完全不同的层面:
- MotorBridge 命令流被相机、写盘或视频编码阻塞。
- 从臂 24 V 电源压降、插座接触或电源限流。
- MotorBridge、急停链路或固件 watchdog。
- 运动到某个区域时,数据线被夹住、急弯或拉扯。
- 夹爪长时间高刚度顶住物体。
- 单个驱动器保护,或多个驱动器共同变为
DISABLED。
软件状态 0x0 只表示驱动器现在已经 DISABLED,并不能完整保留之前究竟是欠压、通信丢失还是保护动作。要用时间相关日志判断,而不是只看最后一行。
8.2 独立 500 Hz 命令流
当前代码持续输出每个 MotorBridge stream 的:
- 当前命令年龄。
- 有效发送频率。
- 完整 7 电机批次最大耗时。
- 历史最大 gap 及发生距今时间。
- 最近一次告警 gap 距今时间。
- 当前目标位置。
- 连续发送失败与反馈丢失计数。
使用下面的脚本验证“主线程故意暂停 5 秒时,从臂是否仍被高频线程稳稳保持”:
python examples/rebot_b601_smolvla_record/debug_follower_command_stream.py
--port /dev/ttyACM0
--duration-s 20
--stall-s 5
--stream-hz 500
8.3 多个关节一起DISABLED
如果命令流当前年龄很小、发送频率接近 500 Hz,但多个驱动仍一起 DISABLED,优先排查:
- 从臂 24 V 电源和电流余量。
- 两台机械臂是否共用接触不良的插排。
- 急停链路和 MotorBridge 固件。
- 串口线、臂内 CAN/数据线和连接器。
- 运动范围内是否存在压线点。
一次现场排查中,把两台机械臂电源改接到独立供电后,程序维持时间明显变长;另一个高风险点是从臂运动时数据线会被结构压迫。这两个现象都不是最终定论,但足以把电源和线束提升为最高优先级检查项。
8.4 只剩夹爪DISABLED
这通常更像局部机械负载、夹爪线束或驱动器保护。当前限力闭合控制正是为减少持续堵转而加入。不要通过无限提高力矩掩盖问题。
8.5 episode 故障恢复
采集开始时记录从臂初始姿态。如果安全限幅被触发:
- 当前 episode 标记为重采。
- 从臂按每步
1.5°、20 Hz、低增益回到起始姿态。
- 等待操作者把主臂放回起始姿态附近。
- 清空未完成 episode buffer。
- 从同一个 episode 重新开始。
驱动器已经报告异常或通信已经不健康时,代码不会自动重新使能。这是有意的安全选择。
9. 采集适合 SmolVLA 的 RGB-D 数据集 🎬
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 × 480
10 FPS
depth visualization: 250-1800 mm
EnhancedDepthFilter confidence_threshold: 51
H.264 encoders: serial
video encoding threads: 1
image writer: 1 process × 1 thread/camera
display_data: false
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 关节 + 夹爪目标 |
9.3 数据要怎样才能学会“随机摆放也能归位”?
不要只在桌面中心重复同一条漂亮轨迹。尤其面对透明瓶和高反光桌面,训练集必须主动覆盖“反光关系变化”,而不只是物体坐标变化。建议给桌面划分位置网格,并按下面的场景矩阵有计划地采集:
| 变化维度 | 需要覆盖的情况 | 为什么重要 |
|---|---|---|
| 物体位置 | 左/中/右、近端/远端、目标区附近 | 防止模型记住固定抓取轨迹 |
| 透明瓶姿态 | 直立、横放、斜放、不同标签和瓶盖朝向 | 瓶壁、标签与背景形成的视觉线索不同 |
| 高光关系 | 桌面高光落在瓶前、瓶后、瓶侧,亮度在部署允许范围内变化 | 深度孔洞和 RGB 对比度会随角度、照明改变 |
| 遮挡关系 | 香蕉遮瓶、瓶遮香蕉、两者相邻或分离 | 训练模型利用全局与腕部视角互补 |
| 操作过程 | 顺利抓取、轻微偏位后修正、重新接近 | 学到闭环纠偏,而不是只复刻完美演示 |

- 香蕉的左、中、右、近端、远端位置。
- 香蕉不同弯曲朝向和旋转角度。
- 透明瓶直立、横放、斜放及不同标签朝向。
- 两物体互相靠近、部分遮挡和交换相对位置。
- 部署范围内不同强度和位置的桌面高光,以及透明瓶前后背景对比变化。
- “先香蕉后瓶子”和“先瓶子后香蕉”两种成功顺序。
- 从轻微偏差中修正抓取的示范,而不只是每次完美直线接近。
建议把高难样本单独做标记或至少记录 episode 清单,例如“瓶身与高光重叠”“瓶子横放且标签背向相机”“瓶子和香蕉部分遮挡”。后续模型失败时,才能判断缺的是哪类演示,而不是笼统地再采几十条相似数据。
同时保持这些条件稳定:
- 相机安装位置和内外参。
- 目标归位区域。
- 任务文本。
- 深度滤波模型、阈值和 250-1800 mm 可视化范围。
- 采集 FPS 与关节校准。
这里的“保持稳定”不等于把灯光永久锁死。相机位置、滤波链路和任务定义应固定;照明和反光变化则应在实际部署可能出现的范围内受控采样。不要在采集时全部使用柔光哑光背景,却要求模型部署后突然面对镜面高光和透明瓶。
43 个 episode 足以完成一次基线训练,但对于两种物体随机摆放,尤其是透明瓶与高光关系不断变化的泛化仍偏少。第一轮实机评估后,建议根据失败区域补充 40-80 个有针对性的成功 episode,而不是只把旧数据训练更多轮。新增数据应优先补齐失败场景,避免数量增加了,视觉分布却几乎没有变。
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。
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 硬故障阈值
9.6 空 episode 不再让程序崩溃
如果操作者立即结束或安全故障发生在第一帧之前,旧代码会对空 buffer 调用 save_episode(),报:
You must add one or several frames with add_frame before calling add_episode
当前记录流程会检测 buffer 大小,跳过空 episode,并清理临时图像。
9.7 PyAV 的canonical_name 兼容问题
部分 ARM64 PyAV 版本的 codec 对象没有 canonical_name。当前代码按以下顺序兼容获取:
canonical_name → name → long_name → str(codec)
因此不会再因为视频元数据字段差异让已经采完的 episode 在保存阶段失败。
10. 分批数据合并与深度格式修复 🧩
10.1 推荐分批采集
把不同批次保存到不同目录,先检查、删除明显失败的数据,再合并:
bash examples/rebot_b601_smolvla_record/merge_b601_datasets.sh
编辑脚本中的:
SOURCE_ROOTS
SOURCE_REPO_IDS
OUTPUT_ROOT
OUTPUT_REPO_ID
所有源数据必须具有相同 FPS、robot type、feature 名称、形状和 dtype。
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。
10.3 图像 CHW/HWC 与深度 dtype
PyAV/TorchVision 可能返回 (3,H,W) float 图像,而数据集期望 (H,W,3);Hugging Face 载入深度时也可能临时表现为 int64。当前 resample 工具会:
- 将图像恢复为元数据声明的 channel-last 格式再写入。
- 将深度安全转换回
uint16。
10.4ArrowTypeError: array[uint16]
原始深度是 Array2D(uint16) Parquet 列,不是视频。合并代码必须使用完整 schema 恢复这个扩展类型。当前实现通过显式 feature schema 和直接 Parquet I/O 处理,避免 PyArrow 把 NumPy dtype 误当成不支持的对象。
失败的 merge 会留下不完整输出目录。确认路径后移走或删除不完整目标,再重跑;源数据不会被修改。
10.5 Hugging Face cache 把系统盘吃满
旧合并方法使用 Dataset.from_parquet() 时,可能在:
~/.cache/huggingface/datasets
为每个源 Parquet 生成重复 Arrow 缓存,几十 GB 很快就没了。当前合并读取绕过这类重复缓存。
仍建议把 cache 放到数据盘:
export HF_HOME=/你的数据盘/huggingface
export HF_DATASETS_CACHE=${HF_HOME}/datasets
mkdir -p "${HF_DATASETS_CACHE}"
清理前先确认没有训练、合并或可视化进程使用缓存。不要用未经检查的递归删除命令指向变量路径。
11. 上传云端并训练 SmolVLA ☁️

11.1 上传哪些内容?
建议上传:
- 当前修改版代码仓库。
- 合并后的完整数据集目录。
smolvla_base本地模型目录。
SmolVLM2-500M-Video-Instruct本地目录。
云端不需要编译或运行 Orbbec C++ bridge,因为训练读取的是已经采好的数据。Jetson 上的 model.sm4 和相机许可也不需要上传到云端。
11.2 复制仓库后一定重新安装 editable package
Conda 环境中的 editable install 保存绝对路径。把仓库直接复制到云端后,旧 lerobot-train 可能仍指向不存在的原机器目录。
cd /root/gpufree-data/reBotArm_SmolVLA
python -m pip install -e ".[smolvla]"
python -c 'import lerobot; print(lerobot.__file__)'
训练脚本也会把当前仓库的 src 放到 PYTHONPATH,并使用 python -m lerobot.scripts.lerobot_train,进一步避免调用旧入口。
11.3 离线下载模型时,仓库 ID 使用正斜杠
Windows 下错误示例:
lerobotsmolvla_base
Hugging Face repo id 不是文件路径,正确命令是:
hf download lerobot/smolvla_base
--local-dir models/smolvla_base
hf download HuggingFaceTB/SmolVLM2-500M-Video-Instruct
--local-dir models/SmolVLM2-500M-Video-Instruct
--exclude "onnx/*"
仅上传 smolvla_base 还不够。SmolVLA 在构造 VLM 和 tokenizer 时仍需要 SmolVLM2 的 config/processor/tokenizer。当前 factory 会把本地 vlm_model_name 同步覆盖给 tokenizer,避免离线训练时偷偷访问 Hugging Face Hub。
11.4 云端建议先做 2-step 冒烟测试
bash examples/rebot_b601_smolvla_record/train_smolvla_local.sh
--dataset-root /root/gpufree-data/datasets/rebot_b601_banana_bottle_rgbd_merged
--repo-id local/rebot_b601_banana_bottle_rgbd_merged
--policy-path /root/gpufree-data/models/smolvla_base
--vlm-path /root/gpufree-data/models/SmolVLM2-500M-Video-Instruct
--output-dir /root/gpufree-data/outputs/rebot_smolvla_smoke
--batch-size 2
--steps 2
--save-freq 2
--num-workers 0
启动日志必须显示:
observation.state (21,)
observation.images.wrist (3, 480, 640)
observation.images.top (3, 480, 640)
observation.images.top_depth (3, 480, 640)
action (7,)
如果仍是 camera1/camera2/camera3 和 6 维 action,立刻停止。那说明基础 checkpoint 的通用 embodiment feature 没有正确绑定到 B601 数据集。
冒烟测试前还应随机抽查若干 observation.images.top_depth:透明瓶区域不应在所有帧中完全等同于桌面,桌面高光附近也不应持续整片黑屏;同时确认增强深度与 top RGB 没有明显空间错位。训练能够启动只证明 schema 正确,不证明视觉数据有效。
短冒烟测试 GPU 只有约 50% 利用率很正常:两步训练包含加载、预处理和收尾,不能代表稳定吞吐,也不能据此直接把 batch 拉到极端。
11.5 RTX 4090 24GB 正式训练参数
本项目成功完成的配置:
RUN_TAG="$(date +%Y%m%d_%H%M%S)"
OUTPUT_DIR="/root/gpufree-data/outputs/rebot_smolvla_b601_${RUN_TAG}"
bash examples/rebot_b601_smolvla_record/train_smolvla_local.sh
--dataset-root /root/gpufree-data/datasets/rebot_b601_banana_bottle_rgbd_merged
--repo-id local/rebot_b601_banana_bottle_rgbd_merged
--policy-path /root/gpufree-data/models/smolvla_base
--vlm-path /root/gpufree-data/models/SmolVLM2-500M-Video-Instruct
--output-dir "${OUTPUT_DIR}"
--batch-size 12
--num-workers 8
--steps 14000
--save-freq 3500
--log-freq 100
--optimizer-lr 1e-4
--scheduler-warmup-steps 700
--scheduler-decay-steps 14000
--use-amp true
训练策略:
freeze_vision_encoder = true
train_expert_only = true
n_action_steps = 10
11.6 记录 GPU 状态
可以另开终端:
mkdir -p /root/gpufree-data/diag
nvidia-smi
--query-gpu=timestamp,index,name,temperature.gpu,utilization.gpu,utilization.memory,memory.used,memory.total,power.draw
--format=csv
-l 1 | tee /root/gpufree-data/diag/gpu_train.csv
训练日志同时保存:
bash examples/rebot_b601_smolvla_record/train_smolvla_local.sh ...
2>&1 | tee /root/gpufree-data/diag/train.log
11.7 训练结果怎么看?
| 指标 | 结果 |
|---|---|
| 数据量 | 43 episodes,18,392 frames,10 FPS |
| Batch size | 12 |
| Steps | 14,000 |
| 数据集等效周期 | 约 9.13 epochs |
| 可训练参数 | 约 100M |
| 总参数 | 约 450M |
| Loss | 0.362 → 0.016 |
| Gradient norm | 2.735 → 0.216,无爆炸或 NaN |
| Checkpoints | 3,500 / 7,000 / 10,500 / 14,000 |
| 总耗时 | 85 分 55 秒 |
上面的是我一次稳定收敛的训练,但不是“实机已经成功”的证明。训练没有独立验证环境,而且 10,500 step 后 loss 已在 0.016-0.017 附近平台化。模型可能只是更熟练地拟合常见摆放,却仍会在“透明瓶与桌面高光重叠”时失败。最终必须比较 7,000、10,500 和 14,000 三个检查点在分层实机测试中的任务成功率。
12. 把训练模型带回 Jetson 开始推理 🚀

12.1 需要复制什么?
从云端选择一个 checkpoint,比如只复制其中的:
checkpoints/014000/pretrained_model/
推理不需要该 checkpoint 外层的 optimizer 和 training state。Jetson 还需要本地 SmolVLM2 config、tokenizer 和 processor 目录。
示例路径:
/home/r/models/rebot_smolvla_b601_14000/pretrained_model
/home/r/models/SmolVLM2-500M-Video-Instruct
12.2 编辑推理脚本
脚本位置:
examples/rebot_b601_smolvla_record/infer_b601_smolvla_rgbd.sh
检查:
POLICY_PATH="/home/r/models/rebot_smolvla_b601_14000/pretrained_model"
VLM_PATH="/home/r/models/SmolVLM2-500M-Video-Instruct"
FOLLOWER_PORT="/dev/ttyACM0"
TOP_SERIAL="你的 Gemini 335L 序列号"
WRIST_SERIAL="你的 Gemini 305 序列号"
LINGBOT_MODEL="/home/r/ws/model.sm4"
12.3 首次运行
bash examples/rebot_b601_smolvla_record/infer_b601_smolvla_rgbd.sh
也可以临时覆盖路径:
bash examples/rebot_b601_smolvla_record/infer_b601_smolvla_rgbd.sh
--policy-path /home/r/models/rebot_smolvla_b601_14000/pretrained_model
--vlm-path /home/r/models/SmolVLM2-500M-Video-Instruct
启动日志中必须看到 EnhancedDepthFilter 已成功加载 model.sm4。推理时应继续使用采集阶段的 Gemini 335L 安装位置、置信度阈值和 250-1800 mm 深度可视化范围;不要为了临时“让图更好看”而单独调整部署端色彩映射。对于本项目,深度处理一致性就是模型输入契约的一部分。
首次推理默认只运行一个 30 秒 episode,并采用更保守的:
arm max_relative_target = 5°
gripper max_relative_target = 15°
multi-joint hold threshold = 20° requested delta
single-joint hold threshold = 30° requested delta
automatic recovery = false
n_action_steps = 10
max_relative_target=5° 是真正限制每个控制周期运动量的硬限幅;后两项则只负责判断模型原始目标是否已经异常离群。SmolVLA 以动作块输出绝对关节目标,动作块切换时可能出现中等幅度跳变,因此推理脚本允许这些目标先经过 5° 限幅平滑追踪,只在至少两个关节同时相差 20°,或任一关节相差 30° 时进入 safety hold。遥操采集仍保留原来的比例判据,二者不会互相改变。
开始前将从臂放在训练数据常见的安全初始姿态,托住机械臂并准备急停。不要因为第一次触发安全限幅,就直接把 max_relative_target 恢复到采集时的 12°/30°。看到 target was slew-limited ... continuing with bounded commands 只表示平滑限幅正在工作,不等于电机故障;看到 safety hold active 才表示模型目标越过了独立停机阈值。
12.4 推理时为什么关闭夹爪映射?
采集流程保存的是 robot.send_action() 后真正下发到从臂的动作,因此模型学习的是从臂夹爪坐标。如果推理时再执行一次 leader → follower 映射,夹爪会被二次换算,开合就会失真。
所以:
遥操采集: gripper_map_leader_to_follower = true
策略推理: gripper_map_leader_to_follower = false
12.5 三个检查点都要测
建议准备固定编号的 20 组随机摆放,并按普通反光、强高光靠近瓶体、瓶身与高光重叠、透明瓶横放、两物体互相遮挡等条件分层。每个 checkpoint 都使用完全相同的场景编号,分别评估:
- 完整任务成功率。
- 香蕉归位成功率。
- 可乐瓶归位成功率。
- 高反光条件下透明瓶归位成功率。
- 瓶体首次定位成功率与抓取前重定位次数。
- 安全中止次数。
- 平均完成时间。
如果 10,500 比 14,000 更好,就使用 10,500。更低的训练 loss 不一定意味着更好的真实泛化。
当模型看得对但纠偏太慢,可以尝试:
bash examples/rebot_b601_smolvla_record/infer_b601_smolvla_rgbd.sh
--n-action-steps 5
10 FPS 下,n_action_steps=10 约等于执行 1 秒后重规划;改成 5 会更频繁推理,但 Jetson 的计算压力也会增加。
12.6 最新实机推理记录:链路跑通,不等于任务跑通
我在 14,000 step checkpoint 上进行的一次阶段性测试使用了:
episode_time_s = 300
fps = 10
n_action_steps = 20
max_relative_target = 3°
multi-joint hold threshold = 20°
single-joint hold threshold = 30°
程序实际连续运行约 106 秒后安全退出。调试日志显示:
- Gemini 305 RGB、Gemini 335L RGB-D 和 EnhancedDepthFilter 全程正常,没有相机读帧超时。
- 设备内许可校验成功,
model.sm4与置信度阈值51正常生效。
- MotorBridge 500 Hz 命令流没有连续发送失败,所有电机保持
ENABLED,没有驱动器故障。
- 日志节流后仍记录到 64 条
target was slew-limited,说明实际发生的限幅次数只会更多。
joint_2和joint_3最常进入限幅,记录到的最大目标差分别约为22.48°和22.95°。
- 夹爪两次进入接触保持,又两次被策略的张开动作释放。接触位置约为
-213.60°和-215.13°。
- 最终
joint_2从当前位置-100.38°收到-69.86°目标,差值30.52°,越过单关节30°阈值后触发 safety hold。
这份日志证明视觉、模型、电机流和安全保护已经组成可持续运行的在线链路;但大量持续限幅也说明策略尚未形成稳定的闭环跟随。尤其要注意:最后的 joint_2 目标是在从较低位置返回关节中部,方向本身不一定危险。它更可能反映了“旧动作队列 + 从臂受限后未能及时跟上”造成的状态差,而不能简单归结为电机、相机或奇异点故障。
12.7 为什么n_action_steps=20 在这里容易积累误差?
SmolVLA 的 select_action() 会在动作队列清空时才重新运行模型。10 FPS 下执行 20 个动作,名义上相当于约 2 秒开环窗口;模型推理本身还会占用时间,实际重新观察场景的间隔可能更长。与此同时,部署端把关节每周期变化限制为 3°,远比采集阶段保守。于是可能形成:
模型一次预测较长动作块
↓
从臂每周期只能追赶 3°
↓
动作队列继续执行基于旧观察的未来动作
↓
真实姿态逐渐落后于队列目标
↓
持续限幅,最终触发大目标差 safety hold
因此,不能通过无限提高 30° 阈值来掩盖问题。下一轮应先固定场景和初始姿态,把测试缩短到 60 秒,并使用:
bash examples/rebot_b601_smolvla_record/infer_b601_smolvla_rgbd.sh
--episode-time-s 60
--n-action-steps 5
--max-relative-target 3
--safety-hold-multi-joint-delta 20
--safety-hold-single-joint-delta 30
若方向正确且没有 safety hold、只是动作明显偏慢,再保持 n_action_steps=5,依次单独测试 max_relative_target=4 和 5。每次只改变一个变量,避免把 checkpoint、动作窗口和速度限制同时调整后无法判断原因。
12.8 透明瓶抓取仍未成功
在我的调试日志中, gripper contact detected 显示夹爪满足了“已经闭合一段距离、速度较低、达到接触判据”,随后控制器切换到有界保持力矩。它不能证明透明瓶已经位于两指之间,更不能证明瓶子在抬升、搬运和放置过程中没有滑落。当前仍缺少:
- 对“空夹桌面、夹到瓶身、夹到瓶盖、夹偏后滑脱”的区分。
- 抓取后抬升阶段的视觉或状态成功判定。
- 高光与瓶身重叠、瓶体横放或斜放等难例的足量成功演示。
- 7,000、10,500、14,000 checkpoint 在同一批测试场景上的量化对比。
- 对夹爪两次接触、两次释放究竟是正常任务阶段还是策略抖动的录像复核。
透明瓶抓取也不只是视觉问题。瓶体可能变形、表面摩擦小、两指接触面窄;即使 RGB 与增强深度定位正确,接近角度、夹爪软垫、闭合速度和保持力矩仍会决定它是否滑落。后续优化应把“看见透明瓶”“对准抓取点”“形成稳定夹持”“抬升验证”“搬运并释放”分别统计,而不是只记录最终完整任务成功或失败。
到这里,我们完成的是从官方基线改造、硬件接入、重力补偿遥操、RGB-D 采集、数据合并、云端训练,到 Jetson 启动并持续运行训练模型的工程闭环。真正的任务闭环,也就是在高反光环境中稳定抓取透明可乐瓶并完成归位,目前仍处于部署微调与补充验证阶段。🛠️
13. 高频故障速查表 🧯
| 现象 | 常见原因 | 处理办法 |
|---|---|---|
| 所有 DM 电机握手失败 | 把 MotorBridge 当 can0、端口错、24 V/急停/线束问题 |
使用 /dev/ttyACM* + transport=motorbridge,检查供电与端口归属 |
python-can not installed 但 pip 已安装 |
CLI 入口来自其他 Python 环境 | which lerobot-setup-can、which python,重新 editable install;MotorBridge 场景通常不需要该 SocketCAN 工具 |
Draccus 无法解析 Literal['motorbridge','socketcan'] |
当前 draccus 版本不支持该 Literal decode | 使用本项目中改为 str 的 transport 配置,并通过运行时校验取值 |
| Orbbec 图像是绿色噪点 | 打开了深度/IR/原始 UVC 节点 | 使用 OrbbecSDK 序列号和 C++ bridge,不使用未验证的 /dev/video* |
Export.h 找不到 |
忘了 SDK generated include | 传 ORBBECSDK_GENERATED_INCLUDE_DIR |
Config has no member enableDevice |
SDK v2 API 与旧示例不同 | Context 枚举设备,再用 Pipeline(device) |
| bridge magic mismatch | SDK 日志污染二进制 stdout | 日志只写 stderr,stdout 只传帧包 |
| Gemini 305/335L 超时 | 设备占用、USB 带宽/供电、序列号错误、warmup 太短 | 单相机测试,关闭其他进程,分离 USB 3,使用 15/25 秒 warmup |
| 透明瓶在原始深度中局部消失 | 透射、折射、背景穿透导致测量无效或跳变 | 这是透明材质的物理难点;启用并验证 EnhancedDepthFilter,同时依赖 top/wrist RGB,不要用插值伪造“完美深度” |
| 增强深度在高光处仍有孔洞 | 镜面反射、入射角或过曝造成有效信息不足 | 调整相机/灯光到部署允许范围,检查曝光与对齐,补采对应难例;滤波不能恢复完全缺失的测量 |
| 训练正常但遇到高反光桌面就失败 | 采集数据大多来自哑光或固定光照,难场景覆盖不足 | 按高光位置、瓶体姿态和遮挡关系分层补采,并保持采集与推理的滤波参数一致 |
License not yet valid 2003 |
Jetson 时间回到 1970 | 修复 RTC/NTP/系统时间,保留设备内官方许可校验 |
| 主臂难拖动 | 重力补偿方向/倍率、kp/kd 或机械摩擦 | 先 dry-run,逐关节调试;使用已验证参数,不盲目加力矩 |
| 主臂夹爪突然自行张大 | 辅助力矩或位置增益过大 | force_assist、kp=kd=0、open bias 0.09、limit 0.15 |
| 从臂夹爪行程不足 | 端点映射或相对限幅错误 | 先直接命令验证 -330→10,再标定 leader/follower endpoint |
| 从臂夹住物体后 gripper 失能 | 高 kp 持续堵转、机械或线束问题 | 使用限力闭合和柔和接触保持,检查机构,不要只增大力矩 |
| 从臂突然多个关节失能 | 命令 gap、电源、急停、MotorBridge、数据线受压 | 查 gap 时间关系;独立电源、线缆应力释放、同步采集系统日志 |
| 下一 episode 开始时报 stream fault | 上一个 episode 编码造成已恢复但被锁定的 gap | H.264 单线程、串行编码、500 Hz 独立命令流、0.5 秒硬阈值 |
save_episode 提示没有 frame |
第一帧前退出或故障 | 当前代码跳过空 buffer;不要手工伪造 episode |
PyAV 没有 canonical_name |
ARM64 PyAV API 差异 | 使用兼容 codec 名称回退逻辑 |
| 合并提示 FPS 不一致 | 分批使用 15/10 FPS | 真正重采样到 10 FPS,不能只改 metadata |
array[uint16] ArrowTypeError |
深度扩展类型丢失 | 使用当前直接 Parquet + 显式 schema 合并代码 |
~/.cache/huggingface/datasets 爆满 |
合并产生重复 Arrow cache | 用当前 merge 实现,把 HF cache 放数据盘,谨慎清理闲置 cache |
| 云端训练仍加载旧代码 | editable install 指向原机器绝对路径 | 在云端重新 pip install -e ".[smolvla]" 并检查 lerobot.__file__ |
| 离线训练仍访问 Hub | 只上传 smolvla_base,VLM/tokenizer 路径未覆盖 | 同时上传 SmolVLM2,并传 --vlm-path、设置 offline 环境变量 |
推理报 No module named transformers |
Jetson 环境只安装了基础 LeRobot,未安装 smolvla extra |
按仓库约束安装 SmolVLA 依赖;不要直接安装无约束的最新版 transformers |
| 安装 transformers 后 Hub 版本冲突 | 无约束安装了 transformers 5.x,并把 huggingface-hub 升到 1.x |
降回 transformers>=4.57.1,<5.0.0、huggingface-hub>=0.34.2,<0.36.0,再运行 python -m pip check |
tokenizer 报云端绝对路径 HFValidationError |
checkpoint 的 policy_preprocessor.json 保存了训练机上的 /root/gpufree-data/... 路径,Jetson 上不存在 |
同步本项目最新版 src/lerobot/policies/factory.py,并传正确 --vlm-path;factory 会在内存中覆盖 tokenizer 路径,不必修改 checkpoint JSON |
推理启动即提示 dataset name 不以 eval_ 开头 |
LeRobot 要求带 policy 的评估数据集在命名空间后的名称以 eval_ 开头 |
使用 local/eval_rebot_b601_smolvla_时间戳;当前推理脚本已按此生成并提前校验自定义 --repo-id |
| 推理夹爪开合错误 | 模型输出被再次做 leader→follower 映射 | 推理设置 gripper_map_leader_to_follower=false |
推理启动时夹爪反馈超出 -330..10° |
夹爪被断电移动到训练坐标范围之外,或零位与采集阶段不一致 | 不要直接放宽限位;先恢复到训练使用的夹爪坐标并检查校准。当前启动保护会在使能前拒绝运行 |
推理中提示 target was slew-limited |
策略绝对目标与当前位置有中等差值,但仍低于异常停机阈值 | 这是受控的 5°/周期 平滑追踪;观察是否很快收敛,并检查运动方向是否合理,无需立刻放宽硬限幅 |
推理中提示 safety hold active |
至少两个关节目标差达到 20°,或一个关节达到 30°;常见于异常初始姿态、动作块离群或模型泛化不足 | 保持托举并检查日志中的 requested_delta;先换回训练常见初始姿态,可比较 --n-action-steps 5。不要直接关闭 safety hold;只有充分验证后才分别调整 --safety-hold-multi-joint-delta 和 --safety-hold-single-joint-delta |
使用 n_action_steps=20 后长期出现限幅 |
10 FPS 下连续执行约 2 秒旧动作,同时较小的关节步长使真实姿态落后于预测队列 | 先降到 n_action_steps=5,固定场景运行 60 秒;方向正确但偏慢时,再逐级把关节步长从 3 调到 4、5,每次只改一个变量 |
日志显示 gripper contact detected,但透明瓶没有被抓起 |
接触检测只证明夹爪遇到阻力,也可能夹到桌面、瓶边或发生偏夹 | 结合 wrist RGB 和录像复核接触位置;分别统计对准、闭合、抬升、搬运成功,不要把接触事件直接算作抓取成功 |
14. 复现时请牢记的安全原则 ❤️
- 从臂运动、恢复、异常退出时都必须有人准备托住机械臂。
- 驱动器异常后不自动重新使能,先查明电源、通信或机械原因。
- 不通过关闭状态检查、无限提高力矩或放大安全阈值来“让报错消失”。
- 电机问题和视觉问题分开测试;单模块稳定后再叠加负载。
- 数据线走线属于安全系统的一部分,不是最后才整理的附件。
- 透明物体抓取尤其需要多样化数据和保守速度,不要仅凭训练 loss 宣布成功。
15. 关键代码入口 📚
| 功能 | 文件 |
|---|---|
| Orbbec Python 相机 | src/lerobot/cameras/orbbec/camera_orbbec.py |
| Orbbec 相机配置 | src/lerobot/cameras/orbbec/configuration_orbbec.py |
| Orbbec C++ bridge | src/lerobot/cameras/orbbec/cpp/orbbec_rgbd_bridge.cpp |
| Bridge CMake | src/lerobot/cameras/orbbec/cpp/CMakeLists.txt |
| B601-DM follower | src/lerobot/robots/rebot_b601_follower/ |
| B601-DM leader | src/lerobot/teleoperators/rebot_b601_leader/ |
| MotorBridge 命令流 | src/lerobot/motors/rebot_motorbridge.py |
| SmolVLA 深度过滤 processor | src/lerobot/policies/smolvla/processor_smolvla.py |
| 数据采集安全与重采 | src/lerobot/scripts/lerobot_record.py |
| 数据采集脚本 | examples/rebot_b601_smolvla_record/record_b601_smolvla_rgbd.sh |
| 数据合并脚本 | examples/rebot_b601_smolvla_record/merge_b601_datasets.sh |
| FPS 重采样 | examples/rebot_b601_smolvla_record/resample_b601_dataset.py |
| 云端训练脚本 | examples/rebot_b601_smolvla_record/train_smolvla_local.sh |
| Jetson 推理脚本 | examples/rebot_b601_smolvla_record/infer_b601_smolvla_rgbd.sh |
| 从臂只读状态检查 | examples/rebot_b601_smolvla_record/debug_follower_state_readonly.py |
| 从臂夹爪单独置零 | examples/rebot_b601_smolvla_record/zero_follower_gripper_at_closed.py |
16. 当前成果边界与后续工作 🧭
目前,本项目进展如下:
| 模块 | 当前状态 | 说明 |
|---|---|---|
| B601-DM 同型号主从臂遥操 | ✅ 已实机验证 | 主臂重力补偿、夹爪微力辅助、从臂位置映射可用于采集 |
| MotorBridge 独立命令流与故障保护 | ✅ 已实机验证 | 500 Hz 保活、状态检测、异常保持和安全退出已覆盖多轮故障排查 |
| Gemini 305 + Gemini 335L C++ bridge | ✅ 已实机验证 | 腕部 RGB、全局 RGB-D 可同时稳定接入 |
| EnhancedDepthFilter 与设备许可 | ✅ 已实机验证 | 设备内许可、model.sm4、对齐深度和 Python 数据包链路已运行 |
| RGB + 增强深度 LeRobot 数据集 | ✅ 已完成 | 43 episodes、18,392 frames、10 FPS,已完成重采样、合并和格式修复 |
| RTX 4090 SmolVLA 训练 | ✅ 已完成基线 | 14,000 steps 稳定收敛,生成多个可部署 checkpoint |
| Jetson Orin NX 在线模型推理 | 🟡 已进入微调 | 已连续运行约 106 秒,硬件链路稳定,动作窗口与限幅仍需调优 |
| 香蕉随机摆放归位泛化 | 🟡 尚未完成量化 | 需要固定测试矩阵比较 checkpoint 和失败类型 |
| 高反光环境透明瓶稳定抓取归位 | ❌ 尚未稳定实现 | 当前不能声称已经达到可重复成功,需要补采难例并完善闭环抓取验证 |
| 无人值守生产部署 | ❌ 不在当前完成范围 | 仍需推理时序监控、故障环形日志、工作空间保护和长期可靠性测试 |
建议后续工作按下面顺序推进:
- 用
n_action_steps=5和max_relative_target=3/4/5完成单变量实机对比。
- 在相同初始姿态和物体摆放下比较 7,000、10,500、14,000 checkpoint。
- 把透明瓶任务拆成定位、对准、闭合、抬升、搬运、释放六个可观察阶段,记录每阶段失败率。
- 针对失败最多的高光位置、透明瓶姿态和遮挡关系补充 40-80 个高质量成功 episode。
- 为部署循环增加实际 FPS、单次推理耗时、动作队列年龄和连续限幅时长统计。
- 对越阈动作采用“保持当前位置、清空旧动作队列、立即重规划;连续异常才停机”的二阶段策略。
- 增加抓取后抬升确认,并结合 wrist RGB 判断空夹、滑落和稳定持有。
- 检查夹爪指面材料、接触宽度和瓶体变形,避免把机械抓取问题全部归因于模型。
项目因为当前工作时间安排先停在这里,但阶段性结果仍然有价值:它已经把一条涉及 DM 主从臂、双 Orbbec 相机、受许可保护的增强深度、LeRobot 数据格式、云端训练和 Jetson 部署的复杂链路跑通,并留下了真实失败日志与安全边界。后续复现者可以从部署微调继续前进,而不必重新踩完前面的坑。🌱

17. 参考资料与致谢 🙌
感谢 Seeed Studio、奥比中光、Hugging Face LeRobot 及开源社区提供的机械臂、相机 SDK、模型与工程基础。本文中的 EnhancedDepthFilter 模型、扩展库和设备许可应遵守奥比中光对应版本的授权与分发要求。
这套系统远没有“按一下运行键就永远稳定”那么简单,欢迎继续补充更精确的电源、MotorBridge、线束和性能诊断结果,让这个项目从“能够复现”继续走向“稳定、可靠、可扩展”。🌈