4. 半径滤波(Radius Outlier Removal):原理详解、PCL实现、参数调优

点云滤波这个事儿,说白了就是给数据“去伪存真”。上一章我们聊了统计滤波,它适合处理那些分布比较均匀的离群点。但实际项目中,我遇到过不少情况——比如激光雷达扫到一只飞过的鸟,或者扫地机器人撞到桌腿时产生的零星噪点。这些点离主体点云很远,但数量又不多。这时候,半径滤波就派上用场了。

4.1 原理详解:为什么是“半径”说了算?

半径滤波的核心思想非常直观:如果一个点周围没有足够多的邻居,那它大概率是个“孤魂野鬼”。

具体怎么判断呢?我习惯这么理解:

  1. 画个圈:以每个待判断的点为中心,设定一个半径 r(比如0.1米)。
  2. 数人头:统计这个球体内有多少个其他点。
  3. 下判决:如果邻居数量小于设定的阈值 k(比如3个),就把这个点标记为离群点,直接干掉。

你想想看,一个正常的墙面点云,每个点周围密密麻麻都是兄弟。而一个孤立的噪点,周围空空荡荡。半径滤波就是抓住了这个本质区别。

核心公式(其实不算公式):

对于点 p_i,搜索半径 r 内的邻域点集 N(p_i)。

如果 |N(p_i)| < k,则 p_i 为离群点。

就这么简单,没有复杂的概率模型,纯靠“人缘”好坏。

我曾经在一个室内建图项目里,用统计滤波死活调不好参数。后来换成半径滤波,把半径设成0.05米,邻居阈值设成2,那些飘在空中的零星噪点瞬间被清干净了。嗯,有时候简单粗暴反而最有效。

半径滤波(Radius Outlier Removal)决策流程 输入点云数据 设定搜索半径 r 和邻居阈值 k 遍历每个点,统计半径内邻居数 邻居数 < k ? 是 标记为离群点 否 保留为内点 输出滤波后点云

4.2 PCL实现:代码说话

理论讲完了,咱们直接上代码。PCL里已经封装好了 RadiusOutlierRemoval 类,用起来非常顺手。

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

// 1. 创建点云对象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);

// 2. 填充点云数据(此处省略读取步骤)
// cloud = ... 从文件或传感器读取

// 3. 创建滤波器对象
pcl::RadiusOutlierRemoval<pcl::PointXYZ> outrem;

// 4. 设置参数
outrem.setInputCloud(cloud);
outrem.setRadiusSearch(0.1);      // 搜索半径:0.1米
outrem.setMinNeighborsInRadius(3); // 邻居阈值:至少3个点

// 5. 执行滤波
outrem.filter(*cloud_filtered);

// 6. 输出结果
std::cerr << "滤波前点数: " << cloud->size() << std::endl;
std::cerr << "滤波后点数: " << cloud_filtered->size() << std::endl;

代码就这么几行。我个人习惯把 setRadiusSearch 和 setMinNeighborsInRadius 放在一起调,因为这两个参数是“夫妻档”,必须配合着来。

小技巧: 如果你不确定半径设多大,可以先算一下点云的平均点间距。用 pcl::computeCloudResolution() 估算一下,然后半径设为平均间距的 2~3 倍,通常效果不错。

4.3 参数调优:半径大小与邻居阈值

参数调优是半径滤波的核心,也是很多新手头疼的地方。我踩过不少坑,这里把经验总结一下。

4.3.1 半径大小(Radius Search)

  • 半径太小:容易把正常的点误删。比如墙面点云,如果半径设成0.01米,可能每个点周围都找不到几个邻居,结果整面墙都被删了。
  • 半径太大:离群点可能被“包庇”。比如一个孤立的噪点,如果半径设成1米,它周围可能正好有几个远处的点,就被误判为内点了。
  • 我的经验值:对于室内场景(如走廊、房间),0.05~0.2米是个不错的起点。对于室外场景(如道路、树木),可以放宽到0.3~0.5米。

4.3.2 邻居阈值(Min Neighbors in Radius)

  • 阈值太低:滤波效果不明显。设成1的话,基本等于没滤。
  • 阈值太高:会把边缘点也删掉。比如墙角处的点,天然就比平面上的点邻居少,阈值设成10,墙角就没了。
  • 我常用的范围:2~6。对于稀疏点云,2~3就够了;对于稠密点云,可以设到4~6。

避坑指南: 我曾经在一个果园的建图项目里,把半径设成0.3米,邻居阈值设成5。结果树干上的点被大量误删,因为树干是圆柱形,每个点周围的邻居确实比墙面少。后来我把半径缩小到0.15米,阈值降到3,效果才正常。所以,一定要根据你的点云密度来调,别死记硬背参数。

4.3.3 参数组合速查表

场景 点云密度 推荐半径 (m) 推荐邻居阈值
室内墙面(激光雷达) 高(每平米数千点) 0.05 ~ 0.1 3 ~ 5
室外道路(车载LiDAR) 中(每平米数百点) 0.2 ~ 0.4 2 ~ 4
无人机航测点云 低(每平米数十点) 0.5 ~ 1.0 2 ~ 3
RGB-D相机(Kinect) 高但噪声大 0.03 ~ 0.08 4 ~ 6

这张表是我自己总结的,不一定适用所有情况,但可以作为初始值。你想想看,调参就像炒菜放盐——先按菜谱来,再根据口味微调。

4.4 半径滤波 vs 统计滤波:怎么选?

很多同学会问:既然都是去噪点,半径滤波和统计滤波有啥区别?我什么时候该用哪个?

我的理解是这样的:

  • 统计滤波:适合处理“分布均匀的离群点”。比如高斯噪声、测量误差。它看的是全局统计特性。
  • 半径滤波:适合处理“孤立稀疏的离群点”。比如飞鸟、灰尘、镜面反射点。它看的是局部密度。

说白了,如果你的点云里噪点是一小撮一小撮的,用统计滤波;如果是零星几个孤立的,用半径滤波。我一般会两个都试一下,哪个效果好就用哪个。有时候也会组合使用——先用半径滤波干掉大离群点,再用统计滤波平滑一下。

一句话总结: 半径滤波是“看人缘”,统计滤波是“看平均”。人缘太差的(邻居太少),直接踢出去。

好了,这一章的内容就到这里。半径滤波虽然简单,但用好了真的能大幅提升建图质量。下次你遇到点云里飘着几个“孤魂野鬼”时,不妨试试它。

可可精品资料库