具身智能:零基础入门睿尔曼机械臂(七)—— 衔接开源代码!机械臂手眼标定实操

理论衔接实操:如何把开源求解器装进现有 Realman 控制栈,拆解标定数据采集、姿态覆盖、求解初值选择与失败回滚,以及如何把手眼矩阵写回运行时 TF 链路。


前言

在上一篇博客中,我们详细拆解了睿尔曼开源手眼标定代码库的核心代码逻辑。今天,我们将聚焦实操层面,从环境搭建、设备准备,到具体的标定流程(眼在手上/眼在手外)、常见问题解决,再到标定结果的实际应用,带大家完整走通机械臂手眼标定的全流程。

注:文章部分图以及代码来自睿尔曼科技官方博客 链接如下:
https://develop.realman-robotics.com/AI/developerGuide/hand/

一、标定前期准备

在开始标定前,我们需要完成环境配置和设备准备两大核心工作,这一步直接影响后续标定的顺利程度和结果精度。

1.1 环境要求与配置

手眼标定依赖特定的操作系统和Python环境,建议严格按照以下版本要求配置,避免因版本兼容问题踩坑。

1.1.1 基础环境准备

项目版本要求
操作系统Ubuntu / Windows
Python3.9 及以上

1.1.2 Python依赖包安装

标定程序需要依赖多个Python科学计算和计算机视觉库,具体版本要求如下:

包名版本要求
numpy2.0.2
opencv-python4.10.0.84
pyrealsense22.55.1.6486
scipy1.13.1
最便捷的安装方式是使用代码库中的requirements.txt文件,在Python环境中执行以下命令即可一键安装所有依赖:

pip install -r requirements.txt

1.2 设备准备

确保以下设备齐全且能正常工作,设备的稳定性直接影响标定精度:

  • 机械臂:支持睿尔曼RM75、RM65、RM63、GEN72型号
  • 相机:Intel RealSense Depth Camera D435(含专用数据线)
  • 网络设备:网线(用于连接电脑与机械臂)
  • 标定板:1号或2号标定板(可打印纸质版,或在淘宝搜索“标定板棋盘格”购买成品)

在这里插入图片描述

二、详细标定流程

手眼标定主要分为“眼在手上”和“眼在手外”两种场景,核心区别在于相机的安装位置:眼在手上是相机固定在机械臂末端,随机械臂运动;眼在手外是相机固定不动,标定板随机械臂运动。两种场景的标定流程略有差异,我们分别详细讲解。

2.1 通用步骤:参数配置

无论哪种场景,在采集数据前都需要先配置标定板参数,步骤如下:

  1. 找到代码库中的配置文件config.yaml
  2. 根据实际使用的标定板,修改以下三个核心参数:

在这里插入图片描述

- xx:标定板横向角点数(长边格子数减1),默认11(例:长边12个格子,角点数为11);

- YY:标定板纵向角点数(短边格子数减1),默认8(例:短边9个格子,角点数为8);

- L:标定板单个方格的实际尺寸(单位:米),默认0.03米。

2.2 场景一:眼在手上(相机随机械臂运动)

核心逻辑:标定板固定,相机随机械臂末端运动,采集不同姿态下的标定板图像,计算相机相对于机械臂末端的位姿。

在这里插入图片描述

2.2.1 采集数据

  1. 连接设备:用相机专用数据线连接电脑与D435相机,用网线连接电脑与机械臂;
  2. 配置IP:将电脑IP与机械臂设置为同一网段(机械臂IP为192.168.1.18时,电脑设为1网段;IP为192.168.10.18时,电脑设为10网段);

在这里插入图片描述

  1. 放置标定板:将标定板固定在机械臂工作区内的平面上,确保相机(机械臂末端)能从不同视角观测到,标定期间不得移动标定板;
  2. 运行采集脚本:执行collect_data.py,会弹出相机视野弹窗;
  3. 调整姿态与采集:拖动机械臂末端,使标定板在弹窗中清晰、完整显示,且标定板与相机镜面呈一定角度(避免正对,正对为错误姿势);将光标放在弹窗上,按键盘s键采集数据;

在这里插入图片描述
在这里插入图片描述

  1. 重复采集:移动机械臂15-20次,每次移动时旋转角度尽量大于30°,确保在X、Y、Z三个轴上都有足够的旋转变化(建议先绕末端Z轴旋转,再绕X轴旋转),共采集15-20张不同姿态的图像。

在这里插入图片描述

2.2.2 计算标定结果

采集完成后,运行以下脚本即可得到标定结果:


python compute_in_hand.py

运行成功后,会输出相机坐标系相对于机械臂末端坐标系的旋转矩阵平移向量(平移向量单位:米)。

2.3 场景二:眼在手外(相机固定不动)

核心逻辑:相机固定,标定板固定在机械臂末端随机械臂运动,采集不同姿态下的标定板图像,计算相机相对于机械臂基坐标系的位姿。

在这里插入图片描述

2.3.1 采集数据

  1. 连接设备:同眼在手上场景,用数据线连接相机与电脑,网线连接机械臂与电脑;
  2. 配置IP:同眼在手上场景,确保电脑与机械臂同一网段;
    在这里插入图片描述
  3. 安装标定板与固定相机:将标定板(打印纸质较小的板子,方便固定)固定在机械臂末端,相机固定不动,移动机械臂末端,使标定板出现在相机视野里。
    标定过程中,标定板安装在机械臂末端执行器上并随机械臂移动。可以直接固定在工具法兰上或由夹具固定安装,安装的确切位置不重要,因为不必知道标定板和末端执行器的相对位姿。重要的是标定板在运动过程中不会出现相对于工具法兰或夹具的位移,它必须被良好地固定住或被夹具紧紧地抓住。建议使用由刚性材料制成的安装支架。
  4. 运行采集脚本:执行collect_data.py,弹出相机视野弹窗;
  5. 调整姿态与采集:拖动机械臂末端,使标定板在弹窗中清晰、完整显示,且与相机镜面呈一定角度;按键盘s键采集数据;
  6. 重复采集:同眼在手上场景,移动机械臂15-20次,每次旋转角度大于30°,覆盖X、Y、Z三轴旋转变化,采集15-20张图像。

2.3.2 计算标定结果

采集完成后,运行以下脚本得到标定结果:


python compute_to_hand.py

运行成功后,会输出相机坐标系相对于机械臂基坐标系的旋转矩阵平移向量(平移向量单位:米)。

2.4 误差范围说明

标定结果的精度受采集图像质量影响,正常情况下,平移向量与实际值的差距应控制在1cm之内。若误差过大,需检查图像采集质量(如标定板是否清晰、姿态变化是否充足),重新采集数据进行标定。

三、标定过程常见问题与解决方案

在执行标定计算脚本时,最常见的问题是“运动信息不足”导致标定失败,我们针对性解决:

问题1:标定失败,报错“Not enough informative motions”

问题描述

执行compute_in_hand.pycompute_to_hand.py时,报错:


[ERROR:0@1.418] global calibration_handeye.cpp:335 calibrateHandEyeTsai Hand-eye calibration failed! Not enough informative motions--include larger rotations.

问题原因

采集数据时,机械臂的旋转运动不足,尤其是缺少足够大的旋转角度变化,导致标定算法无法准确计算手眼关系。

解决方案

  • 增加旋转运动:确保机械臂在X、Y、Z三个轴上的旋转角度均超过30°,提供丰富的运动信息;
  • 多样化姿态:避免机械臂只在小范围平移或只绕单一轴旋转,增加姿态多样性;
  • 增加采集次数:确保采集的图像数量不少于15张,更多样本能提升标定稳定性和准确性。

四、标定结果的实际应用:机械臂抓取

手眼标定的核心目的是实现“视觉引导抓取”——通过相机识别物体位姿,结合标定结果将物体位姿转换为机械臂可识别的基坐标系位姿,进而控制机械臂完成抓取。以下分两种场景讲解具体应用(含代码)。

4.1 场景一:眼在手上的抓取应用

核心逻辑

物体位姿转换流程:
物体在相机坐标系位姿 →(标定结果)→ 物体在机械臂末端坐标系位姿 →(机械臂API)→ 物体在机械臂基坐标系位姿

机械臂通过基坐标系下的物体位姿,计算末端执行器的运动轨迹,完成抓取。

在这里插入图片描述

应用代码

以下代码分别实现“物体为3D点”和“物体为完整位姿”两种情况下的坐标转换(需将代码中的旋转矩阵和平移向量替换为你的标定结果)。

情况1:物体在相机坐标系为3D点(x,y,z)

import numpy as np
from scipy.spatial.transform import Rotation as R

# 相机坐标系到机械臂末端坐标系的旋转矩阵和平移向量(手眼标定得到)
rotation_matrix = np.array([[-0.00235395 , 0.99988123 ,-0.01523124],
                            [-0.99998543, -0.00227965, 0.0048937],
                            [0.00485839, 0.01524254, 0.99987202]])
translation_vector = np.array([-0.09321419, 0.03625434, 0.02420657])

def convert(x, y, z, x1, y1, z1, rx, ry, rz):
    """
    计算物体相对于机械臂基座的位姿(x, y, z)
    参数:
        x,y,z: 深度相机识别的物体坐标(相机坐标系)
        x1,y1,z1,rx,ry,rz: 机械臂末端位姿(基坐标系,弧度)
    返回:
        obj_base_coordinates: 物体在机械臂基坐标系的坐标
    """
    # 深度相机识别物体返回的坐标
    obj_camera_coordinates = np.array([x, y, z])

    # 机械臂末端的位姿转换为齐次变换矩阵
    position = np.array([x1, y1, z1])
    orientation = R.from_euler('xyz', [rx, ry, rz], degrees=False).as_matrix()
    T_base_to_end_effector = np.eye(4)
    T_base_to_end_effector[:3, :3] = orientation
    T_base_to_end_effector[:3, 3] = position

    # 相机到末端的齐次变换矩阵(标定结果)
    T_camera_to_end_effector = np.eye(4)
    T_camera_to_end_effector[:3, :3] = rotation_matrix
    T_camera_to_end_effector[:3, 3] = translation_vector

    # 齐次坐标转换:相机坐标系 → 末端坐标系 → 基坐标系
    obj_camera_coordinates_homo = np.append(obj_camera_coordinates, [1])
    obj_end_effector_coordinates_homo = T_camera_to_end_effector.dot(obj_camera_coordinates_homo)
    obj_base_coordinates_homo = T_base_to_end_effector.dot(obj_end_effector_coordinates_homo)

    # 提取非齐次坐标
    obj_base_coordinates = list(obj_base_coordinates_homo[:3])
    return obj_base_coordinates
