19fd502c65a73.png

> 基于 SeeedStudio 开源机械臂自主抓取项目 rebot_grasp 改编。本文记录一次使用 Orbbec Gemini 305 RGB-D 相机 复现机械臂纯视觉抓取物体的完整流程,包括环境准备、手眼标定、视觉检测、抓取执行、夹爪控制改进和常见避坑点。🔗GitHub

1. 项目背景

本次复现基于 SeeedStudio 开源机械臂自主抓取物体项目 rebot_grasp 进行改编。原项目主要围绕 reBot Arm B601 机械臂、RGB-D 相机、YOLO 目标检测、手眼标定和自主抓取流程展开。

本次复现中的主要差异是:

  • 原项目相机配置逻辑经过适配;

– 实际使用的是 Orbbec Gemini 305 相机;

  • 对 Orbbec 相机的 D2C 对齐失败问题做了自动降级;
  • 对机械臂 SDK 的 src/ 布局导入路径做了兼容;
  • 增加了夹爪初始化闭合、按 P 键释放物体并进入下一轮识别的交互逻辑。

最终目标是实现:

RGB-D 相机观察物体
→ YOLO/YOLOE 识别目标
→ 深度图恢复目标 3D 坐标
→ 手眼标定转换到机械臂基坐标系
→ 机械臂移动并夹取
→ 回到静止位
→ 按 P 释放物体
→ 继续下一轮识别与抓取

2. 系统整体流程图

19fd509590675.png

3. 硬件与软件准备

3.1 硬件

建议准备:

  • reBot Arm B601 机械臂;
  • Orbbec Gemini 305 RGB-D 相机;
  • USB 数据线,尽量使用稳定供电的数据口;
  • ArUco 标定板;
  • 演示物体,例如:
  • 香蕉;
  • 水瓶;
  • 纸杯;
  • 颜色鲜明的小盒子;
  • 轻质泡沫块。

初次复现不建议使用透明杯、金属反光物体、过黑物体或太小的物体,因为深度相机和夹爪都会比较容易失效。

3.2 软件环境

建议使用 Linux 环境,例如 Ubuntu / Jetson 系统,并准备 Python 虚拟环境。

基础依赖包括:

pip install numpy opencv-python pyyaml ultralytics
pip install pyorbbecsdk2

如果项目依赖机械臂 SDK,还需要确保:

pip install -e sdk/reBotArm_control_py

如果运行时报:

ModuleNotFoundError: No module named 'motorbridge'

说明机械臂 SDK 依赖没有装完整,需要回到 SDK 文档或项目环境文件中补齐依赖。

4. 配置文件说明

核心配置文件是:

config/default.yaml

4.1 相机配置

本次实际使用 Orbbec Gemini 305,但代码中仍复用了 Orbbec Gemini 系列驱动入口,例如:

camera:
  type: orbbec_gemini2
  serial: null
  color_width: 1280
  color_height: 720
  fps: 30

这里的 orbbec_gemini2 更像是项目内部相机驱动名称,并不严格等于实际硬件型号。只要 Orbbec SDK 能正常打开 Gemini 305,就可以复用这一套驱动。

4.2 YOLO 模型配置

示例:

yolo:
  model_name: "yoloe-26s-seg.pt"
  device: "cpu"
  use_world: true
  custom_classes:
    - "yellow banana"
    - "water bottle"
    - "light blue coffee cup"
    - "cup"
    - "green object"
    - "tool"

注意:如果使用 YOLOE 的开放词汇能力,即:

use_world: true

程序会调用:

model.set_classes(custom_classes)

这可能触发额外下载:

mobileclip2_b.ts

如果设备无法访问 GitHub,建议提前准备该文件,或者先关闭开放词汇:

use_world: false

4.3 夹爪配置

示例:

robot:
  gripper:
    dm:
      angle_open: 5.0
      counterclockwise: true
      tau_max: 1.5
      close_torque: 1.0
      default_force: 0.50
    rs:
      angle_open: 5.0
      counterclockwise: false
      tau_max: 1.5
      close_torque: 1.5
      default_force: 0.50

参数含义:

