4、统计滤波:统计离群点移除原理、均值与标准差计算、PCL实现、参数设置技巧

大家好,我是你们的老朋友。今天我们来聊聊点云滤波里一个非常实用的方法——统计滤波。说白了,它就是用来干掉那些“不靠谱”的离群点的。

你想想看,激光雷达扫描的时候,难免会碰到一些“捣乱”的点。比如空气中飘着的灰尘、传感器自身的噪声,或者物体边缘反射不稳定产生的点。这些点数量少,位置又很突兀,留着它们,后续做配准、分割、特征提取,都会出问题。统计滤波,就是专门收拾这些“坏点”的。

统计离群点移除原理

这个原理其实很直观。我打个比方:你站在一群人中间,如果大部分人都挤在一起,只有一个人离得远远的,那这个人就是离群点。统计滤波也是这个逻辑。

它基于一个假设:在点云中,每个点周围都应该有足够多的邻居点。如果一个点周围太“冷清”,邻居点很少,那它大概率就是个离群点。

具体怎么做呢?它会计算每个点到它所有邻居点的平均距离。然后,对整个点云的所有平均距离,算一个全局的均值(μ)和标准差(σ)。最后,设定一个阈值。如果一个点的平均距离超出了 μ + k * σ(k 是一个倍数),就把它判定为离群点,移除掉。

嗯,这里要注意:这个“邻居”是怎么定义的? 通常有两种方式:一种是找距离该点最近的 K 个点(K近邻);另一种是以该点为中心,画一个半径为 R 的球,球内的点就算邻居。PCL 里默认用的是 K 近邻。

核心思想一句话总结: 统计每个点与其邻居的距离分布,把那些“不合群”的点剔除。

均值与标准差计算

这部分是数学基础,但别怕,很简单。我拆开来讲。

假设我们有一个点云,里面有 N 个点。对于第 i 个点,我们计算它到 K 个最近邻居的平均距离,记作 d_i。那么,整个点云就有 N 个这样的平均距离值:d_1, d_2, ..., d_N。

均值(μ)的计算:

就是把所有 d_i 加起来,再除以 N。

μ = (d_1 + d_2 + ... + d_N) / N

标准差(σ)的计算:

标准差衡量的是这些平均距离的“离散程度”。公式是:

σ = sqrt( ( (d_1 - μ)^2 + (d_2 - μ)^2 + ... + (d_N - μ)^2 ) / N )

说白了,就是每个点的平均距离和全局平均距离的差的平方,加起来再平均,最后开个根号。

我在项目中遇到过一个问题:如果点云里离群点特别多,或者场景非常稀疏,这个均值和标准差的计算会被“带偏”。比如,一个空旷的走廊,点本来就少,平均距离自然就大。这时候再用默认参数,可能会把正常的墙点也给删了。所以,理解你的数据场景,比死记公式更重要。

PCL实现

在 PCL 里,实现统计滤波非常简单。核心就是 pcl::StatisticalOutlierRemoval 这个类。我直接上代码,大家一看就明白。

#include <pcl/point_types.h>
#include <pcl/filters/statistical_outlier_removal.h>

// 假设 cloud 是输入点云,类型是 pcl::PointCloud<pcl::PointXYZ>::Ptr
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);

// 创建滤波器对象
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);           // 设置输入点云
sor.setMeanK(50);                   // 设置K近邻搜索的邻居点数
sor.setStddevMulThresh(1.0);        // 设置标准差倍数阈值
sor.filter(*cloud_filtered);        // 执行滤波,结果保存到 cloud_filtered

// 如果你想保留被移除的点(离群点),可以这样做:
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_outliers(new pcl::PointCloud<pcl::PointXYZ>);
sor.setNegative(true);              // 设置为取反,即保留离群点
sor.filter(*cloud_outliers);

你看,核心就三个步骤:设置输入、调两个参数、执行滤波。代码量很少,但参数调起来,学问可不少。

小提示: 如果你只想快速看看效果,可以先试试 setMeanK(50) 和 setStddevMulThresh(1.0)。这两个值在很多场景下是个不错的起点。

参数设置技巧

这部分是我最想跟大家分享的。参数设不好,滤波效果天差地别。我曾经就因为这个,在一个项目里折腾了两天。

统计滤波有两个关键参数:MeanK 和 StddevMulThresh。

参数 含义 设置技巧
MeanK 计算平均距离时,考虑的邻居点个数 点云密度大,可以设大一点(如50-100);密度小,设小一点(如10-30)。我一般先设50,再根据效果微调。
StddevMulThresh 标准差倍数阈值,控制滤波的严格程度 值越小,滤波越严格,移除的点越多。通常设为1.0到2.0之间。如果场景噪声大,可以设小点(如0.8);如果场景干净,可以设大点(如1.5)。

避坑指南:

  • MeanK 太小,容易误删。 比如你设成5,一个点周围只有5个邻居,稍微有点抖动,平均距离就偏大,容易被当成离群点。我曾经在室内场景里设了10,结果把墙角的一些点全删了,导致后续建图出现空洞。
  • StddevMulThresh 太大,容易漏删。 设成3.0以上,基本上就没什么滤波效果了,离群点全留着。
  • 先粗调,后细调。 我的习惯是:先用 MeanK=50, StddevMulThresh=1.0 跑一遍,看看效果。如果发现正常点被误删,就增大 StddevMulThresh 或者减小 MeanK。如果发现离群点没删干净,就减小 StddevMulThresh 或者增大 MeanK。

特别注意: 统计滤波对稀疏点云效果不太好。如果你的点云每平方米只有几十个点,那每个点的邻居都很少,平均距离都很大,滤波会把所有点都当成离群点。这种情况下,我建议先做体素滤波降采样,或者改用半径滤波。

最后,我画了一张图,帮大家把统计滤波的整个流程串起来。你看一眼,应该就全明白了。

统计滤波(StatisticalOutlierRemoval)核心流程 输入原始点云 对每个点,搜索K个最近邻居 计算每个点到K个邻居的平均距离 统计全局均值 μ 和标准差 σ 判断:平均距离 > μ + k*σ ? 是 → 离群点(移除) | 否 → 内点(保留) 关键参数 • MeanK (K值) 邻居点个数 • StddevMulThresh (k值) 标准差倍数阈值 值越小,滤波越严格 值越大,滤波越宽松

好了,关于统计滤波,核心内容就这些。记住它的原理、两个关键参数,以及我分享的那些实战技巧。下次你在项目里遇到离群点,就知道该怎么收拾它们了。

可可精品资料库