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 解耦了。

具体步骤

  1. 打印一张棋盘格标定板,贴在平板上。
  2. 用相机从不同角度拍摄 10-20 张照片。
  3. 对每张照片,提取角点坐标。
  4. 用最小二乘法求解内参初值。
  5. 考虑径向畸变(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 是雷达坐标系下的点。

标定流程

  1. 在场景中放置标定物(通常是棋盘格或特制标定板)。
  2. 同时采集雷达点云和相机图像。
  3. 在点云中手动或自动提取标定物的特征点(比如角点)。
  4. 在图像中提取对应的特征点。
  5. 用最小二乘法或 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 棋盘格做内参标定。联合标定时,可以买现成的反光标定板,或者自己用铝板加反光贴纸制作。别在标定板上省钱,它直接影响整个系统的精度。

小技巧:标定板背面可以贴磁铁,方便固定在金属表面。我在实验室里就是这么干的,省去了每次调整支架的麻烦。

好了,标定这部分就讲到这里。下一章咱们聊聊数据预处理——如何把原始数据变成可用的特征。标定是基础,基础不牢,地动山摇。希望各位在实际项目中,能把这些原理用起来。