4. 统计滤波(StatisticalOutlierRemoval):离群点剔除原理、均值与标准差参数调优、实战经验

4.1 为什么要做统计滤波?

做点云处理的人,十有八九都遇到过这种场景:辛辛苦苦扫描了一堆点云,结果一看,好家伙,空中飘着几个孤零零的点,或者物体边缘长了一堆「毛刺」。这些离群点,说白了就是噪声。

我刚开始做SLAM的时候,就被这些离群点坑过。有一次在室内跑建图算法,明明墙壁是平的,结果重建出来的墙面坑坑洼洼。排查了半天,发现是激光雷达扫到玻璃窗时产生了大量反射噪点。嗯,从那以后,我养成了一个习惯——任何点云进来,先做一遍统计滤波。

统计滤波(StatisticalOutlierRemoval,简称SOR)是目前最常用的离群点剔除方法之一。它的核心思想很简单:每个点周围都有邻居,如果这个点离邻居们太远了,那它大概率是个噪声。

4.2 原理:一句话讲清楚

统计滤波的原理其实就三步:

  1. 算距离:对每个点,计算它到所有k个近邻点的平均距离。
  2. 算统计量:所有点的平均距离会形成一个分布,我们计算这个分布的均值μ和标准差σ。
  3. 设阈值:设定一个阈值 μ + std_ratio × σ。如果某个点的平均距离大于这个阈值,就把它剔除。

你想想看,正常点云的点分布比较均匀,每个点到邻居的距离差不多。而离群点呢?它孤零零的,到邻居的距离明显偏大。所以用这个办法,一抓一个准。

核心公式:

保留条件:di ≤ μ + std_ratio × σ

其中 di 是第i个点到其k近邻的平均距离,std_ratio 就是我们常说的标准差倍数参数。

4.3 两个关键参数:k 和 std_ratio

统计滤波就两个参数,但调起来门道不少。我一个个说。

4.3.1 近邻数量 k

k 决定了每个点看多少个邻居。我个人习惯这样选:

  • 稀疏点云(比如16线激光雷达):k 取 10~20。点太少,统计结果不稳定。
  • 稠密点云(比如64线或128线):k 取 20~50。点多了,统计更可靠。
  • 高精度场景(比如结构光扫描):k 取 30~50。精度要求高,需要更精细的统计。

我在项目中遇到过一个问题:k 设得太小(比如 k=5),结果把一些边缘点也误删了。为什么?因为边缘点的邻居本来就少,平均距离偏大,容易被当成离群点。所以,k 不能太小。

小技巧:如果你不确定 k 取多少,可以先设 k=20 试试。这个值在大多数场景下表现都不错。

4.3.2 标准差倍数 std_ratio

std_ratio 决定了滤波的严格程度。这个参数我一般这样调:

场景 推荐 std_ratio 说明
宽松滤波(只剔除明显噪点) 2.0 ~ 3.0 只干掉那些「离谱」的离群点
中等滤波(常规场景) 1.0 ~ 2.0 平衡去噪和保留细节
严格滤波(高精度需求) 0.5 ~ 1.0 去噪彻底,但可能损失边缘细节

说白了,std_ratio 越小,滤波越狠。但要注意,过小的 std_ratio 会把正常点也干掉。我曾经在调一个室内重建项目时,把 std_ratio 设成了 0.3,结果点云直接少了一半——连墙壁上的点都被当成离群点删了。教训深刻啊。

4.4 实战经验:避坑指南

避坑1:点云密度不均匀时慎用

我曾经在室外场景用过统计滤波,结果发现近处的点保留得很好,远处的点被删了一大片。为什么?因为远处点云稀疏,点到邻居的平均距离本来就大,容易被误判为离群点。

解决办法:如果点云密度变化很大,可以考虑分区域滤波,或者改用半径滤波(RadiusOutlierRemoval)。

避坑2:k 和 std_ratio 要配合调

这两个参数不是独立的。k 越大,统计结果越稳定,但 std_ratio 要适当放大。我一般遵循一个原则:k 翻倍,std_ratio 增加 0.2~0.5。这样能保持滤波效果的一致性。

避坑3:先降采样,再滤波

如果点云特别大(比如上百万点),直接做统计滤波会很慢。我的习惯是:先用体素滤波降采样到 10 万点左右,再做统计滤波。速度能快 10 倍以上,效果差别不大。

4.5 代码示例:PCL 实现

下面给一个 PCL 的代码示例。这个代码我用了很多次,基本是标准写法:

#include <pcl/filters/statistical_outlier_removal.h>

// 输入点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 输出点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);

// 创建统计滤波对象
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(20);          // k = 20,近邻数量
sor.setStddevMulThresh(1.5); // std_ratio = 1.5,标准差倍数
sor.filter(*cloud_filtered);

// 如果你想看看被剔除的点
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_outliers(new pcl::PointCloud<pcl::PointXYZ>);
sor.setNegative(true);  // 取反,得到离群点
sor.filter(*cloud_outliers);

这段代码很简单,但有几个细节要注意:

  • setNegative(true) 可以拿到被剔除的点,方便调试。
  • 如果点云带强度或颜色信息,记得把模板参数改成对应的类型。

4.6 知识体系:一张图看懂

下面这张图总结了统计滤波的完整流程和参数调优思路:

统计滤波(SOR)知识体系 原始点云 步骤1:计算每个点的k近邻平均距离 步骤2:计算所有平均距离的均值μ和标准差σ 步骤3:阈值 = μ + std_ratio × σ,剔除超过阈值的点 滤波后点云 参数调优 • k:近邻数量 - 稀疏点云:10~20 - 稠密点云:20~50 • std_ratio:标准差倍数 避坑指南 • 密度不均时慎用 • k和std_ratio要配合 • 先降采样再滤波 • 边缘点容易误删

4.7 总结

统计滤波是点云预处理中非常实用的一招。它原理简单,参数少,效果稳定。但要注意,它并不是万能的。如果点云密度变化很大,或者场景中有大量边缘点,建议结合其他滤波方法一起使用。

我个人建议,刚开始用统计滤波时,先拿一小块数据试参数。把 k 和 std_ratio 调到一个合适的范围,再应用到全量数据上。这样既省时间,又能避免「一刀切」带来的问题。

好了,统计滤波就聊到这儿。记住一句话:离群点要狠,正常点要稳。参数调好了,你的点云质量会上一个台阶。

一句话总结:统计滤波 = 算邻居距离 + 算统计量 + 设阈值。k 控制邻居范围,std_ratio 控制严格程度。两者配合,效果最佳。