4. 多相机标定:棋盘格标定、联合标定、外参优化、标定结果验证
多相机标定,说白了就是给车上的每只「眼睛」配一副合适的眼镜,再让它们学会协同工作。
我刚开始做量产项目时,觉得标定嘛,跑个开源工具就完事了。结果第一次路测,BEV 视角下的车道线直接错位了半米多。嗯,从那以后我再也不敢轻视标定这步了。
这一章,我们聊聊多相机标定的四个核心环节:棋盘格标定、联合标定、外参优化、标定结果验证。
4.1 棋盘格标定:单相机内参的获取
棋盘格标定,目的是获取每个相机的内参(焦距、主点、畸变系数)。
我个人习惯用 张正友标定法。你想想看,只需要打印一张棋盘格,在不同角度拍几张照片,就能解算出内参,非常实用。
核心步骤:
- 采集图像:至少 15-20 张不同姿态的棋盘格图像。注意光照均匀,棋盘格占画面 1/3 以上。
- 角点检测:使用 OpenCV 的
findChessboardCorners函数。 - 标定计算:调用
calibrateCamera得到内参矩阵和畸变系数。 - 重投影误差检查:误差应小于 0.5 像素。
关键参数:
| 参数 | 含义 | 典型值 |
|---|---|---|
| fx, fy | 焦距(像素单位) | 500-2000 |
| cx, cy | 主点坐标 | 图像中心附近 |
| k1, k2, k3 | 径向畸变系数 | ±0.1 以内 |
| p1, p2 | 切向畸变系数 | ±0.01 以内 |
# Python 示例:单相机标定
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)
避坑指南:我曾经遇到过棋盘格反光导致角点检测失败的情况。后来改用哑光纸打印,并在拍摄时避免强光直射,问题就解决了。
4.2 联合标定:多相机之间的外参对齐
单相机标定完成后,我们需要知道每个相机相对于车体的位置和朝向。这就是联合标定的任务。
联合标定通常有两种方式:
- 基于标定场:在专用标定场地中,使用已知位置的特征点,解算相机外参。
- 基于自然特征:利用 SfM 或 SLAM 技术,从自然场景中提取特征点进行标定。
我个人更推荐基于标定场的方式,精度高且稳定。量产项目中,我们通常使用带有 AprilTag 或 ChArUco 标志的标定板。
联合标定的核心公式:
P_cam = R * P_world + T
其中 R 是旋转矩阵,T 是平移向量。我们需要解算出每个相机到车体坐标系的 R 和 T。
标定场要求:
- 地面平整,无明显坡度
- 标定板放置在车辆前后左右 4-8 米处
- 每个相机至少看到 3 个标定板
- 标定板位置精确测量(误差 < 1cm)
4.3 外参优化:让标定结果更精准
联合标定得到的外参,往往不是最优的。为什么?因为标定场中的测量误差、角点检测误差都会累积。
外参优化的目标,是让多相机在 BEV 空间中的投影一致性最大化。
我常用的优化方法:
- 重投影误差最小化:将标定板角点投影到 BEV 空间,最小化投影点与真实位置的误差。
- 光度一致性优化:利用多相机重叠区域的光度信息,优化外参。
- 基于车道线的优化:在结构化道路上,利用车道线作为自然特征进行微调。
# 外参优化示例(伪代码)
def optimize_extrinsics(initial_extrinsics, calibration_data):
# 定义损失函数:重投影误差
def loss_function(params):
total_error = 0
for cam_id, points_3d, points_2d in calibration_data:
# 根据当前外参投影
projected = project_points(points_3d, params[cam_id])
total_error += np.sum((projected - points_2d)**2)
return total_error
# 使用 Levenberg-Marquardt 优化
optimized_params = scipy.optimize.least_squares(
loss_function, initial_extrinsics, method='lm'
)
return optimized_params
注意:外参优化时,不要一次性调整所有参数。我建议先优化旋转矩阵,再优化平移向量。否则容易陷入局部最优。
4.4 标定结果验证:如何判断标定是否合格
标定做完了,怎么知道结果好不好?
我一般从三个维度验证:
- 重投影误差:单相机重投影误差 < 0.5 像素,联合标定重投影误差 < 2 像素。
- BEV 拼接一致性:将多相机图像投影到 BEV 空间,检查重叠区域的车道线、路沿是否对齐。
- 距离测量精度:在 BEV 空间中测量已知距离的物体,误差应 < 3%。
验证流程:
- 拍摄一组验证图像(与标定图像不同)
- 运行标定参数,生成 BEV 图像
- 人工检查 BEV 图像中的对齐情况
- 使用激光雷达或 RTK 作为真值,量化误差
我记得有一次,重投影误差只有 0.3 像素,但 BEV 拼接效果很差。后来发现是标定板摆放位置太集中,导致外参在远距离区域外推误差大。所以,验证时一定要覆盖不同距离的场景。
4.5 知识体系总览
下面这张图,概括了多相机标定的完整流程:
个人经验:标定不是一次性工作。车辆使用一段时间后,由于振动、温度变化,外参可能会漂移。我建议每 3-6 个月重新标定一次,或者在每次 OTA 升级后做一次快速验证。
多相机标定,说白了就是给感知系统打好地基。地基不稳,上层建筑再漂亮也没用。希望这一章的内容,能帮你少走一些我当年走过的弯路。