1a013d8803f32.png

基于 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

也就是让机械臂学习一条可以泛化的桌面整理策略:

  1. 香蕉和透明塑料可乐瓶的起始位置、朝向可以变化。
  1. 两件物体最终对应的位置保持明确、稳定。
  1. 机械臂能够通过视觉判断物体位置,而不是记忆某一条固定轨迹。
  1. 在桌面高光、局部镜面反射和透明瓶深度不完整的情况下,策略仍能利用多路视觉完成操作。
  1. 训练、采集和推理使用同一套视觉分布,尤其是同一种深度滤波模型、阈值与可视化范围。

这比普通哑光物体抓取更难:透明瓶会让深度出现背景穿透、边缘缺失或跳变,高反光桌面又会带来局部孔洞与飞点。项目的关键问题因此不只是“能否接入深度相机”,而是“能否把同一套经过验证的增强深度处理稳定地贯穿采集、训练和推理”。

本项目的第一阶段采用模仿学习:操作者拖动 B601-DM 主臂,从臂同步执行动作,系统同时记录相机、关节状态与实际下发动作,最后用 SmolVLA 学习“看见什么、当前机械臂是什么状态、下一步该怎么动”。

1.1 数据流总览

1a01419e68f3a.png

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 主臂重力补偿遥操

1a0146bd381fd.png

单纯关闭主臂电机虽然轻,但机械臂会坠落;使用刚性位置控制又很难拖动。项目复用了 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 蚂蚁灵波轻量模型:为透明瓶与高反光场景补强深度线索

1a014741a555b.png

这是本项目最具辨识度的视觉改进。透明塑料可乐瓶会透射和折射部分光线,深度图中常见瓶壁缺失、背景深度“穿过”瓶身、轮廓跳变;高反光桌面则可能产生局部无效值、飞点和随观察角度变化的闪烁。直接把这样的原始深度彩色化后送进模型,既可能增加噪声,也可能让策略学到不稳定的伪特征。

需要先讲清楚一件事:任何深度滤波模型都不能凭空恢复传感器从未测到的全部透明表面。 本项目使用蚂蚁灵波轻量模型的目标,是在 Jetson 可承担的计算量下改善有效区域的连续性、抑制随机噪声并稳定物体和桌面边缘,再与两路 RGB 共同提供信息,而不是把透明物体检测完全押在深度图上。

本项目使用奥比中光 SDK 中的 EnhancedDepthFilter 与裁剪模型 model.sm4。它没有作为离线数据清洗脚本附加在采集之后,而是直接进入 OrbbecSDK v2 C++ bridge:

  1. Gemini 335L 同步输出 color + depth FrameSet。
  1. 深度先对齐到彩色图。
  1. SDK 创建 ob::EnhancedDepthFilter(device, model_path)。
  1. 调用官方 filter 处理整个 FrameSet。
  1. Python 只接收滤波后的深度;启用增强模式后若 filter 或许可失败,立即报错,不允许悄悄退回原始深度。
  1. 同一份滤波深度同时生成未伪彩化的 uint16 深度和三通道 top_depth。

原版 SmolVLA 使用视觉编码器处理三通道图像,因此当前方案让 top_depth 作为第三路视觉输入;滤波后的 uint16 深度继续归档,但由 SmolVLARawDepthFilterProcessor 在进入 GPU 前移除,避免把大尺寸整数深度误送进视觉模型。最终形成互补关系:

  • top RGB:识别透明瓶的瓶盖、标签、轮廓和目标区域等语义线索。
  • wrist RGB:在接近和抓取阶段观察局部边缘、夹爪与物体相对位置。
  • top_depth:提供经增强后的桌面几何、距离层次和可用瓶体边界线索。
  • uint16 depth:忠实归档增强后的测量值,用于质量检查和未来深度网络研究。

更重要的是,采集和推理均在设备侧运行同一套 C++ filter、同一个 model.sm4、相同置信度阈值和相同深度可视化范围。这样训练看到的“深度语言”与部署时完全一致,避免出现云端离线处理很漂亮、Jetson 实时输入却是另一种分布的情况。

2.5 数据采集不再和电机保活“抢一条线程”

_cgi-bin_mmwebwx-bin_webwxgetmsgimg__&MsgID=9170302337045191375&skey=@crypt_b87d6e1d_789c53075c32fa25a64dc8e14f92cad6&mmweb_appid=wx_webfilehelper.jpg

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 物理连接建议

推荐优先级如下:

  1. 两台 Orbbec 相机分别接 Jetson 的独立 USB 3 高速路径。
  1. 如果必须使用扩展坞,选择外接供电的 USB 3 Hub,不要让两台 RGB-D 相机和大量低速设备挤在同一条不稳定链路上。
  1. 两台机械臂电源接到可靠、彼此独立的供电插座或电源支路。
  1. MotorBridge 串口线、机械臂内部 CAN/数据线保留完整活动余量,不能被底座、关节或桌沿压住。
  1. 无论软件多么完善,从臂首次测试时都要有物理支撑和随手可按的急停。

使用以下命令检查 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 不提供这个接口。本项目改为:

  1. ob::Context::queryDeviceList() 枚举设备。
  1. 优先使用 getDeviceBySN()。
  1. 如果 SDK 明明列出序列号却直接查找失败,再遍历列表比较序列号并使用 getDevice(index)。
  1. 用选中的 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

不要只确认“程序没有报错”,还要把透明瓶放到真实高反光桌面上做动态检查:

  1. 缓慢旋转和横移透明瓶,观察瓶身深度是否整片反复消失或跳到背景距离。
  1. 改变瓶子与桌面高光的相对位置,观察桌面是否出现大面积随帧闪烁的孔洞。
  1. 对比瓶体边缘、瓶底接触区和香蕉轮廓在连续帧中的稳定性。
  1. 分别测试无瓶、只有透明瓶、只有香蕉和两者部分遮挡,排除 filter 只在单一构图下看起来正常。
  1. 检查 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

依次检查:

  1. OrbbecViewer、旧 bridge、ROS 相机节点是否仍占用设备。
  1. 两个脚本是否错误地同时打开同一序列号。
  1. USB 是否确实运行在 5000M,而不是退化到 USB 2。
  1. 两台相机是否挤在无供电扩展坞上。
  1. 是否给 Gemini 335L 足够的 25 秒 warmup 与 25 秒 timeout。
  1. 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

如果抬起后缓慢下坠,可按以下顺序调试:

  1. 先确认重力方向和关节角零点正确。
  1. 小幅提高 torque_scale,不要越过已验证范围。
  1. kp 用于减小姿态漂移,过高会变得难拖。
  1. kd 用于抑制晃动,过高会有明显黏滞感。
  1. 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. 为什么从臂曾经会突然失能坠落?⚠️

1a014776c750c.png

这是整个项目最值得认真记录的一段。

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,优先排查:

  1. 从臂 24 V 电源和电流余量。
  1. 两台机械臂是否共用接触不良的插排。
  1. 急停链路和 MotorBridge 固件。
  1. 串口线、臂内 CAN/数据线和连接器。
  1. 运动范围内是否存在压线点。

一次现场排查中,把两台机械臂电源改接到独立供电后,程序维持时间明显变长;另一个高风险点是从臂运动时数据线会被结构压迫。这两个现象都不是最终定论,但足以把电源和线束提升为最高优先级检查项。

8.4 只剩夹爪DISABLED

这通常更像局部机械负载、夹爪线束或驱动器保护。当前限力闭合控制正是为减少持续堵转而加入。不要通过无限提高力矩掩盖问题。

8.5 episode 故障恢复

采集开始时记录从臂初始姿态。如果安全限幅被触发:

  1. 当前 episode 标记为重采。
  1. 从臂按每步 1.5°、20 Hz、低增益回到起始姿态。
  1. 等待操作者把主臂放回起始姿态附近。
  1. 清空未完成 episode buffer。
  1. 从同一个 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 对比度会随角度、照明改变