参数 作用
angle_open 最大张开角度
close_torque 真正抓取物体时的闭合力矩
default_force 启动闭合休息位、默认保持力
tau_max 夹爪力矩上限

如果夹爪启动后闭合不彻底,优先调大:

default_force

如果抓取时夹不紧,再调:

close_torque

5. 手眼标定

19fd50c10119e.png

5.1 标定目标

手眼标定用于求解:

camera → gripper

也就是相机坐标系和机械臂末端坐标系之间的固定变换关系。

标定完成后,程序会保存:

config/calibration/orbbec_gemini2/hand_eye.npz

5.2 运行标定脚本

python scripts/collect_handeye_eih.py

成功后会看到类似输出:

[Result] Solving with 18 samples...
[Result] T_cam2gripper translation: x=-0.0834 y=0.0083 z=0.0518 m
[Result] [OK] Saved to /home/r/ws/rebot_grasp/config/calibration/orbbec_gemini2/hand_eye.npz

建议采集样本数不少于 15 组。本次复现采集了 18 组样本,结果较为稳定。

6. Orbbec 相机 D2C 问题与改进

6.1 遇到的问题

Orbbec 相机启动时可能报错:

Current stream profile is not support hardware d2c process

或者:

Hardware D2C unavailable

原因是当前分辨率 / 帧率 / 相机型号组合不支持硬件 D2C。

6.2 代码改进:自动降级 D2C

在 drivers/camera/orbbec_gemini2.py 中增加启动策略:

self._align_mode = "auto"
self._require_depth = True

核心逻辑:

def align_candidates():
    if self._align_mode == "auto":
        requested = ["hw", "sw", "none"] if self._require_depth else ["hw", "none"]
    else:
        requested = [self._align_mode]

    candidates = []
    for mode in requested:
        if mode == "hw" and hasattr(OBAlignMode, "HW_MODE"):
            candidates.append(("hardware D2C", getattr(OBAlignMode, "HW_MODE")))
        elif mode == "sw" and hasattr(OBAlignMode, "SW_MODE"):
            candidates.append(("software D2C", getattr(OBAlignMode, "SW_MODE")))
        elif mode == "none":
            candidates.append(("D2C disabled", None))
    return candidates or [("D2C disabled", None)]

这样程序会按顺序尝试:

硬件 D2C → 软件 D2C → 禁用 D2C

如果硬件 D2C 不可用,程序不会直接退出,而是自动降级。

7. YOLO 感知验证

7.1 无窗口模式验证

由于某些 Linux / Jetson 环境下 OpenCV 的 cv2.namedWindow() 可能卡死,可以先用无窗口模式验证视觉链路:

python scripts/ordinary_grasp_pipeline.py --no-window --max-frames 30

成功时会输出:

[G] Best grasp:
  class=yellow banana conf=0.663
  center_px=(718, 390) angle_deg=86.11
  jaw_width_m=0.0224 object_length_m=0.0763
  position_xyz=[0.0164, 0.0058, 0.1300]

并保存预览图:

runs/ordinary_grasp_preview.jpg

7.2 判断感知结果是否合理

重点看:

position_xyz=[x, y, z]

这里是相机坐标系下的目标位置。

建议物体距离相机:

z = 0.25 ~ 0.45 m

太近时,例如:

z = 0.13 m

深度容易不稳定,也可能导致机械臂抓取点过低或不可达。

8. 机械臂 dry-run 验证

在真正抓取前,先运行:

python scripts/main.py --dry-run

按 G 后程序会打印:

[Grasp] pregrasp  xyz=(+0.482,-0.008,+0.084)
[Grasp] grasp     xyz=(+0.529,-0.012,+0.019)
[Grasp] dry run; skip motion

这里的坐标已经是机械臂基坐标系下的目标位姿。

第一次实抓前建议确认:

x = 0.25 ~ 0.45 m
y = -0.15 ~ 0.15 m
z > 0.05 m

如果 z 很低,例如:

z = 0.019 m

不建议直接真抓,容易碰桌面。

9. 手眼标定补偿

