@
目录
- 前言
- 一、标定前期准备
- 1.1 环境要求与配置
- 1.1.1 基础环境准备
- 1.1.2 Python依赖包安装
- 1.2 设备准备
- 二、详细标定流程
- 2.1 通用步骤:参数配置
- 2.2 场景一:眼在手上(相机随机械臂运动)
- 2.3 场景二:眼在手外(相机固定不动)
- 2.4 误差范围说明
- 三、标定过程常见问题与解决方案
- 问题1:标定失败,报错“Not enough informative motions”
- 四、标定结果的实际应用:机械臂抓取
- 4.1 场景一:眼在手上的抓取应用
- 核心逻辑
- 应用代码
- 情况1:物体在相机坐标系为3D点(x,y,z)
- 情况2:物体在相机坐标系为完整位姿(x,y,z,rx,ry,rz)
- 4.2 场景二:眼在手外的抓取应用
- 核心逻辑
- 应用代码
- 情况1:物体在相机坐标系为3D点(x,y,z)
- 情况2:物体在相机坐标系为完整位姿(x,y,z,rx,ry,rz)
- 五、总结
前言
在上一篇博客中,我们详细拆解了睿尔曼开源手眼标定代码库的核心代码逻辑。今天,我们将聚焦实操层面,从环境搭建、设备准备,到具体的标定流程(眼在手上/眼在手外)、常见问题解决,再到标定结果的实际应用,带大家完整走通机械臂手眼标定的全流程。
注:文章部分图以及代码来自睿尔曼科技官方博客 链接如下:
https://develop.realman-robotics.com/AI/developerGuide/hand/
一、标定前期准备
在开始标定前,我们需要完成环境配置和设备准备两大核心工作,这一步直接影响后续标定的顺利程度和结果精度。
1.1 环境要求与配置
手眼标定依赖特定的操作系统和Python环境,建议严格按照以下版本要求配置,避免因版本兼容问题踩坑。
1.1.1 基础环境准备
项目版本要求操作系统Ubuntu / WindowsPython3.9 及以上1.1.2 Python依赖包安装
标定程序需要依赖多个Python科学计算和计算机视觉库,具体版本要求如下:
包名版本要求numpy2.0.2opencv-python4.10.0.84pyrealsense22.55.1.6486scipy1.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 通用步骤:参数配置
无论哪种场景,在采集数据前都需要先配置标定板参数,步骤如下:
- 找到代码库中的配置文件config.yaml;
- 根据实际使用的标定板,修改以下三个核心参数:
- - xx:标定板横向角点数(长边格子数减1),默认11(例:长边12个格子,角点数为11);
- - YY:标定板纵向角点数(短边格子数减1),默认8(例:短边9个格子,角点数为8);
- - L:标定板单个方格的实际尺寸(单位:米),默认0.03米。
复制代码 2.2 场景一:眼在手上(相机随机械臂运动)
核心逻辑:标定板固定,相机随机械臂末端运动,采集不同姿态下的标定板图像,计算相机相对于机械臂末端的位姿。
2.2.1 采集数据
- 连接设备:用相机专用数据线连接电脑与D435相机,用网线连接电脑与机械臂;
- 配置IP:将电脑IP与机械臂设置为同一网段(机械臂IP为192.168.1.18时,电脑设为1网段;IP为192.168.10.18时,电脑设为10网段);
- 放置标定板:将标定板固定在机械臂工作区内的平面上,确保相机(机械臂末端)能从不同视角观测到,标定期间不得移动标定板;
- 运行采集脚本:执行collect_data.py,会弹出相机视野弹窗;
- 调整姿态与采集:拖动机械臂末端,使标定板在弹窗中清晰、完整显示,且标定板与相机镜面呈一定角度(避免正对,正对为错误姿势);将光标放在弹窗上,按键盘s键采集数据;
- 重复采集:移动机械臂15-20次,每次移动时旋转角度尽量大于30°,确保在X、Y、Z三个轴上都有足够的旋转变化(建议先绕末端Z轴旋转,再绕X轴旋转),共采集15-20张不同姿态的图像。
2.2.2 计算标定结果
采集完成后,运行以下脚本即可得到标定结果:- python compute_in_hand.py
复制代码 运行成功后,会输出相机坐标系相对于机械臂末端坐标系的旋转矩阵和平移向量(平移向量单位:米)。
2.3 场景二:眼在手外(相机固定不动)
核心逻辑:相机固定,标定板固定在机械臂末端随机械臂运动,采集不同姿态下的标定板图像,计算相机相对于机械臂基坐标系的位姿。
2.3.1 采集数据
- 连接设备:同眼在手上场景,用数据线连接相机与电脑,网线连接机械臂与电脑;
- 配置IP:同眼在手上场景,确保电脑与机械臂同一网段;
- 安装标定板与固定相机:将标定板(打印纸质较小的板子,方便固定)固定在机械臂末端,相机固定不动,移动机械臂末端,使标定板出现在相机视野里。
标定过程中,标定板安装在机械臂末端执行器上并随机械臂移动。可以直接固定在工具法兰上或由夹具固定安装,安装的确切位置不重要,因为不必知道标定板和末端执行器的相对位姿。重要的是标定板在运动过程中不会出现相对于工具法兰或夹具的位移,它必须被良好地固定住或被夹具紧紧地抓住。建议使用由刚性材料制成的安装支架。
- 运行采集脚本:执行collect_data.py,弹出相机视野弹窗;
- 调整姿态与采集:拖动机械臂末端,使标定板在弹窗中清晰、完整显示,且与相机镜面呈一定角度;按键盘s键采集数据;
- 重复采集:同眼在手上场景,移动机械臂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.py或compute_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°);
- 标定结果的核心是旋转矩阵和平移向量,需根据安装场景(眼在手上/外)正确应用于坐标转换;
- 若标定失败,优先检查运动姿态是否充足,重新采集数据即可解决大部分问题。
掌握手眼标定后,你可以进一步探索机械臂的视觉引导分拣、精准装配等高级功能。如果在实操过程中有其他问题,欢迎在评论区交流
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |