3、相机标定:内参、外参、畸变模型(径向与切向)、标定板设计与OpenCV标定流程
相机标定是双目立体视觉系统中决定深度估计精度的基石环节。无论是工业检测、机器人导航还是三维重建,如果相机内参、外参和畸变系数存在偏差,后续的极线校正、立体匹配和三角测量都会产生系统性误差。本章将系统讲解针孔模型下的内参与外参物理意义、径向与切向畸变的数学表达、标定板的设计规范,以及基于OpenCV的完整标定流程与实战排障方法。
上图展示了从世界坐标系到像素坐标系的完整映射链路。外参将世界点变换到相机坐标系,内参将相机坐标投影到像素平面,而畸变模型则描述了实际镜头与理想针孔之间的偏差。理解这条链路是标定工作的前提。
3.1 核心原理:针孔模型与内外参的物理意义
理想情况下,相机成像遵循针孔模型。设世界坐标系中一点 Pw = (Xw, Yw, Zw),经过外参矩阵 [R|t] 变换到相机坐标系 Pc = (Xc, Yc, Zc),再通过内参矩阵 K 投影到像素平面 (u, v)。整个过程可表示为:
Zc * [u, v, 1]^T = K * [R|t] * [Xw, Yw, Zw, 1]^T
内参矩阵 K 包含焦距 fx、fy(单位像素),主点坐标 cx、cy,以及倾斜因子 s(通常为0)。焦距的物理意义是镜头光心到成像平面的距离,而主点则是光轴与成像平面的交点。对于双目系统,左右相机的内参通常不同,需要分别标定。
外参矩阵描述相机在世界坐标系中的位姿。旋转矩阵 R 由三个欧拉角(或四元数)构成,平移向量 t 表示相机光心在世界坐标系中的位置。在双目系统中,左右相机之间的相对外参(Rstereo, tstereo)是立体校正和深度计算的关键参数。
3.2 畸变模型:径向与切向的数学表达
实际镜头并非完美针孔,存在两类主要畸变:
- 径向畸变:由透镜形状引起,光线在远离中心处弯曲更严重。表现为桶形畸变(负值)或枕形畸变(正值)。用 k1、k2、k3 三个系数描述,其中 k3 通常用于鱼眼镜头,普通镜头可设为0。
- 切向畸变:由透镜与成像平面不平行引起,源于装配误差。用 p1、p2 两个系数描述。
畸变校正的数学模型如下(以归一化坐标 xdist, ydist 表示):
r^2 = x^2 + y^2
x_dist = x * (1 + k1*r^2 + k2*r^4 + k3*r^6) + 2*p1*x*y + p2*(r^2 + 2*x^2)
y_dist = y * (1 + k1*r^2 + k2*r^4 + k3*r^6) + p1*(r^2 + 2*y^2) + 2*p2*x*y
OpenCV 的 calibrateCamera 函数默认优化 5 个畸变系数(k1, k2, p1, p2, k3)。对于双目系统,建议先单独标定每个相机,再使用 stereoCalibrate 优化双目标定参数。
| 畸变类型 | 系数 | 典型表现 | 校正方法 |
|---|---|---|---|
| 径向畸变 | k1, k2, k3 | 直线变曲线,边缘明显 | 多项式拟合 |
| 切向畸变 | p1, p2 | 图像倾斜,中心偏移 | 透视校正 |
3.3 标定板设计与采集规范
标定板的质量直接影响标定精度。推荐使用高精度棋盘格或圆点阵列板,设计要点如下:
- 棋盘格尺寸:建议 10x7 或 12x9 内角点,方格边长 20-30mm。过小的棋盘格会导致角点提取不稳定,过大则难以在视野中完整呈现。
- 平面度:标定板必须平整,平面度误差应小于 0.1mm。建议使用玻璃基板或铝基板,避免使用纸板(易弯曲)。
- 表面处理:哑光打印,避免反光。黑白格对比度要高,边缘清晰。
- 采集数量与角度:至少采集 15-25 张图像,覆盖不同角度(倾斜 20°-30°)、不同距离(近、中、远)和不同位置(画面四角与中心)。
3.4 OpenCV 标定流程与实战案例
以下是一个完整的 OpenCV 标定流程,以 Python 为例:
import cv2
import numpy as np
import glob
# 1. 准备标定板角点坐标(世界坐标系)
CHECKERBOARD = (10, 7) # 内角点数
square_size = 25.0 # 方格边长 mm
objp = np.zeros((CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)
objp *= square_size
objpoints = [] # 世界坐标
imgpoints = [] # 像素坐标
# 2. 遍历图像提取角点
images = glob.glob('calib_images/*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None)
if ret:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
imgpoints.append(corners2)
cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret)
cv2.imshow('Corners', img)
cv2.waitKey(100)
cv2.destroyAllWindows()
# 3. 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# 4. 评估重投影误差
total_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
print(f"平均重投影误差: {total_error/len(objpoints):.4f} px")
# 5. 保存参数
np.savez('camera_params.npz', mtx=mtx, dist=dist, rvecs=rvecs, tvecs=tvecs)
案例:工业双目系统标定实战
某汽车零部件检测项目需要测量螺栓孔间距,精度要求 ±0.05mm,工作距离 300mm。使用两台 500 万像素工业相机(像元 2.2μm,焦距 12mm),基线距离 120mm。
初始现象:直接使用出厂内参进行立体匹配,深度误差达到 ±2mm,远超标要求。
分析过程:检查发现出厂内参未考虑镜头畸变,且相机在运输过程中可能发生微小位移。决定重新标定。
操作步骤:
- 设计 12x9 棋盘格,方格边长 20mm,玻璃基板,平面度 0.05mm。
- 左右相机各采集 20 张图像,覆盖视野四角、中心及倾斜 25° 视角。
- 分别标定左右相机,重投影误差均小于 0.08px。
- 使用
stereoCalibrate计算双目标定参数,得到 Rstereo 和 tstereo。 - 进行立体校正,验证极线误差小于 0.5px。
结果对比:重新标定后,深度误差降至 ±0.08mm,满足检测要求。同时发现切向畸变系数 p1=0.0023,说明镜头装配存在轻微倾斜,若不校正将导致边缘区域测量偏差。
风险与代价:标定过程耗时约 1 小时,但换来了 25 倍的精度提升。后续每季度需复标一次,防止温度变化或振动导致参数漂移。
3.5 常见误区与排障指南
| 误区 | 现象 | 解决方案 |
|---|---|---|
| 标定板不平整 | 重投影误差大,边缘角点偏差明显 | 更换玻璃基板,使用水平仪验证 |
| 图像数量不足 | 参数过拟合,新视角误差大 | 增加至 20 张以上,覆盖更多角度 |
| 忽略切向畸变 | 图像中心区域校正良好,边缘倾斜 | 启用 p1, p2 参数 |
| 标定板反光 | 角点提取失败或偏移 | 使用哑光板,调整光源角度 |
| 分辨率变化 | 标定参数失效 | 标定时的分辨率必须与使用时一致 |
排障时,首先检查重投影误差是否小于 0.1px。若误差偏大,逐张查看角点提取结果,剔除模糊或遮挡严重的图像。同时注意,标定板在图像中的尺寸应占画面的 20%-80%,过小或过大都会影响角点提取精度。
最后,标定参数的验证不能只看重投影误差,还应通过立体校正后的极线约束来验证。极线误差(左右图像对应点的垂直坐标差)应小于 1px,这是双目系统标定质量的最终检验标准。