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 算法流程
整个流程我总结为四步走:
- 建立KD-Tree:加速最近邻搜索。点云数据量一大,暴力搜索就扛不住了。
- 计算平均距离:对每个点,搜索k个最近邻,计算平均距离。
- 统计全局分布:计算所有平均距离的均值和标准差。
- 剔除离群点:根据阈值过滤。
下面是我常用的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 知识体系图
4.7 避坑指南
最后分享几个我踩过的坑:
- 坑一:我曾经在密集点云上用了k=10,结果滤波效果很差。后来发现邻居太少,统计量方差太大。建议k至少取30。
- 坑二:统计滤波对稀疏点云不友好。如果点云本身就很稀疏,建议先做体素滤波增加密度,或者改用半径滤波。
- 坑三:别忘了先去除NaN点。有些传感器会返回无效点,这些点会影响邻居搜索。
嗯,统计滤波就讲到这里。它虽然简单,但用好了能解决很多实际问题。下次遇到点云里有「飞点」,不妨先试试统计滤波。
可可精品资料库