点云预处理:体素滤波降采样、直通滤波、统计滤波去噪、半径滤波
各位同学,大家好。今天我们来聊聊点云预处理。说实话,在激光雷达感知的整个pipeline里,预处理这块最容易被忽视,但恰恰又是最要命的。我见过不少新手,拿到点云直接丢进检测网络,结果效果一塌糊涂,回头还怪算法不行。其实啊,很多时候是数据本身太“脏”了。
点云预处理的核心目标就三个:降采样、去噪、提取感兴趣区域。今天我把四种最常用的方法掰开揉碎了讲,都是我这些年踩坑踩出来的经验。
核心观点:预处理做得好,后续算法事半功倍;预处理偷懒,后面全是坑。
一、体素滤波降采样(Voxel Grid Filter)
体素滤波,说白了就是把空间切成一个个小立方体(体素),每个体素里只保留一个点。为什么要这么做?64线激光雷达一帧点云可能有十几万个点,直接处理太慢了。我习惯先降采样到1-2万个点,速度能快一个数量级。
具体怎么操作?
- 设定体素大小,比如0.1m x 0.1m x 0.1m
- 把点云划分到各个体素中
- 每个体素内计算所有点的重心(或中心),用这个点代表整个体素
这里有个坑——体素大小怎么选?太小了降采样效果不明显,太大了会丢失细节。我在做高速公路场景时,体素设0.2m效果不错;但换成城区道路,行人多,就得降到0.1m甚至0.05m。
我的经验:体素大小建议设为目标最小尺寸的1/3到1/2。比如你要检测行人(约0.5m宽),体素别超过0.2m。
// PCL实现示例
pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(cloud);
voxel_filter.setLeafSize(0.1f, 0.1f, 0.1f); // 体素大小
voxel_filter.filter(*cloud_filtered);
二、直通滤波(PassThrough Filter)
直通滤波,名字听着玄乎,其实就是切一刀。比如我只关心前方50米内的障碍物,那把距离超过50米的点全砍掉。再比如激光雷达装在车顶,地面以下和车顶以上的点也可以直接去掉。
为什么要这么做?减少计算量是其一,更重要的是排除干扰。我记得有一次做隧道场景,激光雷达打到隧道壁上反射回来的点特别多,直通滤波把距离限制在30米内,瞬间清爽了。
直通滤波的维度通常有三个:X轴(前后)、Y轴(左右)、Z轴(上下)。实际项目中,Z轴滤波最常用——去掉地面和过高点。
// 只保留Z轴在-1.0到2.0米之间的点(地面到车顶)
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");
pass.setFilterLimits(-1.0, 2.0);
pass.filter(*cloud_filtered);
注意:直通滤波的阈值要根据传感器安装位置动态调整。车装高了,Z轴下限就得跟着变。我曾经吃过这个亏,换了一辆车忘了改参数,结果地面点全没滤掉。
三、统计滤波去噪(Statistical Outlier Removal, SOR)
统计滤波,是我个人最常用的去噪方法。它的原理很简单:计算每个点与周围k个邻居的平均距离,如果这个距离太大,就认为是离群点。
为什么会存在离群点?激光打到玻璃、水面、或者边缘时,会产生一些“飘”出去的点。这些点对检测来说是毒药,必须清掉。
SOR有两个关键参数:
| 参数 | 含义 | 典型值 |
|---|---|---|
| k(邻居数) | 计算距离时参考的邻近点个数 | 50 |
| std_dev(标准差倍数) | 超过平均距离多少倍算离群点 | 1.0 |
你想想看,如果std_dev设得太小(比如0.5),很多正常点会被误删;设得太大(比如3.0),噪声又滤不干净。我一般从1.0开始调,看效果再微调。
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50); // 邻居数
sor.setStddevMulThresh(1.0); // 标准差倍数
sor.filter(*cloud_filtered);
避坑指南:我曾经在雨雾天气测试,发现SOR把雨滴点滤掉了,但同时也把远处的车辆点误删了。后来我把k值从50降到20,std_dev从1.0调到1.5,才平衡了去噪和保真。
四、半径滤波(Radius Outlier Removal)
半径滤波和统计滤波有点像,但思路更直接:以每个点为中心,画一个半径为r的球,如果球内的邻居数少于某个阈值,就删掉这个点。
说白了,就是“朋友太少的人不配存在”。这个方法的优点是参数直观,好理解。缺点是对密度变化敏感——远处点本来就稀疏,容易误删。
我一般在什么场景用半径滤波?当点云密度比较均匀时,比如室内场景或者近距离的停车场。远距离场景还是SOR更靠谱。
pcl::RadiusOutlierRemoval<pcl::PointXYZ> rad;
rad.setInputCloud(cloud);
rad.setRadiusSearch(0.8); // 搜索半径(米)
rad.setMinNeighborsInRadius(2); // 最少邻居数
rad.filter(*cloud_filtered);
小技巧:半径滤波的搜索半径可以结合激光雷达线束间距来设。比如16线雷达垂直分辨率2°,50米处相邻线束间距约1.75米,那半径设1米左右比较合理。
五、四种方法怎么选?一张图说清楚
下面这张图是我自己总结的,四种滤波方法在预处理流程中的位置和作用:
从图上可以看得很清楚:直通滤波最先做,把不关心的区域切掉;然后体素滤波降采样,减少数据量;最后用统计滤波或半径滤波去噪。顺序别搞反了,否则先降采样再切ROI,白白浪费计算资源。
六、实战中的组合策略
实际项目中,很少只用一种滤波。我总结了几种常见场景的组合方案:
- 高速公路场景:直通滤波(Z轴-1.5~3.0m,X轴0~80m)→ 体素滤波(0.2m)→ 统计滤波(k=30, std=1.2)
- 城区道路场景:直通滤波(Z轴-1.0~2.5m,X轴0~50m)→ 体素滤波(0.1m)→ 半径滤波(r=0.5m, min=3)
- 停车场/室内场景:直通滤波(Z轴-0.5~2.0m)→ 体素滤波(0.05m)→ 统计滤波(k=20, std=1.0)
嗯,这里要注意:参数不是死的。我每次换传感器或者换场地,都会重新标定一遍滤波参数。别偷懒,这一步省不了。
最后说一句:预处理做得好不好,直接决定了你后面目标检测和跟踪的上限。花30%的时间在预处理上,一点都不亏。
公众号:蓝海资料掘金营,微信deep3321