基于 Piper 机械臂 + Orbbec 深度相机 + YOLO 目标检测,实现自动抓取、分类放置、VLA 数据采集(LeRobot 格式)等功能。
- ROBOARM — 机械臂 + 深度相机 自动采集与控制项目
| 设备 | 说明 |
|---|---|
| Piper 机械臂 | 通过 USB-CAN 适配器连接,CAN 总线通信 |
| Orbbec 深度相机 | 通过 USB 连接,作为俯视(above)相机,用于目标检测与视觉反馈 |
| Intel RealSense 相机(可选) | 作为腕部(wrist)相机,用于双视角 VLA 数据采集与推理 |
| USB-CAN 适配器 | 连接机械臂与主机 |
| Jetson Orin / 任意 Linux 主机 | 运行控制程序 |
相机说明:数据采集与推理默认使用双相机——Orbbec Gemini 作为俯视相机(
observation/image),Intel RealSense 作为腕部相机(observation/wrist_image)。若只做单相机场景(LLM 抓取、象棋等),仅需 Orbbec 相机。
sudo apt update
sudo apt install -y can-utils ethtool build-essentialJetson 上额外需要 gs_usb 内核模块(USB-CAN 通信必需):
先检查是否已加载:
lsmod | grep gs_usb如果没有输出,说明内核未启用 gs_usb,需要编译安装。详见文末 故障排查 / gs_usb 内核模块。
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.bashrc验证:
uv --version # 应 >= 0.5.0# 克隆本仓库
git clone git@github.com:cloud666666666/AIIT-Roboarm.git
cd ~/roboarm
# 克隆 LeRobot 依赖(我们修改过的版本,与 roboarm 同级或任意位置)
git clone git@github.com:cloud666666666/roboarm-lerobot.git lerobot
# 创建软链接,让 roboarm 能找到 lerobot 源码
# 将源路径替换为你实际克隆 lerobot 的位置
ln -s ~/lerobot ~/roboarm/lerobot
# 回到 roboarm,同步 Python 环境(含 lerobot)
cd ~/roboarm
uv sync
# 验证关键依赖
uv run python -c "from piper_sdk import C_PiperInterface_V2; print('piper_sdk OK')"
uv run python -c "import lerobot; print('lerobot OK:', lerobot.__file__)"
uv run python -c "from ultralytics import YOLO; print('ultralytics OK')"说明:
uv sync会根据pyproject.toml和uv.lock自动创建.venv并安装所有依赖(Python 3.10)。
source .venv/bin/activate
SDK_DIR=$(python - <<'PY'
import inspect, os, piper_sdk
print(os.path.dirname(inspect.getfile(piper_sdk)))
PY
)
bash "$SDK_DIR/find_all_can_port.sh"示例输出:
Interface can_piper is connected to USB port 1-4.2:1.0
记下输出中的 接口名(如
can_piper、can4)和 USB 地址(如1-4.2:1.0)。
CAN_IF="can_piper" .
# 替换为上一步的实际接口名
USB_ADDR="1-4.2:1.0" # 替换为上一步的实际 USB 地址
sudo ip link set "$CAN_IF" down 2>/dev/null || true
bash "$SDK_DIR/can_activate.sh" "$CAN_IF" 1000000 "$USB_ADDR"注意:波特率固定为
1000000,不要填成100000。
ip -details link show "$CAN_IF"应看到接口处于 UP 状态,波特率为 1000000。
复制配置文件并修改:
cp config.yaml.example config.yaml编辑 config.yaml,将 arm_type 改为 piper,arm_port 改为实际的 CAN 接口名(如 can_piper 或 can4)。
# 只读连通性测试
uv run python arm/calibrate_offset.py如果能打印当前关节角度/末端位姿,说明 CAN 通信正常。
# 运动测试
uv run python arm/piper_ctrl_by_sdk.py机械臂应能进行使能并执行测试运动。
所有运行配置集中在项目根目录的 config.yaml 中(复制自 config.yaml.example)。
arm_port: can4 # Piper 为 can*,Lerobo 为 COM*
arm_type: piper # piper 或 lerobo
arm_offset: [0, -30, -40, -50, 0] # 关节零位偏移,单位度
arm_move_speed: 50 # 运动速度百分比 1-100,值越小越慢越平稳
arm_reach_mse_threshold_deg2: 1.0 # 到位判定阈值(关节角均方误差,单位平方度)
get_arm_angles_retry_times: 3 # 读取舵机角度的重试次数camera_ip: "" # 留空使用本地 Orbbec 相机;填写 IP 则使用远程相机
camera_port: 8084 # 远程相机端口
cv2_headless_port: 8079 # Web 显示端口(无显示器环境),留空则使用 OpenCV 窗口default_desktop_height: 0.135 # 机械臂坐标系下桌面 Z 坐标,单位米classification_YOLO_model_path:
- /home/czn/roboarm/object_detect/runs/best.pt # YOLO OBB 模型路径
default_conf_thres: 0.5 # 检测置信度阈值
default_gripper_close_threshold: 0.05 # 夹爪闭合阈值
# 各类别抓取与放置配置
class_pos:
default: # 默认配置(匹配不到的类别使用此项)
pos: [0.05, 0.45] # 放置位置 (x, y),单位米
random_pos: # auto-reset 随机撒回范围 [[x_min,x_max],[y_min,y_max]]
- [0.0, 0.5]
- [-0.2, 0.3]
potato:
pos: [0.05, 0.4]
random_pos:
- [0.0, 0.16]
- [-0.2, 0.3]
tomato:
pos: [0.05, 0.4]
random_pos:
- [0.17, 0.33]
- [-0.2, 0.3]
carrot:
pos: [0.05, 0.4]
random_pos:
- [0.34, 0.5]
- [-0.2, 0.3]以下参数(含上方
class_pos.*.random_pos)仅供全自动复位脚本record_and_auto_reset.py使用。该全自动方案实测行不通,实际采集用半自动方案(人工摆放物体),这些参数可忽略。
workspace_x_range: [-0.1, 0.55] # 工作空间 X 范围,超出会拒绝
workspace_y_range: [-0.3, 0.55] # 工作空间 Y 范围
reset_min_place_dist_m: 0.20 # 随机放置时离已有物体的最小距离
reset_max_objects_per_cycle: 1 # 每轮最多撒回物体数catch_raise_height: 0.1 # 抓取前抬起高度,单位米
place_raise_height: 0.1 # 放置前抬起高度,单位米
catch_time_interval_s: 0.5 # 抓取动作间停顿,单位秒
catch_offset: 0.00 # 夹爪前向偏移,单位米
go_down_before_open_gripper_in_place: true # 放置时先下降再松爪项目使用 YOLO OBB(Oriented Bounding Box)模型进行目标检测。
- 训练脚本:
object_detect/train.py - 标注数据工具:
object_detect/dataset_process/(含拍照脚本和 LabelMe JSON 转 YOLO 标签工具) - 预训练模型放在
object_detect/runs/目录下
如果需要训练自己的模型:
- 用
object_detect/dataset_process/get_pic.py拍摄目标物体图片 - 用 LabelMe 标注(OBB 旋转框)
- 用
object_detect/dataset_process/json2label.py转换为 YOLO 格式 - 运行
object_detect/train.py训练
⚠️ 这一步不可跳过。YOLO 自动抓取和数据采集都依赖 2D 手眼标定:程序用它把相机像素坐标(u, v)转换成机械臂平面坐标(x, y)(见 arm/arm_base.py:469 的pixel2pos)。标定矩阵保存在arm/hand-eye-data/2d_homography.npy,而该目录已被.gitignore忽略——克隆后是空的,必须自己标定生成,否则抓取/采集运行时会报没有手眼标定数据,无法转换图像坐标。
标定脚本为 arm/calibrate_handeye_2d.py,支持两种模式:
# 方式 A:手动采点(在相机画面上点击 → 拖动机械臂末端到该点 → 记录,重复 4+ 个点)
uv run python arm/calibrate_handeye_2d.py --mode calibrate
# 方式 B:测试已有标定(复用 2d_homography.npy,点击画面验证映射是否准确)
uv run python arm/calibrate_handeye_2d.py --mode test标定完成后会在 arm/hand-eye-data/ 生成 2d_homography.npy(以及 2d_image_points.npy、2d_end_poses.npy 等中间数据)。calibrate 模式采点结束后会打印重投影误差,误差越小标定越准,建议内点平均误差在毫米级。
标定要点:
- 相机与机械臂的相对位置一旦改变,必须重新标定
- 标定时机械臂末端和点击点应尽量落在同一桌面高度平面(
default_desktop_height) - 采点尽量覆盖整个工作空间,避免全部集中在一小块区域
classification/catch_with_arm.py 使用 Orbbec 俯视相机和 YOLO OBB 模型持续检测物体,将目标的图像坐标通过 2D 手眼标定转换为机械臂坐标,然后自动抓取并放到 config.yaml 中配置的位置。
- 完成 环境搭建,确保 Piper、Orbbec 相机和 YOLO 依赖可用。
- 完成 2D 手眼标定。
- 在
config.yaml中至少确认以下配置:classification_YOLO_model_path:YOLO OBB 权重路径,可配置多个模型。default_conf_thres:检测置信度阈值。place_pos.<类别>.pos:各类别的放置坐标;未配置时会放回目标原位置。place_distance_threshold:目标距放置点小于该距离时跳过,防止重复抓放。catch_offset:抓取点沿夹爪方向的偏移量。
place_pos.<类别>.pos 的两个坐标既可使用米为单位的数值,也可使用 x、-x、y、-y 引用目标物体的坐标。例如:
place_pos:
carrot:
pos: [x, -y]默认只抓取 carrot 类别:
uv run python classification/catch_with_arm.py指定其他 YOLO 类别名:
uv run python classification/catch_with_arm.py --target tomato脚本会在指定类别的检测结果中选择置信度最高的物体。当前命令行参数默认值为 carrot;如果需要不限定类别、直接选择所有检测结果中置信度最高的物体,可在 Python 中调用 main(target_class=None)。
- 程序启动后,机械臂先回到 Home 位,随后持续检测、抓取和放置目标。
- 检测画面会显示 OBB 框、类别、置信度和 FPS。
- 按
Esc或在终端按Ctrl+C退出;退出时机械臂会回到 Home 位,并关闭机械臂连接和相机。 - 该脚本会真实驱动机械臂。首次运行时建议降低
arm_move_speed,确认放置坐标和工作空间安全,并随时准备急停。
classification/catch_with_arm_record_piper.py 是半自动 VLA 数据采集脚本:录制阶段全自动(YOLO 检测 → 机械臂抓取 → 放入收集箱 → 保存为 LeRobot 格式数据),每轮之间的复位由人工手动完成(重新摆放抓取物体的位置,摆好后按键进入下一轮)。
为什么是半自动:项目里还有一个全自动流水线
record_and_auto_reset.py,尝试让机械臂自己把物体重新摆到随机位置来创造新场景,但实测行不通(复位不稳定),因此实际采集改用本节的半自动方案——机械臂只负责录制阶段的抓取,物体摆放交给人工。
┌─ Episode N ──────────────────────────────────────┐
│ │
│ 1. Recording Phase (录制,全自动) │
│ ├─ 相机持续拍摄 │
│ ├─ YOLO 检测目标物体 │
│ ├─ 机械臂移动到目标位置抓取 │
│ ├─ 放入收集箱 │
│ └─ 自动保存 MP4 + 关节状态到 LeRobot 数据集 │
│ │
│ 2. Reset Phase (复位,人工) │
│ ├─ 画面提示 "RESET - Press Right arrow ..." │
│ ├─ 人工重新摆放抓取物体的位置 │
│ └─ 摆好后按右箭头 / n 进入下一轮 │
│ │
│ 3. 循环直到 NUM_EPISODES 完成 │
│ │
└───────────────────────────────────────────────────┘
编辑 classification/catch_with_arm_record_piper.py 顶部配置区:
DATASET_ROOT = "/home/czn/dataset/piper_yolopick" # 数据集保存路径
NUM_EPISODES = 1000 # 总共采集的 episode 数量
FPS = 30 # 采集帧率
EPISODE_TIME_S = 6000 # 单个 episode 最长录制时长(秒),一般靠按键提前结束
RESUME = True # True=断点续采, False=从头开始(目标目录已存在则报错)
TARGET_CLASS_LIST = ["carrot", "potato", "tomato"] # 目标类别列表,按 episode 逐轮轮换数据格式:采集的观测为 7 维状态
[joint_1..6 (deg), gripper_0to1 * 100],动作空间相同;图像包含俯视相机observation/image(Orbbec)与腕部相机observation/wrist_image(RealSense)。此格式与 VLA 推理客户端classification/main.py严格对齐。
uv run python classification/catch_with_arm_record_piper.py程序启动后会打印数据保存路径和键盘快捷键:
开始录制,数据保存到 /home/czn/dataset/piper_yolopick
操作方式:
n/右箭头 -> 结束当前 episode
r/左箭头 -> 丢弃当前 episode 并重录
q/Esc -> 停止录制
| 按键 | 功能 |
|---|---|
n / →(右箭头) |
结束当前 episode,保存数据;在复位阶段则表示"已摆好,进入下一轮" |
r / ←(左箭头) |
丢弃当前 episode(不保存),重新录制 |
q / Esc |
停止采集,保存当前数据后退出 |
复位交互:一个 episode 录完后,画面会显示
RESET - Press Right arrow when ready。此时人工把抓取物体重新摆到合适位置,摆好后按n/ 右箭头即开始下一轮录制。
- 数据以 LeRobot 格式 保存在
DATASET_ROOT目录 - 每个 episode 包含:MP4 视频(H.264)、关节状态序列(parquet)
- 视频编码在后台异步进行,不阻塞采集
RESUME = True时,重启程序会自动检测已有 episode 数量,从断点继续- 异常退出时,程序会尝试保存当前 episode 的已录制部分
RESUME = False时,如果目标目录已存在则报错
项目支持在无显示器(headless)的 Jetson 上运行。在 config.yaml 中设置:
cv2_headless_port: 8079启动程序后,在浏览器中访问 http://<jetson-ip>:8079/?window=Recording 即可实时查看相机画面和检测结果。
启动 Flask 服务器需要约 2-5 秒(Jetson 上较慢),程序会在启动时预热显示。
采集完数据并训练出 VLA 策略后,用 classification/main.py 在真机上做闭环推理。它连接 openpi 策略服务器,发送相机图像 + 机械臂状态,接收动作序列并下发到 Piper 机械臂。
推理客户端的观测/动作格式与训练录制器 catch_with_arm_record_piper.py 严格一致:
| 字段 | 含义 |
|---|---|
observation/state |
[joint_1..6 (deg), gripper_0to1 * 100],7 维 |
action |
同 state 的 7 维空间(绝对关节角度 + 夹爪 * 100) |
observation/image |
俯视相机(Orbbec),RGB HxWx3 |
observation/wrist_image |
腕部相机(Intel RealSense),RGB HxWx3 |
-
启动策略服务器(在训练机 / GPU 机器上,需先安装 openpi):
# openpi 项目内,加载训练好的 pi05_piper 权重 python scripts/serve_policy.py --port 8002 -
在 Piper 主机上安装 openpi 客户端依赖(装入 roboarm 的 venv):
uv pip install -e /home/czn/openpi/packages/openpi-client
-
先干跑(dry-run)验证——只打印动作、不驱动机械臂:
uv run python classification/main.py --host <策略服务器IP> --port 8002 --dry_run
-
真机推理:
uv run python classification/main.py \ --host <策略服务器IP> --port 8002 \ --prompt "pick the carrot toy and place into box"
| 参数 | 默认 | 说明 |
|---|---|---|
--host / --port |
10.0.105.11 / 8002 |
策略服务器地址 |
--prompt |
pick the carrot ... | 任务指令,需与训练时的措辞风格一致 |
--actions_per_chunk |
10 | 每次推理执行的动作步数,越小闭环越紧(网络往返更多) |
--control_dt |
0.033 | 相邻动作下发间隔(≈ 1/30s,与训练帧率对齐) |
--move_speed |
100 | 启动归零 / 控制模式速度百分比 |
--dry_run |
False | 只记录动作不驱动机械臂,首次运行务必先开启 |
安全提示:首次部署或更换权重后,务必先用
--dry_run确认动作合理,并适当降低--move_speed,再进行真机运动。推理时机械臂会以接近开环方式执行模型输出的密集轨迹,请确保工作空间内无人无障碍。
使用 VLM(视觉语言模型)同时分析相机画面和自然语言指令,定位目标物体,转换为机械臂坐标后完成抓取与分类放置。
| 路径 | 作用 |
|---|---|
llm/catch_by_llm.py |
主入口;读取相机画面和文字/语音指令,调用 VLM,并执行抓取、放置和成功率统计 |
llm/llm_detect.py |
图像编码、相机方向修正、VLM 检测请求及检测结果聚合 |
llm/llm_api.py |
OpenAI 兼容多模态接口客户端,读取模型地址、模型名和提示词配置 |
prompts.toml |
user_instruction_prompt 等 VLM 提示词模板 |
arm/arm_base.py |
像素坐标转换、夹爪角度计算以及 catch_and_place() 抓放动作 |
llm/fine_tuing/ |
VLM 数据标注、微调、LoRA 合并、部署和评测脚本;详见该目录下的 README.md |
调用链如下:
llm/catch_by_llm.py
→ llm/llm_detect.py
→ llm/llm_api.py + prompts.toml
→ VLM 返回目标边界框
→ arm/arm_base.py: pixel2pos() / catch_and_place()
- 完成上文的 2D 手眼标定,确保存在
arm/hand-eye-data/2d_homography.npy。 - 根据
config.yaml.example配置以下字段:
# OpenAI 兼容的 VLM 服务
llm_base_url: http://<VLM服务IP>:<端口>/v1
llm_api_key: any # 本地服务也不能留空
llm_model: output/merged-qwen3.5-9b-graspdet
prompts_file: prompts.toml
# 相机画面是否需要旋转 180°;主要影响左右、远近等空间判断
RotationCam2Arm: true
# 各类别的放置位置及匹配关键词
place_pos:
red:
pos: [0.1, 0.2]
keywords: ["red", "红色"]
# 抓取点沿夹爪方向的补偿距离,单位米
catch_offset: 0.00llm_base_url 必须提供 OpenAI 兼容的 /chat/completions 多模态接口。使用仓库内微调模型时,可参考 llm/fine_tuing/README.md 和 llm/fine_tuing/serve_vllm.sh 启动本地服务。
当前脚本默认执行 catch_by_text_instruction(),指令列表定义在 llm/catch_by_llm.py 的 instructions 变量中。按需修改指令后运行:
uv run python llm/catch_by_llm.py程序会依次执行:
- Orbbec 相机采集彩色画面。
- 将画面与自然语言指令发送给 VLM。
- VLM 返回目标类别、中心坐标、宽高和旋转信息。
- 使用 2D 手眼标定将目标像素坐标转换为机械臂平面坐标。
- 根据
place_pos的关键词匹配放置区域,执行抓取与放置。 - 将本次结果写入
llm/catch_stats.json;按Esc退出并使机械臂回到 Home 位。
如需使用麦克风语音指令,将脚本末尾的 catch_by_text_instruction() 改为 catch_by_audio(),并配置 audio2text_backend 及对应语音识别服务参数。
YOLO 识别棋子 → 机械臂自动走棋:
uv run python chess/catch_and_place.pyLeader-Follower 遥操作:
uv run python leader_follower/leader_follower.py以最下面的舵机为原点,红色为 X 轴,绿色为 Y 轴,蓝色为 Z 轴。
相机像素坐标到机械臂基座坐标系的映射由 2D 手眼标定得到,是 YOLO 抓取/采集的前置步骤。完整流程和三种标定模式见上文 手眼标定(抓取/采集前必须)。
现象:lsusb 能看到 USB-CAN 设备,但 ip link show | grep can 看不到 CAN 接口;SDK 报 SEND_MESSAGE_FAILED (100017)。
原因:内核未编译 gs_usb 模块(Jetson 默认内核常见)。
解决(Jetson):
# 检查
zcat /proc/config.gz | grep GS_USB
# 如果输出 "# CONFIG_CAN_GS_USB is not set",则需要编译
# 1. 安装编译依赖
sudo apt install -y build-essential bc kmod flex bison libncurses-dev libssl-dev dwarves wget git
# 2. 获取与当前 uname -r 完全匹配的 Jetson 内核源码
# 3. 在源码目录执行:
zcat /proc/config.gz > .config
sed -i 's/CONFIG_LOCALVERSION=""/CONFIG_LOCALVERSION="-tegra"/' .config
make olddefconfig
export LOCALVERSION=-tegra
export IGNORE_PREEMPT_RT_PRESENCE=1
make -j$(nproc) modules
# 4. 安装
sudo mkdir -p /lib/modules/$(uname -r)/kernel/drivers/net/can/usb
sudo cp drivers/net/can/usb/gs_usb.ko /lib/modules/$(uname -r)/kernel/drivers/net/can/usb/
sudo depmod -a
sudo modprobe gs_usb
# 5. 设置开机自动加载
echo "gs_usb" | sudo tee /etc/modules-load.d/gs_usb.conf恢复流程:
CAN_IF="can_piper" # 改成你的实际接口名
# 1. 关闭接口
sudo ip link set "$CAN_IF" down 2>/dev/null || true
# 2. 卸载驱动
sudo modprobe -r gs_usb
# 3. 物理拔掉 USB-CAN,等待 3 秒后重新插入
# 4. 机械臂断电再上电
# 5. 重新加载驱动
sudo modprobe gs_usb
# 6. 重新确认接口名并激活
ls /sys/class/net | grep can
sudo ip link set "$CAN_IF" type can bitrate 1000000
sudo ip link set "$CAN_IF" txqueuelen 1000
sudo ip link set "$CAN_IF" up- 确认机械臂处于 slave 模式(非 master 模式),否则需重启臂体
judge_flag=False适用于第三方 USB-CAN 适配器- 先跑
arm/calibrate_offset.py验证连通性,再跑运动脚本
- 确认 Orbbec 相机 USB 已连接
- 确认
camera_ip为空(使用本地相机)或填写了正确的远程相机 IP - 检查 udev 规则是否已安装:
camera/scripts/中有 Orbbec 设备的 udev 配置文件
原因:arm/hand-eye-data/2d_homography.npy 不存在。该目录被 .gitignore 忽略,克隆后需自行标定生成。
解决:先执行 手眼标定(抓取/采集前必须),生成标定矩阵后再运行抓取/采集脚本。
roboarm/
arm/ # 机械臂控制(Piper + Lerobo),手眼标定
camera/ # Orbbec 深度相机 + USB 相机控制
classification/ # YOLO 自动抓取 + 数据采集流水线 + VLA 推理客户端(main.py)
object_detect/ # YOLO OBB 模型训练与检测
llm/ # LLM 视觉识别与指令理解
chess/ # 中国象棋场景
leader_follower/ # 主从臂遥操作
sim/ # Isaac Sim / MuJoCo 仿真
utils/ # 配置读取,无头显示
urdf/ # 机械臂 URDF 模型
config.yaml # 运行时配置
prompts.toml # LLM 提示词模板
