4、统计滤波:离群点移除原理、标准差阈值设定、实战案例
点云滤波这件事,做高精地图的人天天都要面对。前面我们聊了直通滤波、体素滤波,今天轮到统计滤波了。说实话,统计滤波是我个人用得最多的一个——因为它解决了一个很实际的问题:那些飘在空中的孤立噪点,怎么去掉?
4.1 离群点移除原理
先说说什么是离群点。你想想看,激光雷达扫描的时候,偶尔会打到一些奇怪的地方——比如空气中的灰尘、飞过的昆虫、或者镜面反射产生的虚假点。这些点跟周围点没有关联,孤零零地飘在那里。在高精地图里,这种点就是毒瘤。
统计滤波的思路其实很朴素:每个点周围都有邻居,邻居多的留下,邻居少的滚蛋。具体怎么做呢?
- 对每个点,计算它到所有k个最近邻的平均距离
- 所有点的平均距离会形成一个分布(通常是高斯分布)
- 设定一个阈值,平均距离超过阈值的点就是离群点
嗯,这里要注意:阈值怎么定?不是拍脑袋定的,而是用标准差来算。
核心公式:
阈值 = μ + 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个邻居的平均距离来判断离群点
- 阈值 = μ + std_mult × σ,std_mult通常取1.0~3.0
- 不同场景需要不同的参数组合,没有万能参数
- 统计滤波对稀疏点云和连续噪点效果有限,需要组合使用
可可精品资料库