4. 半径滤波(Radius Outlier Removal):原理详解、PCL实现、参数调优
点云滤波这个事儿,说白了就是给数据“去伪存真”。上一章我们聊了统计滤波,它适合处理那些分布比较均匀的离群点。但实际项目中,我遇到过不少情况——比如激光雷达扫到一只飞过的鸟,或者扫地机器人撞到桌腿时产生的零星噪点。这些点离主体点云很远,但数量又不多。这时候,半径滤波就派上用场了。
4.1 原理详解:为什么是“半径”说了算?
半径滤波的核心思想非常直观:如果一个点周围没有足够多的邻居,那它大概率是个“孤魂野鬼”。
具体怎么判断呢?我习惯这么理解:
- 画个圈:以每个待判断的点为中心,设定一个半径
r(比如0.1米)。 - 数人头:统计这个球体内有多少个其他点。
- 下判决:如果邻居数量小于设定的阈值
k(比如3个),就把这个点标记为离群点,直接干掉。
你想想看,一个正常的墙面点云,每个点周围密密麻麻都是兄弟。而一个孤立的噪点,周围空空荡荡。半径滤波就是抓住了这个本质区别。
核心公式(其实不算公式):
对于点 p_i,搜索半径 r 内的邻域点集 N(p_i)。
如果 |N(p_i)| < k,则 p_i 为离群点。
就这么简单,没有复杂的概率模型,纯靠“人缘”好坏。
我曾经在一个室内建图项目里,用统计滤波死活调不好参数。后来换成半径滤波,把半径设成0.05米,邻居阈值设成2,那些飘在空中的零星噪点瞬间被清干净了。嗯,有时候简单粗暴反而最有效。
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 统计滤波:怎么选?
很多同学会问:既然都是去噪点,半径滤波和统计滤波有啥区别?我什么时候该用哪个?
我的理解是这样的:
- 统计滤波:适合处理“分布均匀的离群点”。比如高斯噪声、测量误差。它看的是全局统计特性。
- 半径滤波:适合处理“孤立稀疏的离群点”。比如飞鸟、灰尘、镜面反射点。它看的是局部密度。
说白了,如果你的点云里噪点是一小撮一小撮的,用统计滤波;如果是零星几个孤立的,用半径滤波。我一般会两个都试一下,哪个效果好就用哪个。有时候也会组合使用——先用半径滤波干掉大离群点,再用统计滤波平滑一下。
一句话总结: 半径滤波是“看人缘”,统计滤波是“看平均”。人缘太差的(邻居太少),直接踢出去。
好了,这一章的内容就到这里。半径滤波虽然简单,但用好了真的能大幅提升建图质量。下次你遇到点云里飘着几个“孤魂野鬼”时,不妨试试它。