4. 数据采集与标定

各位同学,欢迎来到第四章。这一章的内容,我敢说是整个感知联调里最「磨人」的一环。为什么这么说?因为数据不准,后面所有算法都是白搭。你想想看,传感器时间都对不上,相机拍的图和激光雷达打的点云根本不是同一时刻的场景,那还融合个啥?

我个人习惯把标定工作比作「给传感器做体检」。体检做不好,后面开再好的药方也没用。今天咱们就聊聊这三件事:时间同步、相机内参标定、以及相机和激光雷达的联合标定。

核心观点:标定不是一次性的工作,而是贯穿整个系统生命周期的持续任务。每次更换传感器、甚至环境温度剧烈变化后,都建议重新走一遍标定流程。

第四章 数据采集与标定 知识体系 传感器时间同步 硬件同步(PPS/GPS) 软件同步(时间戳对齐) 插值与外推策略 相机内参标定 张正友标定法 畸变系数(径向/切向) 内参矩阵 K 联合标定 外参矩阵(R|t) 点云投影到图像 手动/自动标定 输出:时间对齐后的数据 + 标定参数 用于后续感知融合算法

4.1 传感器时间同步——别让数据「穿越」

时间同步这事,说白了就是让所有传感器都「看」向同一个时刻。我在项目中遇到过最离谱的情况:相机帧率30Hz,激光雷达10Hz,IMU 100Hz。如果不做同步,你拿到的点云和图像可能差了100毫秒。100毫秒对于一辆时速60公里的车来说,意味着它已经跑了1.67米。你想想看,这融合出来的结果能准吗?

时间同步通常分两个层面:

  1. 硬件同步(硬同步):通过PPS(秒脉冲)或GPS授时,给所有传感器一个共同的时钟基准。这是最可靠的方式,延迟通常在微秒级。
  2. 软件同步(软同步):当硬件条件不具备时,靠时间戳对齐。比如激光雷达每帧有一个时间戳,相机也有,我们找最近邻的配对。

我的经验:能上硬同步就别用软同步。软同步在低速场景下勉强能用,但一旦遇到急加速或颠簸路面,误差会大到让你怀疑人生。我曾经在一个项目中偷懒只做了软同步,结果车辆过减速带时,点云和图像直接错位了半个车身。

软同步的常见做法是「插值」或「外推」。举个例子,激光雷达在t1时刻有一帧,相机在t2时刻有一帧,t2比t1晚了20ms。我们可以根据IMU数据,把激光雷达的点云「推」到t2时刻的位置。代码实现大致如下:

// 伪代码:基于IMU的激光点云时间补偿
void compensateLidarWithImu(LidarFrame &frame, ImuData &imu, double target_timestamp) {
    double dt = target_timestamp - frame.timestamp;
    if (fabs(dt) < 0.001) return; // 时间差小于1ms,不做补偿
    
    for (auto &point : frame.points) {
        // 利用IMU的角速度和线速度进行运动补偿
        point.x += imu.vx * dt;
        point.y += imu.vy * dt;
        point.z += imu.vz * dt;
        // 旋转补偿略...
    }
    frame.timestamp = target_timestamp;
}

4.2 相机内参标定——把镜头「掰直」

相机内参标定,说白了就是搞清楚两件事:焦距和光心在哪,以及镜头畸变有多严重。每个镜头都有畸变,尤其是广角镜头,边缘的弯曲程度简直像哈哈镜。

最经典的方法是张正友标定法。你只需要打印一张棋盘格,从不同角度拍个十几张照片,然后让算法去算。嗯,这里要注意:棋盘格一定要平整,不要皱巴巴的。我见过有人拿A4纸打印的棋盘格,纸都卷边了,标定出来的结果自然一塌糊涂。

标定结果通常是一个内参矩阵K和畸变系数:

K = [fx,  0, cx;
      0, fy, cy;
      0,  0,  1]

畸变系数: [k1, k2, p1, p2, k3]
// k1,k2,k3 是径向畸变,p1,p2 是切向畸变

