3、传感器数据预处理:激光雷达点云滤波、坐标变换、时间同步、数据增强

好,咱们进入正题。传感器数据预处理,说白了就是给原始数据「洗个澡」。你想想看,激光雷达直接吐出来的点云,那叫一个乱——有噪声、有离群点、坐标还不统一。我刚开始做自动驾驶感知那会儿,就吃过这个亏。有一次直接把原始点云丢进网络,结果模型训练出来跟瞎了一样。后来才明白,预处理做不好,后面全是白搭。

3.1 点云滤波——把脏东西筛掉

激光雷达点云里,最常见的噪声就是「飘在空中的孤点」。这些点可能是多路径反射造成的,也可能是传感器本身的测量误差。我的习惯是,上来先做两步滤波。

第一步:直通滤波(PassThrough Filter)

说白了就是切一刀。根据场景需求,把点云限制在某个空间范围内。比如我们只关心车前50米、左右20米、高度-2到4米的范围。超出这个范围的,直接扔掉。这样既能减少计算量,又能排除远处的无效点。

// 伪代码示例
PassThrough filter;
filter.setInputCloud(cloud);
filter.setFilterFieldName("z");  // 高度方向
filter.setFilterLimits(-2.0, 4.0);
filter.filter(*filtered_cloud);

第二步:统计滤波(StatisticalOutlierRemoval)

这个我特别喜欢用。它的原理很简单:计算每个点周围K个邻居的平均距离。如果某个点离邻居太远,那它大概率是个离群点。我在项目中遇到过,雨天的时候激光雷达会打出很多「雨滴点」,用统计滤波一筛,效果立竿见影。

核心参数:

  • K近邻数:一般取50-100,点云密度高就取大点
  • 标准差倍数:通常设为1.0-2.0,越大越宽松

我的小技巧: 如果你发现滤波后点云太稀疏,别急着调参数。先看看是不是场景本身就有稀疏区域(比如远处)。我一般会分区域设置不同的滤波参数,近处严格,远处宽松。

3.2 坐标变换——让所有传感器说同一种语言

自动驾驶车上通常有多个传感器:激光雷达、摄像头、毫米波雷达、IMU...每个传感器都有自己的坐标系。你想想看,如果不做坐标变换,激光雷达说「前方5米」,摄像头说「图像第300列」,这俩怎么对齐?

常见的坐标系:

坐标系 原点位置 用途
激光雷达坐标系 激光雷达中心 点云原始数据
相机坐标系 相机光心 图像投影
车辆坐标系 后轴中心 统一基准
世界坐标系 地图原点 全局定位

坐标变换的核心就四个字:刚体变换。说白了就是旋转加平移。每个传感器安装时,我们都会标定出一个外参矩阵(4x4的齐次变换矩阵)。

// 点云从激光雷达坐标系变换到车辆坐标系
Eigen::Matrix4f lidar_to_vehicle;
// 这个矩阵来自标定文件
pcl::transformPointCloud(*lidar_cloud, *vehicle_cloud, lidar_to_vehicle);

我曾经踩过的坑: 坐标变换的顺序不能搞反!先旋转后平移,这是铁律。有一次我写代码时把顺序弄反了,结果点云全部飞到天上去了。排查了整整一天才发现是这个问题。嗯,从那以后我每次写变换都会在注释里标清楚顺序。

3.3 时间同步——别让数据「穿越」

这个问题其实挺头疼的。激光雷达一般是10Hz,相机可能是20Hz或30Hz,IMU能到100Hz以上。每个传感器都有自己的时间戳,而且不同传感器的时钟可能还有漂移。

常用的同步策略:

  1. 硬同步:通过GPS的PPS信号或者IEEE 1588协议,让所有传感器共享同一个时钟。这是最理想的方式,但硬件成本高。
  2. 软同步:以激光雷达的时间戳为基准,找到最近的相机帧。我一般用「最近邻插值」——找时间差最小的那一帧。
  3. 插值同步:对于IMU这种高频数据,可以用线性插值来推算某个时刻的数值。

我的经验: 在实际工程中,硬同步和软同步要结合着用。硬同步保证大方向不偏,软同步做精细对齐。我见过有些团队只做软同步,结果车辆高速行驶时,点云和图像能差出半米多。这要是做融合,后果可想而知。

3.4 数据增强——让模型见多识广

Occupancy Network的训练,最怕的就是数据不够多样。你想想看,如果训练集里全是晴天白天的数据,模型到了雨天晚上肯定抓瞎。数据增强就是人为制造「困难场景」。

点云数据增强的常用方法:

  • 随机旋转:绕Z轴旋转±30度,模拟车辆转弯时的视角变化
  • 随机缩放:整体缩放0.9-1.1倍,模拟不同距离的感知误差
  • 随机丢弃:随机丢弃5%-20%的点,模拟遮挡或传感器故障
  • 高斯噪声:给每个点加一点随机偏移,模拟测量噪声
  • 混合增强:把两帧点云混合在一起,模拟动态场景
// 数据增强示例:随机旋转+丢弃
void augmentPointCloud(pcl::PointCloud<pcl::PointXYZ>& cloud) {
    // 随机旋转
    float angle = (rand() % 60 - 30) * M_PI / 180.0;
    Eigen::Affine3f transform = Eigen::Affine3f::Identity();
    transform.rotate(Eigen::AngleAxisf(angle, Eigen::Vector3f::UnitZ()));
    pcl::transformPointCloud(cloud, cloud, transform);
    
    // 随机丢弃
    float drop_ratio = 0.1 + (rand() % 10) * 0.01;  // 10%-20%
    // 实际实现时用随机采样
}

我的建议: 数据增强不是越多越好。我见过有人把点云旋转了90度,结果车顶的激光雷达视角完全变了,模型反而学坏了。增强要符合物理规律——比如旋转角度别太大,噪声幅度别太离谱。说白了,你得让增强后的数据看起来「还像真的」。

嗯,预处理这块就这些。你可能会觉得这些步骤有点繁琐,但相信我,把这一步做好,后面的模型训练会省心很多。我每次做新项目,花在预处理上的时间至少占30%。磨刀不误砍柴工嘛。