如果机械臂抓取点整体偏高、偏低、偏左或偏右,可以通过配置补偿:

calibration:
  hand_eye_compensation_m:
    x: 0.00
    y: 0.00
    z: 0.02

相关代码在 utils/camera_utils.py:

def hand_eye_compensation_matrix(cfg: dict[str, Any]) -> np.ndarray:
    calibration = cfg.get("calibration") or {}
    compensation = calibration.get("hand_eye_compensation_m") or {}
    T = np.eye(4, dtype=np.float64)
    T[:3, 3] = [
        float(compensation.get("x", 0.0)),
        float(compensation.get("y", 0.0)),
        float(compensation.get("z", 0.0)),
    ]
    return T


def compose_cam_to_base_transform(
    T_tcp2base: np.ndarray,
    T_hand_eye: np.ndarray,
    cfg: dict[str, Any],
) -> np.ndarray:
    T_compensation = hand_eye_compensation_matrix(cfg)
    return T_compensation @ np.asarray(T_tcp2base) @ np.asarray(T_hand_eye)

含义是:

相机到机械臂基坐标变换
= 手动补偿 × 当前 TCP 到基坐标变换 × 手眼标定结果

第一次真抓建议让 z 保守一点,例如:

z: 0.02

宁可抓空气,也不要一上来碰桌面。

10. 关键代码实现说明

10.1 启动后夹爪自动闭合到休息位

原逻辑中,程序启动后夹爪会保持当前状态。如果启动前夹爪是张开的,初始化后仍然张开。

因此增加了专门的闭合休息位函数:

def close_gripper_rest(self, force: Optional[float] = None, timeout: float = 3.0) -> None:
    self._ensure_running()
    hold_torque = self._close_sign * float(
        np.clip(abs(force if force is not None else self._default_force), 0.05, self._tau_max)
    )
    with self._state_lock:
        self._target_pos = 0.0
        self._hold_torque = hold_torque
        self._state = self._STATE_HOLDING
        self._position_reached = False
        self._grasp_result = None

这个函数不会像 release_gripper() 那样先打开再闭合,而是直接闭合到休息位,并使用 default_force 保持。

在 scripts/main.py 初始化后调用:

print("[Robot] Close gripper to rest")
grasp_driver.close_gripper_rest()

print("[Robot] Move ready")
_move_ready(controller, ready_cfg)

10.2 按 P 键释放物体并进入下一轮识别

新增交互逻辑:

if key in (ord("p"), ord("P")):
    print("n[P] Release object and resume")
    if args.dry_run:
        print("[P] dry run; skip gripper motion")
    elif grasp_driver is None:
        print("[P] Robot is not ready")
    else:
        grasp_driver.release_gripper()
        object_held = False
        print("[P] Gripper opened to max, then closed to rest")

    frozen = False
    last_display = None
    continue

release_gripper() 的动作是:

def release_gripper(self, timeout: float = 4.0) -> None:
    self._ensure_running()
    self.open_gripper(timeout=min(2.0, timeout))
    self._set_position_target(0.0)
    self._wait_until(self._position_done, timeout)

也就是:

张开到最大量程 → 回到最小量程 / 闭合休息位

这样机械臂抓完物体回到 ready 位后,不会自动释放。操作者可以手动按 P 释放,然后系统恢复 live 识别,等待下一次 G。

10.3 抓取动作流程

简化后的抓取执行逻辑:

print("[Grasp] Open gripper")
grasp_driver.open_gripper()

print("[Grasp] Move to pregrasp")
controller.move_to_traj(xp, yp, zp, rxp, ryp, rzp, duration=2.0)

print("[Grasp] Move to grasp")
controller.move_to_traj(xg, yg, zg, rxg, ryg, rzg, duration=1.5)

print("[Grasp] Closing")
ok = grasp_driver.grasp()

print("[Grasp] Return ready")
_move_ready(controller, ready_cfg)

动作顺序是:

打开夹爪
→ 移动到预抓取位
→ 下降到抓取位
→ 闭合夹爪
→ 携带物体回到 ready 位
→ 等待 P 释放

11. 完整运行步骤

11.1 手眼标定

