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 空间中的投影一致性最大化。

我常用的优化方法:

  1. 重投影误差最小化:将标定板角点投影到 BEV 空间,最小化投影点与真实位置的误差。
  2. 光度一致性优化:利用多相机重叠区域的光度信息,优化外参。
  3. 基于车道线的优化:在结构化道路上,利用车道线作为自然特征进行微调。
# 外参优化示例(伪代码)
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%。

验证流程:

  1. 拍摄一组验证图像(与标定图像不同)
  2. 运行标定参数,生成 BEV 图像
  3. 人工检查 BEV 图像中的对齐情况
  4. 使用激光雷达或 RTK 作为真值,量化误差

我记得有一次,重投影误差只有 0.3 像素,但 BEV 拼接效果很差。后来发现是标定板摆放位置太集中,导致外参在远距离区域外推误差大。所以,验证时一定要覆盖不同距离的场景。

4.5 知识体系总览

下面这张图,概括了多相机标定的完整流程:

多相机标定知识体系 棋盘格标定 单相机内参获取 联合标定 多相机外参对齐 外参优化 精度提升 结果验证 质量评估 子步骤 • 图像采集(15-20张) • 角点检测 • 内参解算 • 重投影误差检查 子步骤 • 标定场搭建 • AprilTag/ChArUco • 外参解算 • 坐标系对齐 子步骤 • 重投影误差最小化 • 光度一致性优化 • 车道线微调 • LM优化算法 子步骤 • BEV拼接 • 距离测量 • 真值对比 最终输出 每个相机的内参 + 外参(R, T)→ 可用于 BEV 感知 图:多相机标定知识体系总览 关键指标:重投影误差 < 0.5px | BEV拼接误差 < 2px | 距离测量误差 < 3%

个人经验:标定不是一次性工作。车辆使用一段时间后,由于振动、温度变化,外参可能会漂移。我建议每 3-6 个月重新标定一次,或者在每次 OTA 升级后做一次快速验证。

多相机标定,说白了就是给感知系统打好地基。地基不稳,上层建筑再漂亮也没用。希望这一章的内容,能帮你少走一些我当年走过的弯路。

可可精品资料库