1. 运动姿态的多样性(最关键)
手眼标定的数学本质是解方程。如果你的动作太单一,方程组会出现"退化",导致解不出来或精度极低。
- 多增加xyz轴的旋转角度 ,尝试让相机倾斜不同的角度(前倾、后仰、侧倾)。确保标定板出现在相机画面的不同位置。标定板应占据相机视野的 30%~60%
2. 相机与视觉层面的优化
-
精确的内参标定 :如果内参(焦距、畸变系数)不准,后续所有计算都是错的。重投影误差应控制在 0.1 像素 以内。
-
标定板质量 :不要使用打印纸!纸张的受潮形变会带来毫米级的误差。建议使用高精度陶瓷或玻璃材质的棋盘格标定板。
-
固定焦距与光圈:标定开始后,绝对不能再动镜头的调焦环。
3. 机器人数据的准确性
-
确认位姿定义 :大族 Elfin 机器人的示教器显示的 RX, RY, RZ 通常是欧拉角(ZYX顺序)。如果你的算法默认是 XYZ 顺序,计算结果会南辕北辙。
-
TCP 补偿:如果你在标定时使用了特定的工具末端,请确保在示教器中已经正确设置了工具中心点(TCP)偏移,或者直接使用机械臂法兰盘中心的原始位姿数据。
-
预热机械臂:工业机器人在冷机状态下存在微小的热形变。建议在标定前让机械臂运动 10-20 分钟进入热平衡状态。
4. 算法与数据处理
-
剔除异常值:采集 20 组数据后,可以先用全部数据算一次。观察每组数据的"重投影残差",剔除掉残差最大的 2-3 组(可能是因为拍照瞬间由于震动导致图像模糊),然后重新计算。
-
增加数据量 :虽然理论上 3 组数据就能解出 X,但实际应用中建议采集 15-25 组 数据,通过最小二乘法来平摊单次采集的随机误差。
-
单位统一:严格检查单位。机器人坐标通常是 m 或 mm,相机外参通常是 mm,必须统一。
5. 环境因素控制
-
光照一致性:使用外部光源(如环形灯)确保标定板黑白交界处锐利且无反光。
-
刚性连接:确保相机支架与机械臂(或底座)连接极其稳固。标定过程中,哪怕 0.5mm 的松动都会导致标定失败。
-
标定板固定:在"眼在手上"模式下,标定板必须绝对静止,不能有任何位移。
基于非线性优化的机械臂手眼标定精度提升方案:
该方案不仅仅是计算一个结果,而是通过高精度视觉提取 与全局数学建模,提取出最真实的坐标转换关系
1. 核心问题:为什么传统标定不够准?
在向客户介绍前,先说明传统方法的局限性:
-
误差累积:传统 AX=XB 线性算法通常先算旋转、再算平移。一旦旋转角度有一点偏差,平移误差会像杠杆一样被放大。
-
噪声敏感:手动记录位姿时存在微小的人工读数误差,环境光线会干扰像素点定位。
-
模型简化:普通算法假设相机是没有畸变的理想状态。
2. 本方案的四大精度提升支柱
我们的方案通过以下四个层面来确保标定结果的绝对可靠:
A. 亚像素级视觉提取 (Sub-pixel Detection)
-
技术细节 :不直接使用原始图像像素,而是采用
CornerSubPix算法。 -
汇报点 :我们将标定板角点的定位精度从一个像素点提升到了 0.1 像素级别。这就好比用更细的针尖去对准刻度,从源头上减少了输入误差。
B. 运动姿态的最优解空间 (Non-degenerate Motion)
- 技术细节:在离线采集时,强制要求三维空间内的不平行旋转。我们不仅采集位置信息,更通过至少 15-20 组涵盖不同倾斜角度的姿态,构建了一个"全方位"的数学约束网络,有效避免了算法在某个特定方向上的死角。
C. 全局一致性非线性优化 (Global Non-linear Optimization)
- 技术细节 :采用 Levenberg-Marquardt (LM) 迭代算法。这是本方案的灵魂。传统算法是"一算定终身",而我们的优化算法会通过成百上千次的循环计算,寻找一个让所有 20 组数据同时达到最佳平衡的最优点。它能自动识别并平摊掉单次测量中产生的随机噪声。
D. 残差审计与数据净化 (Residual Audit)
- 技术细节:基于 AX-XB 残差矩阵的离群点剔除。我们拥有一套"审计机制"。在最终出结果前,系统会自动扫描每组数据的表现,剔除掉那些因为震动或光影变化产生的"异常坏数据",确保参与最终优化的都是最高质量的样本。
3. 预期成果与业务价值
最终能达到的效果:
| 指标 | 传统手动标定 | 本方案(优化后) |
|---|---|---|
| 重复定位精度 | 约 1.0 - 2.0 mm | < 0.2 mm |
| 旋转角度误差 | 约 0.5° - 1.0° | < 0.05° |
| 鲁棒性 | 易受单次测量失误影响 | 个强(具有容错纠错能力) |
python
import cv2
import numpy as np
import pandas as pd
from scipy.spatial.transform import Rotation as R
from scipy.optimize import least_squares
# ==========================================
# 1. 基础参数配置 (你需要修改这里)
# ==========================================
# 填入你标定好的相机内参矩阵 K 和 畸变系数 D
K = np.array([[1200.0, 0, 960.0], [0, 1200.0, 540.0], [0, 0, 1]], dtype=np.float64)
D = np.array([0.1, -0.2, 0, 0, 0], dtype=np.float64)
# 棋盘格规格 (内角点个数,如 9x6 的格标定板,内角点是 8x5)
pattern_size = (8, 5)
square_size = 25.0 # 棋盘格方格物理边长 (mm)
# ==========================================
# 2. 定义优化函数 (提高精度的核心)
# ==========================================
def residual_func(params, As, Bs):
"""计算 AX - XB 的残差"""
# params 前3位是旋转向量,后3位是平移向量
r_vec_x = params[:3]
t_vec_x = params[3:].reshape((3, 1))
R_x, _ = cv2.Rodrigues(r_vec_x)
res = []
for A, B in zip(As, Bs):
R_a = A[:3, :3]
t_a = A[:3, 3].reshape((3, 1))
R_b = B[:3, :3]
t_b = B[:3, 3].reshape((3, 1))
# 旋转部分的残差: R_a * R_x - R_x * R_b
rot_res = R_a @ R_x - R_x @ R_b
# 平移部分的残差: R_a * t_x + t_a - (R_x * t_b + t_x)
trans_res = (R_a @ t_vec_x + t_a) - (R_x @ t_b + t_vec_x)
res.append(rot_res.flatten())
res.append(trans_res.flatten())
return np.concatenate(res)
# ==========================================
# 3. 核心执行流程
# ==========================================
def main():
# 读取数据
df = pd.read_csv('data.csv')
As = [] # 机器人末端位姿矩阵
Bs = [] # 相机观测到的标定板位姿矩阵
# 准备标定板三维点坐标
objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) * square_size
for idx, row in df.iterrows():
# A. 处理机器人位姿 (欧拉角转齐次变换矩阵)
# 大族通常是 ZYX 顺序
rot = R.from_euler('zyx', [row['rx'], row['ry'], row['rz']], degrees=True)
A = np.eye(4)
A[:3, :3] = rot.as_matrix()
A[:3, 3] = [row['x'], row['y'], row['z']]
As.append(A)
# B. 处理相机图像 (获取标定板外参)
img = cv2.imread(row['img_path'])
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
if ret:
# 精细化角点 (提升精度关键)
corners_sub = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
_, rvec, tvec = cv2.solvePnP(objp, corners_sub, K, D)
B = np.eye(4)
B[:3, :3], _ = cv2.Rodrigues(rvec)
B[:3, 3] = tvec.flatten()
Bs.append(B)
else:
print(f"警告: 图像 {row['img_path']} 未检测到角点,请检查!")
# 4. 线性求解获得初值 (使用 OpenCV)
R_gripper2base = [a[:3, :3] for a in As]
t_gripper2base = [a[:3, 3] for a in As]
R_target2cam = [b[:3, :3] for b in Bs]
t_target2cam = [b[:3, 3] for b in Bs]
R_initial, t_initial = cv2.calibrateHandEye(R_gripper2base, t_gripper2base,
R_target2cam, t_target2cam,
method=cv2.CALIB_HAND_EYE_TSAI)
# 5. 非线性优化 (将精度提升至最高)
r_initial_vec, _ = cv2.Rodrigues(R_initial)
initial_params = np.concatenate([r_initial_vec.flatten(), t_initial.flatten()])
# 核心优化步骤
opt_res = least_squares(residual_func, initial_params, args=(As, Bs), method='lm')
# 结果转换回矩阵
final_rvec = opt_res.x[:3]
final_t = opt_res.x[3:]
final_R, _ = cv2.Rodrigues(final_rvec)
print("\n--- 最终手眼标定矩阵 (X) ---")
print("相机相对于机械臂末端的旋转矩阵:\n", final_R)
print("相机相对于机械臂末端的平移(mm):\n", final_t)
if __name__ == "__main__":
main()