在机器人视觉系统中,手眼标定是连接机械臂与相机的关键桥梁。想象一下,当你需要让机械臂精准抓取传送带上的零件,或是完成高精度的装配任务时,相机看到的物体位置必须准确转换为机械臂坐标系下的坐标——这就是手眼标定的核心价值。本文将带你从零开始,用Python、Intel RealSense和JAKA机械臂构建一套工业级的手眼标定系统。
首先创建独立的conda环境以避免依赖冲突:
bash复制conda create -n handeye python=3.8
conda activate handeye
pip install numpy opencv-contrib-python pyrealsense2 transforms3d jkrc
注意:安装opencv-contrib-python时需包含aruco模块,若遇到问题可尝试从源码编译OpenCV
python复制import jkrc
robot = jkrc.RC("192.168.0.104") # 替换为实际IP
robot.login()
print(robot.get_tcp_position())
python复制import pyrealsense2 as rs
pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.color, 848, 480, rs.format.bgr8, 30)
pipeline.start(config)
frames = pipeline.wait_for_frames()
color_frame = frames.get_color_frame()
print("相机分辨率:", color_frame.width, "x", color_frame.height)
pipeline.stop()
手眼标定解决的是求解相机坐标系到机械臂末端坐标系变换矩阵的问题。根据机械臂运动方式,可分为两类情况:
| 标定类型 | 适用场景 | 求解方程 |
|---|---|---|
| Eye-to-Hand | 相机固定 | AX = XB |
| Eye-in-Hand | 相机随动 | AX = XB |
本文以Eye-in-Hand配置为例,使用Tsai方法求解:
python复制R_cam2gripper, t_cam2gripper = cv2.calibrateHandEye(
R_gripper2base, t_gripper2base,
R_target2cam, t_target2cam,
method=cv2.CALIB_HAND_EYE_TSAI
)
机械臂欧拉角到旋转矩阵的转换(注意单位统一):
python复制import transforms3d as tfs
# JAKA返回的欧拉角(弧度)转旋转矩阵
euler_angles = [0.1, -0.2, 0.3] # rx, ry, rz
R = tfs.euler.euler2mat(euler_angles[0], euler_angles[1], euler_angles[2], 'sxyz')
相机旋转向量到旋转矩阵的转换:
python复制rvec = np.array([0.5, -0.3, 0.8]) # 旋转向量
R_cam, _ = cv2.Rodrigues(rvec)
采用交互式数据采集策略,通过键盘控制记录时机:
python复制hands = [] # 存储机械臂末端位姿
cameras = [] # 存储相机检测到的标定板位姿
while True:
rgb, _, intr_matrix, intr_coeffs = get_aligned_images()
cv2.imshow('Preview', rgb)
key = cv2.waitKey(1)
if key == ord('r'): # 记录当前位姿
hands.append(get_jaka_gripper())
cameras.append(get_realsense_mark(intr_matrix, intr_coeffs))
print(f"已记录 {len(hands)} 组数据")
elif key == ord('c'): # 开始计算
calculate_handeye(hands, cameras)
break
计算完成后,可通过重投影误差验证标定质量:
python复制def verify_calibration(R_c2g, t_c2g, hands, cameras):
errors = []
for i in range(len(hands)):
# 理论机械臂位姿
RT_g2b = tfs.affines.compose(hands[i][:3],
tfs.euler.euler2mat(hands[i][3], hands[i][4], hands[i][5], 'sxyz'),
[1,1,1])
# 预测的标定板位姿
pred_RT_t2b = RT_g2b @ tfs.affines.compose(t_c2g, R_c2g, [1,1,1]) @ \
tfs.affines.compose(cameras[i][:3], cv2.Rodrigues(cameras[i][3:6])[0], [1,1,1])
# 计算误差
error = np.linalg.norm(pred_RT_t2b[:3,3] - ground_truth[i])
errors.append(error)
print(f"平均重投影误差: {np.mean(errors):.2f} mm")
ArUco标记检测失败:
机械臂位姿读取异常:
标定结果不稳定:
python复制from threading import Thread
class CameraThread(Thread):
def run(self):
while self.running:
self.frames = pipeline.wait_for_frames()
camera_thread = CameraThread()
camera_thread.start()
python复制def draw_axis(img, rvec, tvec, K, dist):
cv2.drawFrameAxes(img, K, dist, rvec, tvec, 0.1)
return img
python复制sensor = profile.get_device().first_color_sensor()
sensor.set_option(rs.option.enable_auto_exposure, 1)
sensor.set_option(rs.option.auto_exposure_priority, 0) # 优先保证帧率
在实际项目中,我们测试了多种标定方法的精度和稳定性:
| 方法 | 平均误差(mm) | 计算时间(ms) | 适用场景 |
|---|---|---|---|
| Tsai | 1.2 | 15 | 通用场景 |
| Park | 1.5 | 22 | 大范围运动 |
| Horaud | 0.8 | 35 | 高精度需求 |
| Daniilidis | 1.1 | 28 | 异常值较多时 |
得到相机到机械臂的变换矩阵后,可以方便地进行坐标转换:
python复制def cam_to_robot(point_in_cam, R_c2g, t_c2g, robot_pose):
# point_in_cam: [x,y,z] in camera coordinate
# robot_pose: current TCP pose [x,y,z,rx,ry,rz]
# 构建齐次变换矩阵
RT_g2b = tfs.affines.compose(robot_pose[:3],
tfs.euler.euler2mat(robot_pose[3], robot_pose[4], robot_pose[5], 'sxyz'),
[1,1,1])
RT_c2g = tfs.affines.compose(t_c2g, R_c2g, [1,1,1])
# 坐标转换
point_in_base = RT_g2b @ RT_c2g @ np.append(point_in_cam, 1)
return point_in_base[:3]
对于需要频繁调整的场景,可以考虑:
在最近的一个装配项目中,我们实现了误差小于0.5mm的标定精度,关键是在机械臂工作空间的不同区域分别采集数据,最后进行加权融合。