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以上。每个传感器都有自己的时间戳,而且不同传感器的时钟可能还有漂移。
常用的同步策略:
- 硬同步:通过GPS的PPS信号或者IEEE 1588协议,让所有传感器共享同一个时钟。这是最理想的方式,但硬件成本高。
- 软同步:以激光雷达的时间戳为基准,找到最近的相机帧。我一般用「最近邻插值」——找时间差最小的那一帧。
- 插值同步:对于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%。磨刀不误砍柴工嘛。