3. 统计滤波(Statistical Outlier Removal):原理、均值与标准差、PCL实现与参数调优

3.1 为什么要做统计滤波?

点云数据里总有些「不速之客」。比如激光雷达打到空气中的灰尘,或者深度相机边缘的噪点。这些离群点看着不多,但影响很大——建图会飘,配准会偏。

我个人习惯,拿到原始点云的第一件事,就是做统计滤波。为什么?因为它简单、有效,而且参数很直观。说白了,就是看每个点跟邻居们「合不合群」。

3.2 原理:邻居关系与高斯分布

统计滤波的核心思想其实很朴素:

  • 对每个点,计算它到所有邻居的平均距离
  • 这些平均距离应该服从高斯分布
  • 那些距离均值太远的点,就是离群点

你想想看,一个正常的点,周围总有几个小伙伴。如果它孤零零的,周围啥也没有,那大概率是噪声。

具体步骤是这样的:

  1. 设定邻居数量 k(比如 50 个)
  2. 对每个点,找最近的 k 个邻居
  3. 计算到这些邻居的平均距离 d_i
  4. 所有点的 d_i 构成一个分布,计算均值 μ 和标准差 σ
  5. 设定阈值:如果 d_i > μ + std_mult × σ,就剔除

这里的 std_mult 就是标准差倍数,也是我们调参的关键。

核心公式:

保留条件:d_i ≤ μ + std_mult × σ

其中 μ 是所有 d_i 的均值,σ 是标准差

3.3 均值与标准差:到底在算什么?

我记得刚入行时,总觉得「均值」和「标准差」是数学课上的东西。直到我在一个隧道场景里做滤波,才发现这两个值太重要了。

举个例子:

场景 均值 μ 标准差 σ 说明
室内平整墙面 0.05 m 0.02 m 点云很均匀
室外灌木丛 0.15 m 0.08 m 点云分布散
有飞点的场景 0.12 m 0.25 m 标准差突然变大

看到没?当场景里有飞点时,标准差会急剧增大。这时候如果还用固定的阈值,就会把很多正常点也滤掉。

我曾经在一个果园项目里吃过这个亏。果树叶子稀疏,标准差本来就大,结果我把阈值设得太死,把树干上的点都干掉了。后来我学乖了——先看统计直方图,再决定参数。

3.4 PCL实现:代码怎么写?

PCL 里已经封装好了,直接用 StatisticalOutlierRemoval 滤波器。代码不长,但有几个坑要注意。

#include <pcl/filters/statistical_outlier_removal.h>

// 输入点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 假设 cloud 已经加载了数据

// 创建滤波器对象
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);

// 关键参数设置
sor.setMeanK(50);          // 邻居数量 k
sor.setStddevMulThresh(1.0); // 标准差倍数

// 执行滤波
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
sor.filter(*cloud_filtered);

// 如果想看看被剔除的点
sor.setNegative(true);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_removed(new pcl::PointCloud<pcl::PointXYZ>);
sor.filter(*cloud_removed);

嗯,这里要注意:setNegative(true) 会返回被剔除的点,调试时很有用。我一般会先跑一遍,看看剔除的都是些什么货色。

3.5 参数调优:k 和 std_mult 怎么选?

这是大家最头疼的问题。我直接给经验值:

  • k(邻居数量):一般取 20~100。点云越密,k 可以越大。室内场景我常用 50,室外稀疏场景用 20 就够了。
  • std_mult(标准差倍数):一般取 0.5~3.0。值越小,滤得越狠。保守点用 2.0,激进点用 1.0。

调参小技巧:

先设 std_mult=3.0,看看剔除效果。如果还有明显飞点,逐步降低到 2.0、1.5。如果正常点被误删了,就调高 std_mult 或增大 k。

我曾经在一个工厂自动化项目里,点云里有大量反光造成的飞点。std_mult 设到 0.8 才勉强干净,但代价是边缘点也被削掉了一层。后来我改用半径滤波配合统计滤波,效果才好。

避坑指南:

我曾经在低密度点云上用过 k=100,结果每个点都找不到那么多邻居,算法直接崩了。记住:k 不能超过点云总点数。

3.6 核心逻辑流程图

下面这张图总结了统计滤波的完整流程,我建议你保存下来,调参时对照着看:

统计滤波核心流程 输入原始点云 设置邻居数量 k 对每个点:计算到k个邻居的平均距离 计算所有平均距离的均值μ和标准差σ d_i > μ + std_mult × σ ? 剔除 保留 输出滤波后点云

3.7 实战经验总结

最后分享几个我踩过的坑:

  • 场景不同,参数不同:室内和室外不要用同一套参数。我习惯给每个场景单独标定一次。
  • 先降采样再滤波:点云太密时,统计滤波会很慢。先体素滤波降采样,速度能快 10 倍。
  • 不要迷信默认值:PCL 默认 k=50、std_mult=1.0,但这只是通用值。我见过有人直接拿来用,结果把墙上的点都滤没了。
  • 可视化验证:调参后一定要用可视化工具看看效果。肉眼比任何指标都靠谱。

一句话总结:

统计滤波就是「看邻居,算距离,砍 outliers」。参数 k 控制邻居范围,std_mult 控制砍的力度。调参时先松后紧,配合可视化,效果不会差。

好了,统计滤波就聊到这儿。下一节我们讲半径滤波,那家伙在某些场景下比统计滤波更狠。

可可精品资料库