3、传感器标定:相机内参标定(张正友法)、相机外参标定、雷达与相机联合标定原理、标定板设计
好,咱们进入第三个章节。传感器标定,说白了就是给传感器“配眼镜”。
你想想看,摄像头和雷达都是“近视眼”,它们看到的原始数据是有畸变的。如果不先校准,后面做融合就是“盲人摸象”。我当年刚入行时,就吃过这个亏——标定没做好,融合出来的目标位置偏差了半米多,排查了三天才发现是内参没更新。
这一章,咱们把标定这件事彻底讲透。
3.1 相机内参标定:张正友法
相机内参标定,说白了就是搞清楚“像素坐标”和“物理坐标”之间的映射关系。张正友法是目前最主流的方法,因为它只需要一张棋盘格,拍几张照片就能搞定。
核心原理
相机成像模型可以简化为:
s * [u, v, 1]^T = K * [R|t] * [X, Y, Z, 1]^T
其中 K 就是内参矩阵,包含焦距 fx、fy 和主点 cx、cy。张正友法的巧妙之处在于:它假设标定板是平面(Z=0),这样就把外参 R、t 和内参 K 解耦了。
具体步骤
- 打印一张棋盘格标定板,贴在平板上。
- 用相机从不同角度拍摄 10-20 张照片。
- 对每张照片,提取角点坐标。
- 用最小二乘法求解内参初值。
- 考虑径向畸变(k1, k2),用非线性优化迭代求精。
我的经验:我个人习惯拍 15 张左右。太少的话,畸变参数容易过拟合;太多的话,计算量上去了,精度提升有限。另外,照片的角度要覆盖视野的四个角落,别只拍中间区域。
代码示例(OpenCV 实现)
import cv2
import numpy as np
# 准备棋盘格尺寸
chessboard_size = (9, 6) # 内角点数量
square_size = 0.025 # 每个格子边长,单位米
# 准备世界坐标系中的点
objp = np.zeros((chessboard_size[0]*chessboard_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2)
objp *= square_size
# 存储所有图像的点
objpoints = [] # 世界坐标系中的点
imgpoints = [] # 图像坐标系中的点
images = [...] # 你的图片列表
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
if ret:
objpoints.append(objp)
imgpoints.append(corners)
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
避坑指南:我曾经遇到过标定结果反复跳动的情况。后来发现是棋盘格打印时纸张有褶皱,导致角点提取不准。记住:标定板一定要平整!最好贴在玻璃板或铝板上。
3.2 相机外参标定
内参搞定了,接下来是外参。外参描述的是相机在世界坐标系中的位置和朝向,也就是旋转矩阵 R 和平移向量 t。
为什么要标外参?
因为我们要把相机看到的物体,映射到统一的世界坐标系中。比如自动驾驶场景,车身上的相机需要知道“我相对于车体中心在哪儿”。
常用方法
- PnP 方法:已知 3D 点和对应的 2D 点,求解 R 和 t。OpenCV 的 solvePnP 可以直接用。
- 手眼标定:适用于机械臂上的相机,通过多次运动求解 AX=XB。
- 直接测量法:用全站仪或激光跟踪仪直接测量相机位置。精度高,但设备贵。
我的建议
在实际项目中,我建议先用 PnP 方法粗标定,再用优化算法精调。粗标定可以用 4-6 个特征点,精调时用 10 个以上。
# PnP 求解外参示例
object_points = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]], dtype=np.float32)
image_points = np.array([[100, 200], [300, 200], [100, 400], [300, 400]], dtype=np.float32)
ret, rvec, tvec = cv2.solvePnP(object_points, image_points, mtx, dist)
R, _ = cv2.Rodrigues(rvec) # 旋转向量转旋转矩阵
print("旋转矩阵:\n", R)
print("平移向量:\n", tvec)
3.3 雷达与相机联合标定原理
这是融合算法的核心前提。雷达和相机各自有坐标系,我们需要找到一个变换矩阵,把雷达点云投影到图像上。
联合标定的本质
说白了,就是求解一个 4x4 的刚体变换矩阵 T,使得:
P_cam = T * P_lidar
其中 P_cam 是相机坐标系下的点,P_lidar 是雷达坐标系下的点。
标定流程
- 在场景中放置标定物(通常是棋盘格或特制标定板)。
- 同时采集雷达点云和相机图像。
- 在点云中手动或自动提取标定物的特征点(比如角点)。
- 在图像中提取对应的特征点。
- 用最小二乘法或 ICP 算法求解变换矩阵。
关键点:雷达点云是稀疏的,很难直接看到棋盘格的角点。我常用的方法是:在标定板边缘贴反光条,这样雷达能清晰检测到边缘,再结合图像中的边缘进行匹配。
优化目标函数
min ∑ || P_img_proj - P_img_detected ||^2
其中 P_img_proj 是雷达点投影到图像上的像素坐标,P_img_detected 是图像中检测到的对应点。
我曾经踩过的坑:有一次联合标定结果总是不收敛,检查发现是雷达和相机的采集时间不同步。雷达是 10Hz,相机是 30Hz,导致点云和图像对应的是不同时刻的场景。后来加了硬件同步触发器才解决。
3.4 标定板设计
标定板看似简单,其实门道不少。一个好的标定板能省去很多后期处理的工作。
设计原则
- 尺寸合适:标定板在图像中要占 1/3 到 1/2 的视野。太小了角点提取不准,太大了容易出视野。
- 材质平整:亚克力板或铝板最佳。纸板容易变形,不推荐。
- 图案清晰:棋盘格的黑白对比度要高。我习惯用哑光打印,避免反光。
- 雷达可见性:如果用于联合标定,标定板表面要能反射雷达波。可以在表面贴金属箔或使用特制材料。
常见标定板类型
| 类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 棋盘格 | 相机内参标定 | 角点提取简单 | 雷达不可见 |
| 圆点阵列 | 相机外参标定 | 中心点定位精度高 | 需要检测圆 |
| 反光条棋盘格 | 雷达-相机联合标定 | 雷达和相机都能看到 | 制作成本高 |
| 3D 立体标定板 | 高精度联合标定 | 提供深度信息 | 体积大,携带不便 |
我的推荐
对于初学者,我建议先用标准的 9x6 棋盘格做内参标定。联合标定时,可以买现成的反光标定板,或者自己用铝板加反光贴纸制作。别在标定板上省钱,它直接影响整个系统的精度。
小技巧:标定板背面可以贴磁铁,方便固定在金属表面。我在实验室里就是这么干的,省去了每次调整支架的麻烦。
好了,标定这部分就讲到这里。下一章咱们聊聊数据预处理——如何把原始数据变成可用的特征。标定是基础,基础不牢,地动山摇。希望各位在实际项目中,能把这些原理用起来。