4、统计滤波:离群点移除原理、标准差阈值设定、实战案例

点云滤波这件事,做高精地图的人天天都要面对。前面我们聊了直通滤波、体素滤波,今天轮到统计滤波了。说实话,统计滤波是我个人用得最多的一个——因为它解决了一个很实际的问题:那些飘在空中的孤立噪点,怎么去掉?

4.1 离群点移除原理

先说说什么是离群点。你想想看,激光雷达扫描的时候,偶尔会打到一些奇怪的地方——比如空气中的灰尘、飞过的昆虫、或者镜面反射产生的虚假点。这些点跟周围点没有关联,孤零零地飘在那里。在高精地图里,这种点就是毒瘤。

统计滤波的思路其实很朴素:每个点周围都有邻居,邻居多的留下,邻居少的滚蛋。具体怎么做呢?

  1. 对每个点,计算它到所有k个最近邻的平均距离
  2. 所有点的平均距离会形成一个分布(通常是高斯分布)
  3. 设定一个阈值,平均距离超过阈值的点就是离群点

嗯,这里要注意:阈值怎么定?不是拍脑袋定的,而是用标准差来算。

核心公式:

阈值 = μ + std_mult × σ

其中μ是所有点平均距离的均值,σ是标准差,std_mult是标准差倍数(通常取1.0~3.0)

我在项目中遇到过这样的情况:某次采集的数据里,有一片区域因为玻璃幕墙反射,产生了大量离群点。如果不做统计滤波,这些点会被当成真实地物,地图里就会出现一堵"鬼墙"。后来我调了标准差倍数,从2.0降到1.5,效果立竿见影。

4.2 标准差阈值设定

阈值设定是统计滤波的灵魂。我见过很多新手上来就设个固定距离,比如5米。这其实不对——为什么呢?因为不同场景下点云的密度差异太大了。

举个例子:

  • 近距离扫描(比如10米内),点云很密,平均邻居距离可能只有0.1米
  • 远距离扫描(比如50米外),点云稀疏,平均邻居距离可能到0.5米

如果你固定用5米做阈值,近处的离群点全漏掉了,远处的正常点反而被误删。所以要用标准差来自适应。

场景类型 推荐std_mult值 邻居数k 说明
城市道路 1.5 ~ 2.0 20 ~ 30 点云密集,离群点少
高速公路 2.0 ~ 3.0 10 ~ 20 点云稀疏,阈值放宽
隧道/地下车库 1.0 ~ 1.5 30 ~ 50 多径反射多,严格过滤
郊区/野外 2.5 ~ 3.5 5 ~ 15 点云极稀疏,避免误删

我个人习惯是:先跑一遍默认参数(k=20, std_mult=2.0),看看效果。如果发现离群点没清干净,就把std_mult调小;如果发现正常点被误删了,就调大。说白了,这是个经验活。

小技巧:你可以先统计一下点云的平均密度。如果平均每个点1立方米内只有5个点,那k值就别设太大,否则邻居都找不到。

4.3 实战案例:PCL实现统计滤波

好了,理论说完了,上代码。我用的是PCL库,这是点云处理最常用的工具。代码不长,但每一行都有讲究。

#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(20);          // 邻居数k=20
sor.setStddevMulThresh(2.0); // 标准差倍数=2.0

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

// 输出结果
std::cout << "原始点云: " << cloud->size() << " 个点" << std::endl;
std::cout << "滤波后: " << cloud_filtered->size() << " 个点" << std::endl;
std::cout << "移除: " << cloud->size() - cloud_filtered->size() << " 个离群点" << std::endl;

这段代码跑完之后,你会看到移除了多少离群点。我曾经在一个项目里,原始点云有120万个点,统计滤波后剩下108万个,移除了10%的离群点。这些点大部分是空气中的漂浮物和远距离的噪点。

避坑指南:我曾经犯过一个错误——把std_mult设成了0.5,结果好点全被删了,只剩下一堆稀疏的点。后来才意识到,标准差倍数太小,会把正常点也当成离群点。记住:std_mult < 1.0 基本等于自杀。

4.4 统计滤波的局限性

说实话,统计滤波不是万能的。我总结了几点:

  • 对稀疏点云效果差:如果点云本身就很稀疏,每个点的邻居都少,统计出来的平均距离方差很大,阈值不好定
  • 计算量大:要计算每个点的k近邻,复杂度是O(n log n),点云超过100万时,速度明显变慢
  • 对连续噪点无效:如果噪点聚成一团(比如一片树叶),统计滤波会认为它们是正常点

那怎么办呢?我的做法是:统计滤波作为第一道防线,后面再配合半径滤波或者条件滤波。说白了,没有一种滤波是银弹,组合使用才是王道。

4.5 核心逻辑流程图

下面这张图是我自己画的,把统计滤波的整个流程串起来了。你看一遍应该就能记住。

统计滤波核心流程 输入原始点云 构建k-d树加速搜索 对每个点:计算k个最近邻平均距离 统计所有点平均距离的均值μ和标准差σ 距离 > μ+std_mult×σ ? 是 标记为离群点 否 保留为正常点

这张图把统计滤波的五个核心步骤串起来了。你注意看那个判断菱形——说白了,整个算法的核心就是那个阈值比较。阈值设得好,离群点一个跑不掉;设不好,要么漏检,要么误杀。

好了,统计滤波就聊到这儿。记住一句话:统计滤波不是万能的,但没有统计滤波是万万不能的。在高精地图构建中,它是清理点云的第一道防线,也是最常用的一招。

本章要点回顾:

  • 统计滤波基于每个点到k个邻居的平均距离来判断离群点
  • 阈值 = μ + std_mult × σ,std_mult通常取1.0~3.0
  • 不同场景需要不同的参数组合,没有万能参数
  • 统计滤波对稀疏点云和连续噪点效果有限,需要组合使用

可可精品资料库