情况2:物体在相机坐标系为完整位姿(x,y,z,rx,ry,rz)

import numpy as np
from scipy.spatial.transform import Rotation as R

# 相机坐标系到机械臂末端坐标系的旋转矩阵和平移向量(手眼标定得到)
rotation_matrix = np.array([[-0.00235395 , 0.99988123 ,-0.01523124],
                            [-0.99998543, -0.00227965, 0.0048937],
                            [0.00485839, 0.01524254, 0.99987202]])
translation_vector = np.array([-0.09321419, 0.03625434, 0.02420657])

def decompose_transform(matrix):
    """将齐次变换矩阵分解为平移向量和欧拉角(rx, ry, rz)"""
    translation = matrix[:3, 3]
    rotation = matrix[:3, :3]

    sy = np.sqrt(rotation[0, 0] ** 2 + rotation[1, 0] ** 2)
    singular = sy < 1e-6

    if not singular:
        rx = np.arctan2(rotation[2, 1], rotation[2, 2])
        ry = np.arctan2(-rotation[2, 0], sy)
        rz = np.arctan2(rotation[1, 0], rotation[0, 0])
    else:
        rx = np.arctan2(-rotation[1, 2], rotation[1, 1])
        ry = np.arctan2(-rotation[2, 0], sy)
        rz = 0
    return translation, rx, ry, rz

def convert(x,y,z,rx,ry,rz,x1,y1,z1,rx1,ry1,rz1):
    """
    计算物体相对于机械臂基座的完整位姿
    参数:
        x,y,z,rx,ry,rz: 机械臂末端位姿(基坐标系,弧度)
        x1,y1,z1,rx1,ry1,rz1: 深度相机识别的物体位姿(相机坐标系)
    返回:
        result: 物体在基坐标系的位姿(平移向量 + 欧拉角)
    """
    # 机械臂末端位姿转换为齐次变换矩阵
    end_position = np.array([x, y, z])
    end_orientation = R.from_euler('xyz', [rx, ry, rz], degrees=False).as_matrix()
    T_end_to_base = np.eye(4)
    T_end_to_base[:3, :3] = end_orientation
    T_end_to_base[:3, 3] = end_position

    # 相机到末端的齐次变换矩阵(标定结果)
    T_camera_to_end = np.eye(4)
    T_camera_to_end[:3, :3] = rotation_matrix
    T_camera_to_end[:3, 3] = translation_vector

    # 物体在相机坐标系的位姿转换为齐次变换矩阵
    obj_position = np.array([x1, y1, z1])
    obj_orientation = R.from_euler('xyz', [rx1, ry1, rz1], degrees=False).as_matrix()
    T_object_to_camera = np.eye(4)
    T_object_to_camera[:3, :3] = obj_orientation
    T_object_to_camera[:3, 3] = obj_position

    # 齐次坐标转换:物体 → 相机 → 末端 → 基坐标系
    obj_end_coords_homo = T_camera_to_end.dot(T_object_to_camera)
    obj_base_coords = T_end_to_base.dot(obj_end_coords_homo)

    # 分解得到最终位姿
    result = decompose_transform(obj_base_coords)
    return result

4.2 场景二:眼在手外的抓取应用

核心逻辑

物体位姿转换流程:
物体在相机坐标系位姿 →(标定结果)→ 物体在机械臂基坐标系位姿

因相机固定,标定结果直接给出相机与基坐标系的关系,无需经过末端坐标系转换,流程更简洁。

在这里插入图片描述

应用代码

以下代码同样分“3D点”和“完整位姿”两种情况,需替换为眼在手外的标定结果。

情况1:物体在相机坐标系为3D点(x,y,z)

import numpy as np

# 相机坐标系到机械臂基坐标系的旋转矩阵和平移向量(手眼标定得到)
rotation_matrix = np.array([[-0.00235395 , 0.99988123 ,-0.01523124],
                            [-0.99998543, -0.00227965, 0.0048937],
                            [0.00485839, 0.01524254, 0.99987202]])
translation_vector = np.array([-0.09321419, 0.03625434, 0.02420657])

def convert(x, y, z):
    """
    计算物体相对于机械臂基座的坐标
    参数:
        x,y,z: 深度相机识别的物体坐标(相机坐标系)
    返回:
        obj_base_coordinates: 物体在基坐标系的坐标
    """
    obj_camera_coordinates = np.array([x, y, z])

    # 相机到基坐标系的齐次变换矩阵(标定结果)
    T_camera_to_base = np.eye(4)
    T_camera_to_base[:3, :3] = rotation_matrix
    T_camera_to_base[:3, 3] = translation_vector

    # 齐次坐标转换
    obj_camera_coords_homo = np.append(obj_camera_coordinates, [1])
    obj_base_coords_homo = T_camera_to_base.dot(obj_camera_coords_homo)
    obj_base_coordinates = list(obj_base_coords_homo[:3])
    return obj_base_coordinates
