3、多模态数据预处理:图像去畸变与校正、点云滤波与降采样、时间戳同步与空间对齐(外参标定基础)

各位同学,欢迎来到第三章。

上一章我们聊了BEV感知的整体架构,很多同学跃跃欲试,想直接上模型。别急,数据预处理这关过不了,后面全是白搭。我见过太多项目,模型结构改得花里胡哨,结果因为图像没校正、点云没对齐,最终精度就是上不去。

说白了,多模态融合的第一步,不是算法,是“对齐”。

3.1 图像去畸变与校正

摄像头拍出来的照片,天生就是变形的。尤其是鱼眼镜头,边缘的楼房都是弯的。你想想看,如果拿这种图去跟激光雷达的点云做融合,那位置肯定对不上。

畸变的来源主要有两种:

  • 径向畸变:光线通过透镜时,边缘弯曲得厉害。说白了就是“桶形”或“枕形”变形。
  • 切向畸变:镜头和成像平面不平行,导致图像倾斜。这个在量产车上比较常见,因为装配公差很难避免。

去畸变的原理其实不复杂。我们用一个数学模型来描述畸变:

// 径向畸变模型
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]

其中 k1、k2、k3 是径向畸变系数,p1、p2 是切向畸变系数。这些参数需要通过标定得到。

我的经验: 在实际项目中,我习惯用棋盘格标定板,拍20-30张不同角度的照片,然后用OpenCV的 calibrateCamera() 函数。注意,标定板一定要平整,我曾经因为标定板有点褶皱,导致去畸变后图像边缘还是有残留变形,排查了两天才发现。

校正完成后,图像就变成了“针孔模型”下的理想图像。这时候,像素点和真实世界的对应关系才是线性的。

3.2 点云滤波与降采样

激光雷达一秒钟能扫几十万甚至上百万个点。这么多点,直接扔给模型,计算量太大了。而且很多点是噪声,比如地面上的灰尘、空气中的雨滴。

点云滤波,说白了就是“去噪”。常用的方法有:

  • 直通滤波:根据距离、高度、角度直接切掉。比如我只关心前方50米内的障碍物,那50米以外的点直接扔掉。
  • 统计滤波:计算每个点周围邻居的平均距离,如果某个点离邻居太远,就认为是离群点,删掉。这个对去除“飞点”特别有效。
  • 半径滤波:设定一个半径,如果这个半径内点数太少,就删掉。适合去除稀疏的噪声点。

降采样,则是为了减少数据量。最常用的是体素滤波(Voxel Grid Filter):

// 体素滤波伪代码
1. 设定体素大小,比如 (0.1m, 0.1m, 0.1m)
2. 将点云空间划分成一个个小立方体
3. 每个立方体内,只保留一个点(通常是重心点或中心点)
4. 其他点全部丢弃
注意: 体素大小直接影响感知精度。设得太小,降采样效果不明显;设得太大,会丢失细节。我个人的习惯是,对于高速场景(如高速公路),体素可以设到0.2m;对于城区场景,建议0.1m甚至更小。

嗯,这里还要提一句。点云滤波和降采样的顺序,我建议先滤波后降采样。因为噪声点如果不先去掉,降采样时可能会把噪声点当成“代表点”保留下来,那就尴尬了。

3.3 时间戳同步

摄像头和激光雷达的采集频率不一样。摄像头通常是20-30fps,激光雷达是10-20fps。而且,它们触发采集的时刻也不一样。

你想想看,如果图像是t1时刻拍的,点云是t2时刻扫的,而车在这段时间里移动了,那融合出来的结果肯定是“重影”的。

时间戳同步,就是要解决这个问题。常见做法有两种:

  • 硬同步:通过硬件触发信号,让摄像头和激光雷达在同一时刻开始采集。这个精度最高,但需要硬件支持。
  • 软同步:在软件层面,找到时间戳最接近的图像和点云帧,然后做插值或补偿。

软同步的代码实现大致如下:

def sync_sensor_data(image_list, lidar_list, max_time_diff=0.05):
    """
    image_list: [(timestamp, image), ...]
    lidar_list: [(timestamp, pointcloud), ...]
    """
    synced_pairs = []
    i, j = 0, 0
    while i < len(image_list) and j < len(lidar_list):
        img_ts = image_list[i][0]
        lidar_ts = lidar_list[j][0]
        diff = abs(img_ts - lidar_ts)
        
        if diff < max_time_diff:
            synced_pairs.append((image_list[i], lidar_list[j]))
            i += 1
            j += 1
        elif img_ts < lidar_ts:
            i += 1
        else:
            j += 1
    return synced_pairs
避坑指南: 我曾经遇到过一个坑,摄像头的时间戳是系统时间,激光雷达的时间戳是GPS时间,两者差了整整1秒。排查了好久才发现是时间基准不一致。所以,同步前一定要确认所有传感器的时间戳都是同一个时钟源。

3.4 空间对齐与外参标定基础

时间对齐了,空间还没对齐。摄像头看到的是像素坐标,激光雷达看到的是三维空间坐标。要把它们融合,就得知道摄像头和激光雷达之间的相对位置和姿态。

这个相对关系,就是外参。外参包括:

  • 旋转矩阵 R:3x3的矩阵,描述两个传感器之间的旋转关系。
  • 平移向量 t:3x1的向量,描述两个传感器之间的位移。

空间对齐的公式很简单:

// 将激光雷达点云投影到图像平面
1. 将点云从激光雷达坐标系转换到摄像头坐标系:
   P_cam = R * P_lidar + t

2. 将摄像头坐标系下的点投影到图像平面:
   u = f_x * (x_cam / z_cam) + c_x
   v = f_y * (y_cam / z_cam) + c_y

其中 f_x、f_y 是摄像头内参,c_x、c_y 是主点坐标。

外参标定,就是求解 R 和 t 的过程。常用的方法有:

  • 靶标法:在场景中放置标定板(如棋盘格),同时被摄像头和激光雷达看到。然后通过匹配角点来求解外参。
  • 手眼标定法:如果传感器安装在机械臂或可移动平台上,可以通过多次移动来求解。
  • 在线标定:在车辆行驶过程中,利用场景中的特征(如车道线、杆状物)自动优化外参。
我的建议: 对于量产项目,我建议先做一次离线精标,然后配合在线标定做微调。因为车辆在使用过程中,振动、温度变化都可能导致外参漂移。我曾经见过一个项目,出厂时标定得好好的,跑了两个月后,投影误差从2个像素漂到了15个像素,最后不得不召回。

好了,这一章的内容就到这里。数据预处理虽然繁琐,但它是多模态融合的基石。下一章,我们会正式进入BEV特征提取环节,看看如何把图像和点云的特征统一到BEV空间中去。