python scripts/collect_handeye_eih.py

确认生成:

ls -lh config/calibration/orbbec_gemini2/hand_eye.npz

11.2 验证视觉感知

如果 GUI 正常:

python scripts/ordinary_grasp_pipeline.py

如果 OpenCV 窗口卡死:

python scripts/ordinary_grasp_pipeline.py --no-window --max-frames 30

查看输出图:

ls -lh runs/ordinary_grasp_preview.jpg

11.3 机械臂 dry-run

python scripts/main.py --dry-run

按 G,观察打印出来的机械臂目标位姿是否合理。

11.4 真正执行抓取

python scripts/main.py

按键说明:

按键 功能
G 识别当前帧并执行抓取
P 释放当前夹取物体,然后恢复识别
R 恢复 live 预览
Q / Esc 退出,释放夹爪并回 home

推荐流程:

放置物体
→ 按 G 抓取
→ 机械臂回 ready 位并保持夹持
→ 移动接物区域或准备下一步
→ 按 P 释放物体
→ 放置新物体
→ 再按 G

12. 常见避坑点

12.1 YOLO 权重明明在本地,却还去 GitHub 下载

如果使用 YOLOE 开放词汇:

use_world: true

程序会额外需要:

mobileclip2_b.ts

这不是主模型 yoloe-26s-seg.pt,而是文本编码器相关资源。

解决方法:

  • 提前下载;
  • 或关闭开放词汇:
use_world: false

12.2 Orbbec 相机被占用

报错:

uvc_open failed

检查占用进程:

ps aux | grep -E "ordinary_grasp|object_detection|grasp.py|collect_handeye|python"

结束旧进程:

kill <PID>

如果不退出:

kill -9 <PID>

12.3 OpenCV 窗口卡死

如果卡在:

[GUI] namedWindow...

说明 OpenCV GUI 后端可能有问题。

可使用无窗口模式:

python scripts/ordinary_grasp_pipeline.py --no-window --max-frames 30

或者尝试:

QT_QPA_PLATFORM=xcb python scripts/main.py

12.4 抓取点太低

如果 dry-run 输出:

z = 0.019

不建议真抓。可以在配置中提高补偿:

calibration:
  hand_eye_compensation_m:
    z: 0.02

12.5 物体太小,夹爪夹不到

如果演示道具比夹爪最小闭合间距还小,软件无法突破硬件限制。

建议:

  • 换更大的道具;
  • 给道具包泡棉或胶带;
  • 给夹爪内侧贴软垫;
  • 更换 V 型或窄指尖夹爪。

13. 推荐演示物体

优先选择:

  • 黄色香蕉;
  • 纸杯;
  • 矿泉水瓶;
  • 彩色泡沫块;
  • 轻质塑料盒。

不建议初次使用:

  • 透明玻璃杯;
  • 金属反光物体;
  • 黑色吸光物体;
  • 太小或太薄的物体;
  • 表面过于光滑的物体。

14. 总结

本次复现的关键工作并不只是“跑通一个抓取脚本”,而是把视觉、深度、标定、机械臂控制和夹爪状态管理串成了一个可重复执行的闭环。

最终系统具备以下能力:

  • 使用 Orbbec Gemini 305 获取 RGB-D 信息;
  • 使用 YOLOE 识别开放词汇物体;
  • 使用深度图估计目标 3D 抓取点;
  • 使用手眼标定将相机坐标转换到机械臂基坐标;
  • 执行预抓取、抓取、回 ready 位;
  • 按 P 手动释放物体,并进入下一轮识别抓取。

这个流程跑通后,后续可以继续优化:

  • 更稳定的目标选择策略;
  • 更安全的桌面高度约束;
  • 更好的夹爪指尖结构;
  • GraspNet 6D 抓取;
  • 无窗口自动抓取模式;
  • 多物体连续抓取任务。

机械臂抓取这件事看起来像“看见 → 伸手 → 夹住”这么简单,但背后其实是相机、标定、深度、运动规划和夹爪控制一起排练的一场小型交响乐。只要每个模块都调到靠谱,它就会很自然地动起来。

发表回复

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