3. 点云预处理:体素滤波降采样、统计滤波去噪、半径滤波去噪、直通滤波

拿到一帧原始点云,你直接扔进分割算法?我劝你别这么干。原始点云里什么都有:离群噪点、冗余点、远处的杂波,甚至还有传感器自带的反射鬼影。不做预处理,分割结果基本没法看。

这一章,我带你过一遍点云预处理最常用的四个滤波器。说白了,就是给点云「洗个澡、剃个毛、剪个边」。每个滤波器解决一类问题,组合起来用,效果翻倍。

核心思路: 预处理不是越复杂越好,而是「够用就行」。我见过有人堆了七八个滤波器,结果把目标本身的点都滤没了。记住:降采样保结构,去噪保边缘,直通保范围。
点云预处理四步走 原始点云 ① 直通滤波 裁剪空间范围,去掉远处/无关区域 ② 体素滤波降采样 用体素网格替代密集点,降低数据量 ③ 统计滤波去噪 基于邻域距离统计,剔除稀疏离群点 ④ 半径滤波去噪 半径内点数不足则剔除,去除孤立点 干净点云 → 分割 💡 顺序建议:先直通 → 再体素 → 最后去噪 先减量,再精洗,效率最高

3.1 直通滤波:先切一刀

直通滤波是最简单的滤波器,没有之一。你指定一个轴(比如 Z 轴),设一个范围,范围外的点全砍掉。为什么先做它?因为很多场景下,我们只关心某个距离范围内的目标。

举个例子。车载激光雷达扫一圈,能扫到 100 米开外。但你要检测的是前方 30 米内的行人。那 30 米外的点留着干嘛?徒增计算量而已。

我的习惯: 拿到点云第一件事,先看直方图或距离分布。确定目标所在的大致范围,然后一刀切下去。我做过一个项目,原始点云 50 万点,直通过滤后只剩 8 万点——速度直接起飞。
// PCL 直通滤波示例
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");        // 沿 Z 轴过滤
pass.setFilterLimits(0.0, 30.0);     // 保留 0~30 米范围
// pass.setFilterLimitsNegative(true); // 设为 true 则保留范围外的点
pass.filter(*cloud_filtered);

参数就两个:轴和范围。但有个坑——坐标系方向。我记得有一次,同事把 Z 轴当成高度,结果滤完发现地面没了。后来一查,他用的传感器坐标系里 Z 轴是朝前的。所以,先搞清楚你的点云坐标系再动手。

3.2 体素滤波降采样:给点云「减肥」

体素滤波是降采样的主力。它的原理很简单:把空间划分成一个个小立方体(体素),每个体素内只保留一个点(通常是重心或中心点)。

你想想看,一片平坦的地面,可能有几万个点。但实际分割时,几百个点就够描述地面形状了。体素滤波就是干这个的——用更少的点,保留同样的几何结构。

体素大小 点数保留比例 适用场景
0.01 m ~80% 精细建模、小目标检测
0.05 m ~30% 一般分割、地面提取
0.10 m ~10% 大场景、快速预览
// PCL 体素滤波示例
pcl::VoxelGrid<pcl::PointXYZ> voxel;
voxel.setInputCloud(cloud_filtered);
voxel.setLeafSize(0.05f, 0.05f, 0.05f); // 5cm 体素
voxel.filter(*cloud_downsampled);
注意: 体素大小不是越小越好。设太小,降采样效果不明显;设太大,小目标(比如行人、杆子)可能直接被「吃」掉。我一般从 0.03 m 开始试,根据目标尺寸调整。

体素滤波还有一个隐藏好处:均匀化点密度。原始点云近处密、远处疏,体素滤波能把这个差异抹平。后续做特征计算时,不会因为密度不均导致误判。

3.3 统计滤波去噪:干掉「孤魂野鬼」

统计滤波去噪,是我用得最多的去噪方法。它的原理基于一个观察:真实物体表面的点,邻域内点很多;而离群噪点周围,点很少。

具体做法是:对每个点,计算它到 k 个最近邻的平均距离。如果这个距离超过全局均值 + 若干倍标准差,就判定为离群点,干掉。

// PCL 统计滤波示例
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud_downsampled);
sor.setMeanK(50);          // 邻域点数
sor.setStddevMulThresh(1.0); // 标准差倍数
sor.filter(*cloud_denoised);

两个关键参数:MeanK 和 StddevMulThresh。

  • MeanK:邻域点数。设太小,统计不稳定;设太大,边缘点容易被误杀。我一般设 20~50。
  • StddevMulThresh:标准差倍数。设 1.0 比较严格,能去掉大部分噪点;设 2.0 更宽松,只去掉极端离群点。
避坑指南: 我曾经在一个隧道点云上做统计滤波,结果把墙壁上的点也滤掉了一大片。后来发现,隧道内壁点密度本身就不均匀,统计滤波把稀疏区域的点当成了离群点。解决办法:先做体素滤波均匀密度,再做统计滤波。

3.4 半径滤波去噪:简单粗暴但有效

半径滤波和统计滤波有点像,但更直接。它给每个点画一个半径为 R 的球,如果球内的点数少于阈值 N,就干掉这个点。

什么时候用半径滤波?当你的噪点分布比较均匀,或者你想保留特定密度的区域时。比如,我只想保留每平方米至少有 10 个点的区域,半径滤波就很好用。

// PCL 半径滤波示例
pcl::RadiusOutlierRemoval<pcl::PointXYZ> ror;
ror.setInputCloud(cloud_denoised);
ror.setRadiusSearch(0.1);   // 搜索半径 0.1 米
ror.setMinNeighborsInRadius(5); // 至少 5 个点
ror.filter(*cloud_clean);
滤波器 核心参数 适用场景 缺点
统计滤波 MeanK, StddevMulThresh 稀疏离群点、传感器噪点 密度不均时误杀
半径滤波 Radius, MinNeighbors 均匀噪点、密度阈值控制 对半径敏感
我的经验: 统计滤波和半径滤波可以组合使用。先统计滤波去掉稀疏大噪点,再半径滤波去掉小簇噪点。但别搞反了——半径滤波对密度变化敏感,先做它容易把边缘点干掉。

3.5 预处理流程实战建议

说了这么多,到底怎么组合?我给出一个通用流程,你根据实际场景微调:

  1. 直通滤波:先切出感兴趣区域,减少数据量。这一步能省 50%~80% 的点。
  2. 体素滤波:降采样到可接受密度。目标大就用大体素,目标小就用小体素。
  3. 统计滤波:去掉稀疏离群点。标准参数 MeanK=50, StddevMulThresh=1.0。
  4. 半径滤波:可选步骤,用于进一步清理小簇噪点。

嗯,这里要注意:每一步的参数都不是固定的。我习惯先跑一遍默认参数,看看效果,再微调。比如体素大小,我会从 0.05 开始,如果发现目标点被削得太厉害,就改小到 0.03。

性能提醒: 统计滤波和半径滤波的计算量跟点数成正比。如果点云超过 100 万点,建议先体素降采样再做去噪。否则,等几分钟是常有的事。