遮挡关系 香蕉遮瓶、瓶遮香蕉、两者相邻或分离 训练模型利用全局与腕部视角互补
操作过程 顺利抓取、轻微偏位后修正、重新接近 学到闭环纠偏,而不是只复刻完美演示
1a013cfcda85e.png
  • 香蕉的左、中、右、近端、远端位置。
  • 香蕉不同弯曲朝向和旋转角度。
  • 透明瓶直立、横放、斜放及不同标签朝向。
  • 两物体互相靠近、部分遮挡和交换相对位置。
  • 部署范围内不同强度和位置的桌面高光,以及透明瓶前后背景对比变化。
  • “先香蕉后瓶子”和“先瓶子后香蕉”两种成功顺序。
  • 从轻微偏差中修正抓取的示范,而不只是每次完美直线接近。

建议把高难样本单独做标记或至少记录 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 ☁️

1a01479fa281c.png

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 开始推理 🚀

1a0147dad61f4.png

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. 复现时请牢记的安全原则 ❤️

  1. 从臂运动、恢复、异常退出时都必须有人准备托住机械臂。
  1. 驱动器异常后不自动重新使能,先查明电源、通信或机械原因。
  1. 不通过关闭状态检查、无限提高力矩或放大安全阈值来“让报错消失”。
  1. 电机问题和视觉问题分开测试;单模块稳定后再叠加负载。
  1. 数据线走线属于安全系统的一部分,不是最后才整理的附件。
  1. 透明物体抓取尤其需要多样化数据和保守速度,不要仅凭训练 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 和失败类型
高反光环境透明瓶稳定抓取归位 ❌ 尚未稳定实现 当前不能声称已经达到可重复成功,需要补采难例并完善闭环抓取验证
无人值守生产部署 ❌ 不在当前完成范围 仍需推理时序监控、故障环形日志、工作空间保护和长期可靠性测试

建议后续工作按下面顺序推进:

  1. 用 n_action_steps=5 和 max_relative_target=3/4/5 完成单变量实机对比。
  1. 在相同初始姿态和物体摆放下比较 7,000、10,500、14,000 checkpoint。
  1. 把透明瓶任务拆成定位、对准、闭合、抬升、搬运、释放六个可观察阶段,记录每阶段失败率。
  1. 针对失败最多的高光位置、透明瓶姿态和遮挡关系补充 40-80 个高质量成功 episode。
  1. 为部署循环增加实际 FPS、单次推理耗时、动作队列年龄和连续限幅时长统计。
  1. 对越阈动作采用“保持当前位置、清空旧动作队列、立即重规划;连续异常才停机”的二阶段策略。
  1. 增加抓取后抬升确认,并结合 wrist RGB 判断空夹、滑落和稳定持有。
  1. 检查夹爪指面材料、接触宽度和瓶体变形,避免把机械抓取问题全部归因于模型。

项目因为当前工作时间安排先停在这里,但阶段性结果仍然有价值:它已经把一条涉及 DM 主从臂、双 Orbbec 相机、受许可保护的增强深度、LeRobot 数据格式、云端训练和 Jetson 部署的复杂链路跑通,并留下了真实失败日志与安全边界。后续复现者可以从部署微调继续前进,而不必重新踩完前面的坑。🌱

1a01483cd8abf.png

17. 参考资料与致谢 🙌

感谢 Seeed Studio、奥比中光、Hugging Face LeRobot 及开源社区提供的机械臂、相机 SDK、模型与工程基础。本文中的 EnhancedDepthFilter 模型、扩展库和设备许可应遵守奥比中光对应版本的授权与分发要求。

这套系统远没有“按一下运行键就永远稳定”那么简单,欢迎继续补充更精确的电源、MotorBridge、线束和性能诊断结果,让这个项目从“能够复现”继续走向“稳定、可靠、可扩展”。🌈

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注