4. 统计滤波:离群点移除原理、均值与标准差计算、在激光雷达数据中的应用

4.1 为什么要做统计滤波?

做激光SLAM的人都知道,点云数据里总有一些「不速之客」。比如飞鸟、扬尘、传感器自身的噪声点。这些离群点如果不处理,后面的配准、建图都会出问题。

我刚开始做室外激光SLAM时,就吃过这个亏。有一次在矿区测试,点云里全是扬尘产生的漂浮点。结果ICP配准直接跑飞了,轨迹图惨不忍睹。后来加了统计滤波,世界清净了。

统计滤波的原理其实很朴素:离群点周围通常很「孤独」。你想想看,一个正常的墙面点,周围至少有几十个邻居。而一个噪声点,周围可能空荡荡的。

核心思想:计算每个点到其k个最近邻的平均距离。如果这个平均距离超出了全局统计范围,就判定为离群点。

4.2 均值与标准差计算

这里涉及两个关键统计量:均值和标准差。我习惯用数学语言先描述清楚,再给代码。

假设点云有N个点。对于每个点p_i,我们找到它的k个最近邻,计算平均距离d_i。然后对所有d_i求均值和标准差:

μ = (1/N) * Σ d_i
σ = sqrt( (1/N) * Σ (d_i - μ)² )

判定规则很简单:如果某个点的d_i > μ + std_mult * σ,就把它剔除。std_mult是一个可调参数,我一般取1.0到3.0之间。

我的经验:std_mult取2.0是个不错的起点。太大会放过离群点,太小会误删有效点。我曾经在隧道场景里把std_mult调到1.5,因为隧道里反射噪声特别多。

4.3 算法流程

整个流程我总结为四步走:

  1. 建立KD-Tree:加速最近邻搜索。点云数据量一大,暴力搜索就扛不住了。
  2. 计算平均距离:对每个点,搜索k个最近邻,计算平均距离。
  3. 统计全局分布:计算所有平均距离的均值和标准差。
  4. 剔除离群点:根据阈值过滤。

下面是我常用的PCL实现代码:

// PCL统计滤波示例
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50);          // 邻居点数k
sor.setStddevMulThresh(2.0); // 标准差倍数
sor.filter(*cloud_filtered);

代码就这么几行,但参数调起来有讲究。我见过有人把setMeanK设成5,结果滤波效果很差。为什么?因为邻居太少,统计量不稳定。

4.4 参数调优指南

参数 推荐范围 我的建议
setMeanK (k) 20 ~ 100 点云密度大时取大值,稀疏时取小值
setStddevMulThresh 1.0 ~ 3.0 噪声多取小值,场景干净取大值

注意:k值不是越大越好。我试过k=200,结果计算量暴增,而且把一些边缘点也误删了。边缘点本身邻居就少,平均距离自然偏大。

4.5 在激光雷达数据中的应用

激光雷达数据有几个特点:

  • 近密远疏:近处点密集,远处点稀疏。统计滤波对远处点不太友好。
  • 多回波噪声:雨雪天气下,多回波会产生大量离群点。
  • 运动畸变:运动中的点云会有拉伸,影响邻居搜索。

我处理过的一个实际案例:在港口场景,集装箱表面有大量反光点。这些反光点不是离群点,但反射强度异常。统计滤波只看几何位置,不看强度,所以反光点不会被剔除。这时候需要结合强度滤波。

另外,对于自动驾驶场景,我建议先做体素滤波降采样,再做统计滤波。降采样后的点云更均匀,统计滤波效果更好。顺序别搞反了,否则统计滤波会误删很多点。

4.6 知识体系图

统计滤波知识体系 统计滤波 离群点移除原理 均值与标准差计算 激光雷达数据应用 k近邻搜索 距离阈值判定 全局均值μ 标准差σ 近密远疏处理 雨雪噪声过滤 参数调优:k=20~100, std_mult=1.0~3.0

4.7 避坑指南

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

  • 坑一:我曾经在密集点云上用了k=10,结果滤波效果很差。后来发现邻居太少,统计量方差太大。建议k至少取30。
  • 坑二:统计滤波对稀疏点云不友好。如果点云本身就很稀疏,建议先做体素滤波增加密度,或者改用半径滤波。
  • 坑三:别忘了先去除NaN点。有些传感器会返回无效点,这些点会影响邻居搜索。

嗯,统计滤波就讲到这里。它虽然简单,但用好了能解决很多实际问题。下次遇到点云里有「飞点」,不妨先试试统计滤波。

可可精品资料库