情况2:物体在相机坐标系为完整位姿(x,y,z,rx,ry,rz)

import numpy as np
from scipy.spatial.transform import Rotation as R

# 相机坐标系到机械臂基坐标系的旋转矩阵和平移向量(手眼标定得到)
rotation_matrix = np.array([[-0.00235395 , 0.99988123 ,-0.01523124],
                            [-0.99998543, -0.00227965, 0.0048937],
                            [0.00485839, 0.01524254, 0.99987202]])
translation_vector = np.array([-0.09321419, 0.03625434, 0.02420657])

def decompose_transform(matrix):
    """将齐次变换矩阵分解为平移向量和欧拉角"""
    translation = matrix[:3, 3]
    rotation = matrix[:3, :3]

    sy = np.sqrt(rotation[0, 0] ** 2 + rotation[1, 0] ** 2)
    singular = sy < 1e-6

    if not singular:
        rx = np.arctan2(rotation[2, 1], rotation[2, 2])
        ry = np.arctan2(-rotation[2, 0], sy)
        rz = np.arctan2(rotation[1, 0], rotation[0, 0])
    else:
        rx = np.arctan2(-rotation[1, 2], rotation[1, 1])
        ry = np.arctan2(-rotation[2, 0], sy)
        rz = 0
    return translation, rx, ry, rz

def convert(x, y, z, rx, ry, rz):
    """
    计算物体相对于机械臂基座的完整位姿
    参数:
        x,y,z,rx,ry,rz: 深度相机识别的物体位姿(相机坐标系)
    返回:
        result: 物体在基坐标系的位姿(平移向量 + 欧拉角)
    """
    # 物体在相机坐标系的位姿转换为齐次变换矩阵
    obj_position = np.array([x, y, z])
    obj_orientation = R.from_euler('xyz', [rx, ry, rz], degrees=False).as_matrix()
    T_object_to_camera = np.eye(4)
    T_object_to_camera[:3, :3] = obj_orientation
    T_object_to_camera[:3, 3] = obj_position

    # 相机到基坐标系的齐次变换矩阵(标定结果)
    T_camera_to_base = np.eye(4)
    T_camera_to_base[:3, :3] = rotation_matrix
    T_camera_to_base[:3, 3] = translation_vector

    # 齐次坐标转换:物体 → 相机 → 基坐标系
    obj_base_coords = T_camera_to_base.dot(T_object_to_camera)
    result = decompose_transform(obj_base_coords)
    return result

五、总结

本文详细讲解了基于睿尔曼开源代码库的机械臂手眼标定全流程,包括前期环境与设备准备、两种核心场景(眼在手上/手外)的标定步骤、常见问题解决,以及标定结果在视觉引导抓取中的实际应用。核心要点可总结为:

  • 环境配置需严格匹配版本要求,避免兼容问题;
  • 数据采集的关键是“姿态多样化”,旋转角度需足够大(>30°);
  • 标定结果的核心是旋转矩阵和平移向量,需根据安装场景(眼在手上/外)正确应用于坐标转换;
  • 若标定失败,优先检查运动姿态是否充足,重新采集数据即可解决大部分问题。

掌握手眼标定后,你可以进一步探索机械臂的视觉引导分拣、精准装配等高级功能。如果在实操过程中有其他问题,欢迎在评论区交流

具身智能:零基礎入門睿爾曼機械臂(七)—— 銜接開源代碼!機械臂手眼標定實操

理論接上實務:將開源手眼解法嵌入睿爾曼控制流程,講資料蒐集、姿態涵蓋、數值發散時的排查與復原,並示範如何把手眼矩陣寫回運行期的座標發布鏈。

來源:https://blog.csdn.net/2403_87969572/article/details/156150986

抓取時間(ISO本地):2026-05-18 05:17:17


文章目錄

前言

在上一篇博客中,我們詳細拆解了睿爾曼開源手眼標定代碼庫的核心代碼邏輯。今天,我們將聚焦實操層面,從環境搭建、設備準備,到具體的標定流程(眼在手上/眼在手外)、常見問題解決,再到標定結果的實際應用,帶大家完整走通機械臂手眼標定的全流程。

注:文章部分圖以及代碼來自睿爾曼科技官方博客 鏈接如下:
https://develop.realman-robotics.com/AI/developerGuide/hand/

一、標定前期準備

在開始標定前,我們需要完成環境配置和設備準備兩大核心工作,這一步直接影響後續標定的順利程度和結果精度。

1.1 環境要求與配置

手眼標定依賴特定的操作系統和Python環境,建議嚴格按照以下版本要求配置,避免因版本兼容問題踩坑。

1.1.1 基礎環境準備

項目版本要求
操作系統Ubuntu / Windows
Python3.9 及以上

1.1.2 Python依賴包安裝

標定程序需要依賴多個Python科學計算和計算機視覺庫,具體版本要求如下:

包名版本要求
numpy2.0.2
opencv-python4.10.0.84
pyrealsense22.55.1.6486
scipy1.13.1
最便捷的安裝方式是使用代碼庫中的requirements.txt文件,在Python環境中執行以下命令即可一鍵安裝所有依賴:

