第4章:传感器标定基础
4.1 摄像头内参标定——张正友标定法
说到传感器标定,我第一个想聊的就是摄像头内参标定。你想想看,摄像头装上车以后,它看到的图像是扭曲的,像素点和真实世界的点之间有个映射关系。这个关系搞不清楚,后面的感知算法全是白搭。
张正友标定法,说白了就是用一张棋盘格照片,通过数学计算把摄像头的内参矩阵和畸变系数算出来。这个方法我用了好多年,稳定、实用,而且不需要昂贵的设备。
核心原理
摄像头成像模型可以简化为:
s * [u, v, 1]^T = K * [R|t] * [X, Y, Z, 1]^T
其中K就是内参矩阵:
K = [fx, 0, cx;
0, fy, cy;
0, 0, 1]
fx、fy是焦距参数,cx、cy是主点偏移。这些参数决定了像素坐标和相机坐标之间的转换关系。
张正友的方法巧妙在哪呢?它假设标定板是平面(Z=0),这样就把三维问题降成了二维问题。然后通过拍摄不同角度的棋盘格照片,利用单应性矩阵来求解内参。
关键步骤:
- 打印一张棋盘格标定板,贴在平面上
- 用摄像头从不同角度拍摄15-20张照片
- 检测每张照片中的角点坐标
- 利用角点坐标和已知的棋盘格尺寸,计算单应性矩阵
- 通过多张照片的单应性矩阵,求解内参矩阵
- 用最小二乘法优化畸变系数
实际操作中的坑
我在项目中遇到过最头疼的问题,就是标定板不平整。有一次在实验室里标定得好好的,换到车上就全偏了。后来发现是标定板贴在车门上,车门本身有弧度。嗯,这里要注意:标定板一定要贴在绝对平整的表面上。
避坑指南:
- 标定板不要太小,建议A3大小以上
- 照片要覆盖视野的各个角落
- 避免纯正面拍摄,要有倾斜角度
- 光照要均匀,不要有反光
代码示例
用OpenCV实现张正友标定,其实就几行代码:
import cv2
import numpy as np
# 准备棋盘格角点坐标
pattern_size = (9, 6) # 内角点数量
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)
# 存储所有照片的角点
objpoints = [] # 世界坐标系中的点
imgpoints = [] # 图像坐标系中的点
# 遍历所有标定照片
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:
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)
标定完成后,记得用重投影误差来评估标定质量。我一般要求重投影误差小于0.5像素,超过这个值就要重新拍了。
4.2 激光雷达与摄像头联合标定原理
摄像头和激光雷达,一个给颜色信息,一个给深度信息。但这两个传感器的坐标系不一样,得把它们对齐。联合标定要解决的就是这个问题——找到激光雷达坐标系到摄像头坐标系之间的旋转矩阵R和平移向量t。
标定原理
联合标定的核心公式很简单:
P_cam = R * P_lidar + t
P_lidar是激光雷达坐标系下的3D点,P_cam是相机坐标系下的3D点。我们需要找到R和t,让激光点云投影到图像上时,和图像中的对应物体重合。
具体怎么做呢?我习惯用标定板来做。在标定板上放几个特征点,激光雷达能扫到这些点的距离,摄像头能拍到这些点的像素位置。然后通过PnP算法求解R和t。
我的经验:
标定板最好用反光材料做,激光雷达的回波强度会更高,更容易检测到边缘。我曾经用普通纸板做过一次,结果激光点打在纸板上回波太弱,边缘检测不准,标定结果差了5个像素。
标定流程
| 步骤 | 操作 | 注意事项 |
|---|---|---|
| 1 | 放置标定板 | 距离传感器5-10米,角度多样化 |
| 2 | 采集数据 | 同时记录激光点云和图像 |
| 3 | 提取特征 | 检测标定板在点云和图像中的位置 |
| 4 | 求解外参 | 使用最小二乘法或RANSAC |
| 5 | 验证结果 | 投影点云到图像,检查对齐精度 |
验证方法
标定完成后,怎么知道结果对不对?我一般用两种方法验证:
- 视觉验证:把激光点云投影到图像上,看边缘是否对齐。比如建筑物的边缘、路沿、车辆轮廓,这些特征在点云和图像中都很明显。
- 定量验证:计算投影误差。选几个特征点,手动标注它们在图像中的位置,和投影位置做对比。误差在2-3个像素以内就算合格。
投影验证代码:
def project_lidar_to_image(points_3d, R, t, K, dist_coeffs):
# 将激光点转换到相机坐标系
points_cam = (R @ points_3d.T + t.reshape(3,1)).T
# 只保留相机前方的点
mask = points_cam[:, 2] > 0
points_cam = points_cam[mask]
# 投影到图像平面
points_2d, _ = cv2.projectPoints(
points_cam, np.zeros(3), np.zeros(3), K, dist_coeffs
)
return points_2d.reshape(-1, 2)
常见问题
联合标定中我踩过最大的坑,是时间同步问题。激光雷达和摄像头的数据采集时间不一致,导致标定板在移动时,点云和图像对应不上。后来我加了一个硬件触发信号,让两个传感器同时采集,问题就解决了。
还有一个坑:标定板的尺寸要足够大。太小的话,激光雷达扫到的点太少,拟合出来的平面误差大。我建议标定板边长至少1米,这样激光雷达能扫到100个点以上。
重要提醒:
联合标定不是一次性的工作。传感器在车辆运行中可能会发生微小的位移,建议每3个月重新标定一次。如果车辆发生过碰撞或传感器拆装,必须立即重新标定。
好了,传感器标定基础就聊到这里。下一章我们会深入讨论激光雷达和摄像头的在线标定方法,以及如何在实车调试中快速验证标定结果。