传感器基础与标定:相机模型与内参标定、激光雷达原理与外参标定、相机-激光雷达联合标定

做SLAM这么多年,我越来越觉得一个道理:标定做不好,后面全是白忙活。你想想看,传感器就像机器人的眼睛,如果眼睛本身就有散光、斜视,那后面再怎么优化算法也救不回来。这一章,咱们就把相机和激光雷达的底细摸清楚,顺便把标定这件事彻底搞定。

核心观点:传感器标定是SLAM系统的"地基工程"。地基歪了,楼盖得再高也得塌。我见过太多团队花几个月调算法,最后发现是标定参数差了0.1个像素——嗯,这种坑我踩过不止一次。

1. 相机模型:从针孔到畸变

相机模型说白了就是回答一个问题:三维世界里的一个点,是怎么跑到二维图像上的?

最经典的当然是针孔模型。它的数学表达很简单:

// 世界坐标系到像素坐标的映射
// [u, v, 1]^T = K * [R|t] * [X, Y, Z, 1]^T

// 内参矩阵 K:
// [fx,  0, cx]
// [ 0, fy, cy]
// [ 0,  0,  1]

这里fx, fy是焦距(单位是像素),cx, cy是光心偏移。我习惯把内参理解为"相机自己的性格"——每台相机出厂时都不一样,必须通过标定来摸清它的脾气。

但现实世界没有完美的针孔。镜头会引入畸变,主要有两种:

  • 径向畸变:镜头边缘的"桶形"或"枕形"变形。说白了就是直线变弯了。
  • 切向畸变:镜头和成像平面不平行导致的偏移。

畸变矫正公式长这样:

// 径向畸变
x_distorted = x * (1 + k1*r^2 + k2*r^4 + k3*r^6)
y_distorted = y * (1 + k1*r^2 + k2*r^4 + k3*r^6)

// 切向畸变
x_distorted = x + (2*p1*x*y + p2*(r^2 + 2*x^2))
y_distorted = y + (p1*(r^2 + 2*y^2) + 2*p2*x*y)

我的经验:标定相机时,棋盘格一定要贴平。我曾经用一张皱巴巴的A4纸标定,结果算出来的畸变参数全是错的,跑VIO时地图直接飘到天上去了。后来换了亚克力板棋盘格,一次搞定。

2. 内参标定:张正友法的实战要点

说到内参标定,绕不开张正友法。这个方法的核心思路是:拍不同角度的棋盘格照片,然后利用棋盘格的几何约束来求解内参。

具体步骤我总结为四步:

  1. 采集图像:至少15-20张,覆盖不同角度和距离
  2. 角点检测:找到棋盘格的内角点
  3. 求解单应性矩阵:每张图都有一个单应性矩阵
  4. 优化内参:用非线性最小二乘(比如Levenberg-Marquardt)迭代优化

我用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 = []  # 3D点
imgpoints = []  # 2D点

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
)

避坑指南:我曾经用手机拍棋盘格标定,结果发现照片分辨率太高(4000x3000),角点检测慢得要命。后来把图像缩放到640x480再标定,速度提升10倍,精度几乎没损失。另外,棋盘格不要放在纯色背景上,否则角点检测会失败。

3. 激光雷达原理:TOF与机械式扫描

激光雷达(LiDAR)的原理其实不复杂:发射激光,接收反射光,算时间差,得距离。目前主流的有两种:

  • 机械式LiDAR:通过旋转电机带动激光器旋转扫描。典型代表是Velodyne的16线、32线、64线。优点是视场角大(360°),缺点是机械结构容易坏。
  • 固态LiDAR:没有旋转部件,用光学相控阵或MEMS微镜来偏转激光束。优点是体积小、寿命长,但视场角通常有限。

LiDAR输出的数据格式一般是点云,每个点包含(x, y, z, intensity)。其中intensity是反射强度,可以用来区分不同材质——比如车道线反射强,柏油路反射弱。

我习惯把LiDAR数据组织成这样的结构:

// 一帧点云的数据结构
struct Point {
    float x, y, z;      // 三维坐标(米)
    float intensity;    // 反射强度(0-255)
    uint16_t ring;      // 线束编号(0-15 对于16线LiDAR)
    double timestamp;   // 时间戳(秒)
};

一个小技巧:处理LiDAR数据时,我通常会先做去畸变。因为机械式LiDAR在扫描过程中,车辆本身也在运动,导致一帧点云内部存在运动畸变。用IMU数据或者匀速运动模型可以矫正这个问题。

4. 外参标定:LiDAR到IMU/车体的变换