pip install -r requirements.txt

1.2 設備準備

確保以下設備齊全且能正常工作,設備的穩定性直接影響標定精度:

  • 機械臂:支持睿爾曼RM75、RM65、RM63、GEN72型號
  • 相機:Intel RealSense Depth Camera D435(含專用數據線)
  • 網絡設備:網線(用於連接電腦與機械臂)
  • 標定板:1號或2號標定板(可打印紙質版,或在淘寶搜索“標定板棋盤格”購買成品)

在這裡插入圖片描述

二、詳細標定流程

手眼標定主要分為“眼在手上”和“眼在手外”兩種場景,核心區別在於相機的安裝位置:眼在手上是相機固定在機械臂末端,隨機械臂運動;眼在手外是相機固定不動,標定板隨機械臂運動。兩種場景的標定流程略有差異,我們分別詳細講解。

2.1 通用步驟:參數配置

無論哪種場景,在採集數據前都需要先配置標定板參數,步驟如下:

  1. 找到代碼庫中的配置文件config.yaml
  2. 根據實際使用的標定板,修改以下三個核心參數:

在這裡插入圖片描述

- xx:標定板橫向角點數(長邊格子數減1),默認11(例:長邊12個格子,角點數為11);

- YY:標定板縱向角點數(短邊格子數減1),默認8(例:短邊9個格子,角點數為8);

- L:標定板單個方格的實際尺寸(單位:米),默認0.03米。

2.2 場景一:眼在手上(相機隨機械臂運動)

核心邏輯:標定板固定,相機隨機械臂末端運動,採集不同姿態下的標定板圖像,計算相機相對於機械臂末端的位姿。

在這裡插入圖片描述

2.2.1 採集數據

  1. 連接設備:用相機專用數據線連接電腦與D435相機,用網線連接電腦與機械臂;
  2. 配置IP:將電腦IP與機械臂設置為同一網段(機械臂IP為192.168.1.18時,電腦設為1網段;IP為192.168.10.18時,電腦設為10網段);

在這裡插入圖片描述

  1. 放置標定板:將標定板固定在機械臂工作區內的平面上,確保相機(機械臂末端)能從不同視角觀測到,標定期間不得移動標定板;
  2. 運行採集腳本:執行collect_data.py,會彈出相機視野彈窗;
  3. 調整姿態與採集:拖動機械臂末端,使標定板在彈窗中清晰、完整顯示,且標定板與相機鏡面呈一定角度(避免正對,正對為錯誤姿勢);將光標放在彈窗上,按鍵盤s鍵採集數據;

在這裡插入圖片描述
在這裡插入圖片描述

  1. 重複採集:移動機械臂15-20次,每次移動時旋轉角度儘量大於30°,確保在X、Y、Z三個軸上都有足夠的旋轉變化(建議先繞末端Z軸旋轉,再繞X軸旋轉),共採集15-20張不同姿態的圖像。

在這裡插入圖片描述

2.2.2 計算標定結果

採集完成後,運行以下腳本即可得到標定結果:


python compute_in_hand.py

運行成功後,會輸出相機座標系相對於機械臂末端座標系的旋轉矩陣平移向量(平移向量單位:米)。

2.3 場景二:眼在手外(相機固定不動)

核心邏輯:相機固定,標定板固定在機械臂末端隨機械臂運動,採集不同姿態下的標定板圖像,計算相機相對於機械臂基座標系的位姿。

在這裡插入圖片描述

2.3.1 採集數據

  1. 連接設備:同眼在手上場景,用數據線連接相機與電腦,網線連接機械臂與電腦;
  2. 配置IP:同眼在手上場景,確保電腦與機械臂同一網段;
    在這裡插入圖片描述
  3. 安裝標定板與固定相機:將標定板(打印紙質較小的板子,方便固定)固定在機械臂末端,相機固定不動,移動機械臂末端,使標定板出現在相機視野裡。
    標定過程中,標定板安裝在機械臂末端執行器上並隨機械臂移動。可以直接固定在工具法蘭上或由夾具固定安裝,安裝的確切位置不重要,因為不必知道標定板和末端執行器的相對位姿。重要的是標定板在運動過程中不會出現相對於工具法蘭或夾具的位移,它必須被良好地固定住或被夾具緊緊地抓住。建議使用由剛性材料製成的安裝支架。
  4. 運行採集腳本:執行collect_data.py,彈出相機視野彈窗;
  5. 調整姿態與採集:拖動機械臂末端,使標定板在彈窗中清晰、完整顯示,且與相機鏡面呈一定角度;按鍵盤s鍵採集數據;
  6. 重複採集:同眼在手上場景,移動機械臂15-20次,每次旋轉角度大於30°,覆蓋X、Y、Z三軸旋轉變化,採集15-20張圖像。

2.3.2 計算標定結果

採集完成後,運行以下腳本得到標定結果:


python compute_to_hand.py

運行成功後,會輸出相機座標系相對於機械臂基座標系的旋轉矩陣平移向量(平移向量單位:米)。

2.4 誤差範圍說明

標定結果的精度受採集圖像質量影響,正常情況下,平移向量與實際值的差距應控制在1cm之內。若誤差過大,需檢查圖像採集質量(如標定板是否清晰、姿態變化是否充足),重新採集數據進行標定。