避坑指南:我曾经在户外强光下标定,结果棋盘格反光严重,角点检测总是失败。后来我换成了哑光材质的棋盘格,并且在阴天或室内均匀光照下进行标定。记住,光照均匀比光照强度更重要。

标定完成后,一定要做验证。怎么验证?把标定好的参数拿去矫正一张图,看看棋盘格的边缘是不是直线。如果还有弯曲,说明标定不够好,需要重新来。

4.3 相机与激光雷达联合标定——让「眼睛」和「耳朵」对齐

联合标定的目标,是求出一个外参矩阵(R|t),把激光雷达坐标系下的点云,投影到相机图像上。说白了,就是让激光雷达「看到」的物体,和相机「看到」的物体在空间上重合。

具体做法有很多种,我比较常用的是手动标定和自动标定两种:

  • 手动标定:找一个有明确边缘的标定物(比如棋盘格或三角架),在点云中手动选取几个点,再在图像中对应选取像素坐标,然后解算PnP问题。优点是直观,缺点是费时费力。
  • 自动标定:利用标定物的几何特征,自动检测点云中的平面和图像中的角点,然后优化求解。现在很多开源工具如lidar_camera_calibration都做得不错。

联合标定的结果,可以用一个投影公式来表示:

// 将激光雷达点云投影到图像平面
cv::Point2f projectLidarToCamera(const cv::Point3f &lidar_pt, 
                                  const cv::Mat &R, 
                                  const cv::Mat &t, 
                                  const cv::Mat &K) {
    // 1. 将激光雷达坐标转换到相机坐标系
    cv::Mat cam_pt = R * cv::Mat(lidar_pt) + t;
    // 2. 投影到像素平面
    float u = cam_pt.at<float>(0) * K.at<float>(0,0) / cam_pt.at<float>(2) + K.at<float>(0,2);
    float v = cam_pt.at<float>(1) * K.at<float>(1,1) / cam_pt.at<float>(2) + K.at<float>(1,2);
    return cv::Point2f(u, v);
}

验证方法:标定完成后,把点云投影到图像上,看看点云的边缘和图像的边缘是否对齐。比如,路沿在点云中是一条线,投影到图像上也应该和图像中的路沿重合。如果偏差超过3个像素,建议重新标定。

我记得有一次在园区里做联合标定,怎么调都差那么几个像素。后来发现是激光雷达的安装支架有轻微变形。你想想看,支架变形1毫米,在50米外就是几十厘米的误差。所以,标定前一定要检查机械安装的刚性。

4.4 标定流程总结

说了这么多,咱们把整个流程串一下。我个人习惯按这个顺序来:

  1. 硬件准备:固定传感器,确保机械结构稳定。连接PPS/GPS做硬同步。
  2. 相机内参标定:拍摄棋盘格,用OpenCV或MATLAB标定工具箱。保存内参K和畸变系数。
  3. 联合标定数据采集:在场景中放置标定物,同时录制激光雷达和相机数据。注意标定物要在两者视野内。
  4. 联合标定求解:用工具或手动方法求解外参R|t。
  5. 验证:投影点云到图像,检查对齐精度。如果不满意,回到第3步。
步骤 输入 输出 常见问题
时间同步 各传感器原始数据 时间对齐后的数据帧 时间戳精度不够,建议用硬件触发
相机内参标定 棋盘格图像 内参K + 畸变系数 棋盘格不平整、光照不均
联合标定 点云 + 图像 + 内参 外参R|t 标定物不在共同视野、机械变形

一个小技巧:标定完成后,把外参和内参写到一个YAML文件里,方便后续读取。我习惯的格式是:

camera:
  K: [fx, 0, cx, 0, fy, cy, 0, 0, 1]
  dist: [k1, k2, p1, p2, k3]
lidar_to_camera:
  R: [r11, r12, r13, r21, r22, r23, r31, r32, r33]
  t: [tx, ty, tz]

好了,这一章的内容就到这里。标定工作虽然繁琐,但它是整个感知系统的基石。你花在标定上的每一分钟,都会在后面的融合和检测环节里加倍回报给你。记住,数据质量决定算法上限。

可可精品资料库