外参标定要回答的问题是:激光雷达装在车上的哪个位置?朝向如何?

这个变换矩阵通常是一个4x4的齐次变换矩阵:

T_lidar_to_vehicle = [R | t]
                     [0 | 1]

其中R是旋转矩阵(3x3),t是平移向量(3x1)。

标定方法我推荐两种:

  • 手工测量法:用卷尺量安装位置,用角度尺量朝向。简单粗暴,但精度有限(厘米级)。
  • 基于特征的方法:在LiDAR前方放置标定物(比如立方体或平面),通过点云拟合来求解外参。精度可以达到毫米级。

我实际项目中用得最多的是平面拟合标定法

// 伪代码:基于平面约束的外参标定
// 1. 在LiDAR点云中提取地面平面
// 2. 在IMU/车体坐标系中已知地面方程
// 3. 通过平面法向量对齐求解旋转
// 4. 通过平面距离约束求解平移

// 平面方程:ax + by + cz + d = 0
// 法向量 n = [a, b, c]^T
// 距离 d

我曾经踩过的坑:有一次标定LiDAR到IMU的外参,怎么算都对不上。折腾了两天,最后发现是IMU的安装支架有0.5度的倾斜。从那以后,我每次标定前都会先用水平仪检查安装面是否水平。这个细节,真的能省你三天调试时间。

5. 相机-激光雷达联合标定:让视觉和激光"对齐"

这是融合SLAM的核心环节。说白了就是:找到相机像素和LiDAR点云之间的对应关系

联合标定的目标是求解一个4x4的变换矩阵T_cam_to_lidar,把LiDAR点投影到相机图像上:

// LiDAR点投影到图像
// 1. 将LiDAR点从LiDAR坐标系变换到相机坐标系
// 2. 用相机内参投影到像素平面

// 数学表达:
// [u, v, 1]^T = K * T_cam_to_lidar * [X_lidar, Y_lidar, Z_lidar, 1]^T

标定方法我推荐基于棋盘格的方法

  1. 在LiDAR和相机共同视野内放置棋盘格
  2. 相机检测棋盘格角点
  3. LiDAR检测棋盘格平面上的点云
  4. 通过点-面对应关系求解外参

具体实现时,我习惯用Autoware的标定工具包,或者自己写一个基于PCL和OpenCV的标定程序:

// 联合标定的核心步骤
// 1. 从LiDAR点云中提取棋盘格平面上的点
// 2. 从相机图像中提取棋盘格角点
// 3. 构建优化问题:最小化重投影误差

// 优化目标函数:
// min Σ || pi - K * T * Pi ||^2
// 其中 pi 是图像角点,Pi 是LiDAR点,T是待求外参

关键指标:标定完成后,我会用重投影误差来评估标定质量。好的标定结果,重投影误差应该在1-2个像素以内。如果超过5个像素,说明标定有问题,需要重新来。

6. 知识体系总览

这一章的内容比较多,我画了一张图帮你理清思路:

传感器基础与标定知识体系 相机模型与内参标定 • 针孔模型 • 畸变模型(径向+切向) • 内参矩阵 K • 张正友标定法 • 棋盘格角点检测 • 非线性优化 ⚠ 关键输出 fx, fy, cx, cy k1, k2, p1, p2, k3 激光雷达原理与外参标定 • TOF测距原理 • 机械式 vs 固态 • 点云数据结构 • 运动畸变矫正 • 外参标定方法 • 平面拟合标定 ⚠ 关键输出 T_lidar_to_vehicle R, t (4x4齐次矩阵) 相机-激光雷达联合标定 • 点-面对应关系 • 重投影误差优化 • 棋盘格联合标定 • Autoware工具包 • PCL + OpenCV • 重投影误差评估 ⚠ 关键输出 T_cam_to_lidar 重投影误差 < 2px 内参输入 外参输入 标定是SLAM系统的"地基工程" — 精度决定上限

这张图把三个模块的关系理清楚了。相机内参和LiDAR外参是联合标定的输入,联合标定又把两者融合到一起。说白了,这就是一个层层递进的过程。

我的建议:初学者不要一上来就搞联合标定。先把相机内参标定做到重投影误差小于0.5像素,再把LiDAR外参标定做到毫米级精度。这两步做好了,联合标定就是水到渠成的事。

好了,这一章的内容就到这里。传感器标定这件事,说难不难,说简单也不简单。关键是要理解原理、掌握方法、注意细节。下一章我们会进入SLAM的核心——前端里程计,到时候这些标定好的传感器就要真正派上用场了。


专注资料整理