三、標定過程常見問題與解決方案

在執行標定計算腳本時,最常見的問題是“運動信息不足”導致標定失敗,我們針對性解決:

問題1:標定失敗,報錯“Not enough informative motions”

問題描述

執行compute_in_hand.pycompute_to_hand.py時,報錯:


[ERROR:0@1.418] global calibration_handeye.cpp:335 calibrateHandEyeTsai Hand-eye calibration failed! Not enough informative motions--include larger rotations.

問題原因

採集數據時,機械臂的旋轉運動不足,尤其是缺少足夠大的旋轉角度變化,導致標定算法無法準確計算手眼關係。

解決方案

  • 增加旋轉運動:確保機械臂在X、Y、Z三個軸上的旋轉角度均超過30°,提供豐富的運動信息;
  • 多樣化姿態:避免機械臂只在小範圍平移或只繞單一軸旋轉,增加姿態多樣性;
  • 增加採集次數:確保採集的圖像數量不少於15張,更多樣本能提升標定穩定性和準確性。

四、標定結果的實際應用:機械臂抓取

手眼標定的核心目的是實現“視覺引導抓取”——通過相機識別物體位姿,結合標定結果將物體位姿轉換為機械臂可識別的基座標系位姿,進而控制機械臂完成抓取。以下分兩種場景講解具體應用(含代碼)。

4.1 場景一:眼在手上的抓取應用

核心邏輯

物體位姿轉換流程:
物體在相機座標系位姿 →(標定結果)→ 物體在機械臂末端座標系位姿 →(機械臂API)→ 物體在機械臂基座標系位姿

機械臂通過基座標系下的物體位姿,計算末端執行器的運動軌跡,完成抓取。

在這裡插入圖片描述

應用代碼

以下代碼分別實現“物體為3D點”和“物體為完整位姿”兩種情況下的座標轉換(需將代碼中的旋轉矩陣和平移向量替換為你的標定結果)。

情況1:物體在相機座標系為3D點(x,y,z)

import numpy as np
from scipy.spatial.transform import Rotation as R

# 相機座標系到機械臂末端座標系的旋轉矩陣和平移向量(手眼標定得到)
rotation_matrix = np.array([[-0.00235395 , 0.99988123 ,-0.01523124],
                            [-0.99998543, -0.00227965, 0.0048937],
                            [0.00485839, 0.01524254, 0.99987202]])
translation_vector = np.array([-0.09321419, 0.03625434, 0.02420657])

def convert(x, y, z, x1, y1, z1, rx, ry, rz):
    """
    計算物體相對於機械臂基座的位姿(x, y, z)
    參數:
        x,y,z: 深度相機識別的物體座標(相機座標系)
        x1,y1,z1,rx,ry,rz: 機械臂末端位姿(基座標系,弧度)
    返回:
        obj_base_coordinates: 物體在機械臂基座標系的座標
    """
    # 深度相機識別物體返回的座標
    obj_camera_coordinates = np.array([x, y, z])

    # 機械臂末端的位姿轉換為齊次變換矩陣
    position = np.array([x1, y1, z1])
    orientation = R.from_euler('xyz', [rx, ry, rz], degrees=False).as_matrix()
    T_base_to_end_effector = np.eye(4)
    T_base_to_end_effector[:3, :3] = orientation
    T_base_to_end_effector[:3, 3] = position

    # 相機到末端的齊次變換矩陣(標定結果)
    T_camera_to_end_effector = np.eye(4)
    T_camera_to_end_effector[:3, :3] = rotation_matrix
    T_camera_to_end_effector[:3, 3] = translation_vector

    # 齊次座標轉換:相機座標系 → 末端座標系 → 基座標系
    obj_camera_coordinates_homo = np.append(obj_camera_coordinates, [1])
    obj_end_effector_coordinates_homo = T_camera_to_end_effector.dot(obj_camera_coordinates_homo)
    obj_base_coordinates_homo = T_base_to_end_effector.dot(obj_end_effector_coordinates_homo)

    # 提取非齊次座標
    obj_base_coordinates = list(obj_base_coordinates_homo[:3])
    return obj_base_coordinates
情況2:物體在相機座標系為完整位姿(x,y,z,rx,ry,rz)

import numpy as np
from scipy.spatial.transform import Rotation as R

# 相機座標系到機械臂末端座標系的旋轉矩陣和平移向量(手眼標定得到)
rotation_matrix = np.array([[-0.00235395 , 0.99988123 ,-0.01523124],
                            [-0.99998543, -0.00227965, 0.0048937],
                            [0.00485839, 0.01524254, 0.99987202]])
translation_vector = np.array([-0.09321419, 0.03625434, 0.02420657])

def decompose_transform(matrix):
    """將齊次變換矩陣分解為平移向量和歐拉角(rx, ry, rz)"""
    translation = matrix[:3, 3]
    rotation = matrix[:3, :3]

    sy = np.sqrt(rotation[0, 0] ** 2 + rotation[1, 0] ** 2)
    singular = sy < 1e-6

    if not singular:
        rx = np.arctan2(rotation[2, 1], rotation[2, 2])
        ry = np.arctan2(-rotation[2, 0], sy)
        rz = np.arctan2(rotation[1, 0], rotation[0, 0])
    else:
        rx = np.arctan2(-rotation[1, 2], rotation[1, 1])
        ry = np.arctan2(-rotation[2, 0], sy)
        rz = 0
    return translation, rx, ry, rz

