4. 相机标定:内参标定原理、棋盘格标定流程、手眼标定
相机标定,说白了就是给机器人的眼睛配一副准确的眼镜。你想想看,机械臂要抓一个杯子,如果相机看到的坐标和实际物理坐标对不上,那抓取精度肯定一塌糊涂。我在项目里见过太多因为标定马虎导致抓取失败的案例了。
这一章我们重点解决两个问题:相机自己怎么看世界(内参标定),以及相机和机械臂怎么对齐坐标系(手眼标定)。
4.1 内参标定原理
相机内参,描述的是光线从三维世界投影到二维图像传感器的过程。核心参数就这几个:
| 参数 | 符号 | 含义 |
|---|---|---|
| 焦距 | fx, fy | 镜头到成像平面的距离,单位像素 |
| 主点 | cx, cy | 光轴与成像平面的交点,一般在图像中心附近 |
| 畸变系数 | k1, k2, p1, p2, k3 | 径向畸变和切向畸变的矫正参数 |
畸变这东西,我刚开始做的时候没太在意。结果有一次在产线上,机械臂抓取边缘的工件总是偏几毫米。查了半天,发现是镜头边缘畸变没矫正。嗯,从那以后我再也不敢跳过畸变标定了。
内参标定的数学本质,就是求解一个3x3的矩阵加上畸变向量。常用的方法是张正友标定法,用棋盘格作为已知尺寸的平面靶标。
4.2 棋盘格标定流程
棋盘格标定,我个人习惯用OpenCV的cv2.calibrateCamera()。流程其实不复杂,但有几个坑要注意。
- 打印棋盘格:用A4纸打印,贴在硬纸板上。格子大小要量准,我一般用30mm或40mm的方格。
- 采集图像:从不同角度拍15-20张。记住,棋盘格要占画面1/3以上,角度要覆盖俯仰、偏航、旋转。
- 提取角点:用
cv2.findChessboardCorners()。如果光线不好,可以调低阈值或者用亚像素细化。 - 标定计算:传入世界坐标和图像坐标,调用
calibrateCamera。 - 评估结果:看重投影误差,一般小于0.5像素就算合格。
代码示例,我常用的标定片段:
import cv2
import numpy as np
# 棋盘格尺寸,内角点数量
pattern_size = (9, 6) # 9x6个内角点
square_size = 30.0 # 方格边长30mm
# 准备世界坐标
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)
objp *= square_size
obj_points = [] # 世界坐标
img_points = [] # 图像坐标
# 遍历图像
for fname in image_files:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
if ret:
obj_points.append(objp)
# 亚像素细化
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
img_points.append(corners2)
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
obj_points, img_points, gray.shape[::-1], None, None)
print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
print("重投影误差:", ret)
4.3 手眼标定
内参标定解决的是相机自己的问题。手眼标定解决的是相机坐标系和机械臂基坐标系之间的变换关系。这里有两种主流配置:
| 配置 | 相机安装位置 | 求解方程 | 适用场景 |
|---|---|---|---|
| Eye-to-Hand | 固定在机械臂外部(如支架上) | AX = XB | 大范围、固定工位 |
| Eye-in-Hand | 固定在机械臂末端(随臂移动) | AX = XB | 近距离、高精度、灵活 |
有意思的是,两种配置的数学形式都是AX = XB。A是机械臂末端到基座的变换,B是相机到标定板的变换,X就是我们要找的手眼矩阵。
我个人更常用Eye-in-Hand。为什么?因为相机跟着机械臂走,可以凑近看工件细节。但要注意,标定板必须固定在机械臂工作空间内不动。
4.4 手眼标定流程(以Eye-in-Hand为例)
- 固定标定板:放在机械臂工作空间内,确保相机在各个角度都能看到。
- 采集数据对:移动机械臂到15-20个不同位姿,记录每个位姿下:
- 机械臂末端位姿(从控制器读取)
- 相机拍摄标定板的位姿(用solvePnP计算)
- 求解AX=XB:用Tsai法或Park法求解。
- 验证:用一组新数据验证,看重投影误差。
代码实现,我习惯用OpenCV的cv2.calibrateHandEye():
import cv2
import numpy as np
# 假设已经采集了N组数据
# R_gripper2base, t_gripper2base: 机械臂末端到基座的旋转和平移
# R_target2cam, t_target2cam: 标定板到相机的旋转和平移
R_gripper2base = np.array([...]) # Nx3x3
t_gripper2base = np.array([...]) # Nx3
R_target2cam = np.array([...]) # Nx3x3
t_target2cam = np.array([...]) # Nx3
# 手眼标定
R_cam2gripper, t_cam2gripper = cv2.calibrateHandEye(
R_gripper2base, t_gripper2base,
R_target2cam, t_target2cam,
method=cv2.CALIB_HAND_EYE_TSAI
)
print("相机到机械臂末端的旋转矩阵:\n", R_cam2gripper)
print("相机到机械臂末端的平移向量:\n", t_cam2gripper)
- 机械臂位姿要多样化,不要只在一个小范围内移动
- 每个位姿下,标定板要清晰可见,角点提取准确
- 至少采集15组数据,20组更稳妥
下面这张图展示了Eye-in-Hand和Eye-to-Hand两种配置的坐标系关系:
4.5 标定结果验证
标定完不是就完事了。我每次都会做一步验证:
- 内参验证:用标定好的相机拍一张已知尺寸的物体,测量像素距离换算成物理距离,看误差。
- 手眼验证:让机械臂末端去碰一个固定点,用相机定位该点,看两者坐标是否一致。
验证通过后,把内参矩阵和手眼矩阵保存成YAML或JSON文件,后续部署时直接加载。嗯,这一步千万别省,我见过有人标定完直接上产线,结果抓偏了才发现标定数据没保存。