def convert(x,y,z,rx,ry,rz,x1,y1,z1,rx1,ry1,rz1):
    """
    計算物體相對於機械臂基座的完整位姿
    參數:
        x,y,z,rx,ry,rz: 機械臂末端位姿(基座標系,弧度)
        x1,y1,z1,rx1,ry1,rz1: 深度相機識別的物體位姿(相機座標系)
    返回:
        result: 物體在基座標系的位姿(平移向量 + 歐拉角)
    """
    # 機械臂末端位姿轉換為齊次變換矩陣
    end_position = np.array([x, y, z])
    end_orientation = R.from_euler('xyz', [rx, ry, rz], degrees=False).as_matrix()
    T_end_to_base = np.eye(4)
    T_end_to_base[:3, :3] = end_orientation
    T_end_to_base[:3, 3] = end_position

    # 相機到末端的齊次變換矩陣(標定結果)
    T_camera_to_end = np.eye(4)
    T_camera_to_end[:3, :3] = rotation_matrix
    T_camera_to_end[:3, 3] = translation_vector

    # 物體在相機座標系的位姿轉換為齊次變換矩陣
    obj_position = np.array([x1, y1, z1])
    obj_orientation = R.from_euler('xyz', [rx1, ry1, rz1], degrees=False).as_matrix()
    T_object_to_camera = np.eye(4)
    T_object_to_camera[:3, :3] = obj_orientation
    T_object_to_camera[:3, 3] = obj_position

    # 齊次座標轉換:物體 → 相機 → 末端 → 基座標系
    obj_end_coords_homo = T_camera_to_end.dot(T_object_to_camera)
    obj_base_coords = T_end_to_base.dot(obj_end_coords_homo)

    # 分解得到最終位姿
    result = decompose_transform(obj_base_coords)
    return result

4.2 場景二:眼在手外的抓取應用

核心邏輯

物體位姿轉換流程:
物體在相機座標系位姿 →(標定結果)→ 物體在機械臂基座標系位姿

因相機固定,標定結果直接給出相機與基座標系的關係,無需經過末端座標系轉換,流程更簡潔。

在這裡插入圖片描述

應用代碼

以下代碼同樣分“3D點”和“完整位姿”兩種情況,需替換為眼在手外的標定結果。

情況1:物體在相機座標系為3D點(x,y,z)

import numpy as np

# 相機座標系到機械臂基座標系的旋轉矩陣和平移向量(手眼標定得到)
rotation_matrix = np.array([[-0.00235395 , 0.99988123 ,-0.01523124],
                            [-0.99998543, -0.00227965, 0.0048937],
                            [0.00485839, 0.01524254, 0.99987202]])
translation_vector = np.array([-0.09321419, 0.03625434, 0.02420657])

def convert(x, y, z):
    """
    計算物體相對於機械臂基座的座標
    參數:
        x,y,z: 深度相機識別的物體座標(相機座標系)
    返回:
        obj_base_coordinates: 物體在基座標系的座標
    """
    obj_camera_coordinates = np.array([x, y, z])

    # 相機到基座標系的齊次變換矩陣(標定結果)
    T_camera_to_base = np.eye(4)
    T_camera_to_base[:3, :3] = rotation_matrix
    T_camera_to_base[:3, 3] = translation_vector

    # 齊次座標轉換
    obj_camera_coords_homo = np.append(obj_camera_coordinates, [1])
    obj_base_coords_homo = T_camera_to_base.dot(obj_camera_coords_homo)
    obj_base_coordinates = list(obj_base_coords_homo[:3])
    return obj_base_coordinates
情況2:物體在相機座標系為完整位姿(x,y,z,rx,ry,rz)

import numpy as np
from scipy.spatial.transform import Rotation as R

# 相機座標系到機械臂基座標系的旋轉矩陣和平移向量(手眼標定得到)
rotation_matrix = np.array([[-0.00235395 , 0.99988123 ,-0.01523124],
                            [-0.99998543, -0.00227965, 0.0048937],
                            [0.00485839, 0.01524254, 0.99987202]])
translation_vector = np.array([-0.09321419, 0.03625434, 0.02420657])

def decompose_transform(matrix):
    """將齊次變換矩陣分解為平移向量和歐拉角"""
    translation = matrix[:3, 3]
    rotation = matrix[:3, :3]

    sy = np.sqrt(rotation[0, 0] ** 2 + rotation[1, 0] ** 2)
    singular = sy < 1e-6

    if not singular:
        rx = np.arctan2(rotation[2, 1], rotation[2, 2])
        ry = np.arctan2(-rotation[2, 0], sy)
        rz = np.arctan2(rotation[1, 0], rotation[0, 0])
    else:
        rx = np.arctan2(-rotation[1, 2], rotation[1, 1])
        ry = np.arctan2(-rotation[2, 0], sy)
        rz = 0
    return translation, rx, ry, rz

def convert(x, y, z, rx, ry, rz):
    """
    計算物體相對於機械臂基座的完整位姿
    參數:
        x,y,z,rx,ry,rz: 深度相機識別的物體位姿(相機座標系)
    返回:
        result: 物體在基座標系的位姿(平移向量 + 歐拉角)
    """
    # 物體在相機座標系的位姿轉換為齊次變換矩陣
    obj_position = np.array([x, y, z])
    obj_orientation = R.from_euler('xyz', [rx, ry, rz], degrees=False).as_matrix()
    T_object_to_camera = np.eye(4)
    T_object_to_camera[:3, :3] = obj_orientation
    T_object_to_camera[:3, 3] = obj_position

    # 相機到基座標系的齊次變換矩陣(標定結果)
    T_camera_to_base = np.eye(4)
    T_camera_to_base[:3, :3] = rotation_matrix
    T_camera_to_base[:3, 3] = translation_vector

    # 齊次座標轉換:物體 → 相機 → 基座標系
    obj_base_coords = T_camera_to_base.dot(T_object_to_camera)
    result = decompose_transform(obj_base_coords)
    return result

五、總結

本文詳細講解了基於睿爾曼開源代碼庫的機械臂手眼標定全流程,包括前期環境與設備準備、兩種核心場景(眼在手上/手外)的標定步驟、常見問題解決,以及標定結果在視覺引導抓取中的實際應用。核心要點可總結為:

  • 環境配置需嚴格匹配版本要求,避免兼容問題;
  • 數據採集的關鍵是“姿態多樣化”,旋轉角度需足夠大(>30°);
  • 標定結果的核心是旋轉矩陣和平移向量,需根據安裝場景(眼在手上/外)正確應用於座標轉換;
  • 若標定失敗,優先檢查運動姿態是否充足,重新採集數據即可解決大部分問題。

掌握手眼標定後,你可以進一步探索機械臂的視覺引導分揀、精準裝配等高級功能。如果在實操過程中有其他問題,歡迎在評論區交流

Embodied AI: RealMan Arm (7) — Hand-Eye Calibration Hands-On

Hands-on sequel: wiring open-source AX=XB solvers into Realman pipelines—data rituals, posture coverage, numeric stability, recovering sane extrinsics, and publishing transforms back into the runtime graph.

Captured at (local ISO): 2026-05-18 05:17:17


Preface

Following the code walkthrough, this post covers end-to-end calibration: environment, capture, compute, troubleshooting, and vision-guided grasp transforms.

Reference: https://develop.realman-robotics.com/AI/developerGuide/hand/

I. Preparation

1.1 Environment

ItemRequirement
OSUbuntu / Windows
Python3.9+
PackageVersion
numpy2.0.2
opencv-python4.10.0.84
pyrealsense22.55.1.6486
scipy1.13.1
pip install -r requirements.txt

1.2 Hardware

  • Arm: RM75 / RM65 / RM63 / GEN72
  • Camera: Intel RealSense D435 + cable
  • Ethernet to PC
  • Printed or purchased checkerboard

在这里插入图片描述

II. Calibration Workflow

2.1 Shared: config.yaml

在这里插入图片描述

- xx: inner corners along long edge (e.g. 11 for 12 squares)
- YY: inner corners along short edge (e.g. 8)
- L: square size in meters (e.g. 0.03)

2.2 Eye-in-Hand (camera on arm)

Board fixed; camera moves.

在这里插入图片描述

Capture (collect_data.py):

  1. D435 + arm on same subnet (e.g. arm 192.168.1.18 → PC 192.168.1.x).
  2. Fix board; do not move it during capture.
  3. Run script; avoid head-on views — tilt board vs. camera.
  4. Press ‘s’ per pose; 15–20 poses, >30° rotation on X/Y/Z.

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Compute:

python compute_in_hand.py

Output: rotation + translation (meters) camera → flange.

2.3 Eye-to-Hand (fixed camera)

Board on tool; camera static.

在这里插入图片描述

Same capture flow; board must not slip on the flange.

python compute_to_hand.py

Output: camera → base.

2.4 Accuracy

Translation error should be < 1 cm; otherwise recapture with clearer images and richer motion.

III. Troubleshooting

“Not enough informative motions”

[ERROR:0@1.418] global calibration_handeye.cpp:335 calibrateHandEyeTsai Hand-eye calibration failed! Not enough informative motions--include larger rotations.

Fix: rotate >30° on all axes, diverse poses, ≥15 images.

IV. Grasp Applications

4.1 Eye-in-Hand

Chain: object in camera → (calibration) → flange → (FK) → base.

在这里插入图片描述

3D point — full code in source convert(x,y,z,x1,y1,z1,rx,ry,rz) with your rotation_matrix and translation_vector.

Full posedecompose_transform + homogeneous chain for orientation.

(Code blocks preserved verbatim in Chinese source file — replace matrices with your calibration output.)

4.2 Eye-to-Hand

Object in camera → base directly via T_camera_to_base.

在这里插入图片描述

Simpler convert(x,y,z) and full-pose variants in source.

V. Summary

  • Match dependency versions
  • Rich rotations during capture
  • Apply the correct transform (in-hand vs to-hand)
  • On failure, recollect with larger rotations

Ready for visual picking, sorting, and assembly tasks.