第4章:点云预处理——点云滤波与降采样

大家好,我是你们的老朋友。今天我们来聊聊点云预处理。

说实话,很多刚入行的朋友拿到激光雷达数据,第一件事就是直接跑建图算法。结果呢?地图建得一塌糊涂,定位也飘得厉害。我当年也犯过这个错——记得有一次在室内走廊跑数据,明明直直的走廊,建出来的地图却是歪的。排查了半天,才发现是原始点云里混了一堆噪声点。

所以,点云预处理不是可有可无的步骤。它是整个SLAM系统的基石。你想想看,地基没打好,房子能稳吗?

核心观点:点云预处理的质量,直接决定了后续建图和定位的精度上限。花30%的时间做预处理,能省掉后面70%的调试痛苦。

点云预处理知识体系 原始点云数据 点云滤波 点云降采样 点云去噪 体素滤波 直通滤波 统计滤波 体素降采样 半径滤波 输出:干净、稀疏、高质量的点云

4.1 点云滤波——把垃圾数据筛出去

点云滤波,说白了就是「筛子」。把没用的点筛掉,留下有用的。我习惯把滤波分成三类:体素滤波、直通滤波、统计滤波。它们各自解决不同的问题。

4.1.1 体素滤波(Voxel Filter)

体素滤波的核心思想很简单:把空间切成一个个小立方体(体素),每个立方体里只保留一个点。这个点通常是体素内所有点的重心。

为什么要这么做?因为激光雷达扫描时,同一个平面上的点会非常密集。比如一面墙,可能有几万个点。但建图时,你不需要这么多——100个点就能把墙的形状描述清楚。多出来的点只会增加计算量。

我的经验:体素大小一般设为0.05m~0.1m。室内场景用0.05m,室外开阔场景可以放宽到0.1m。太小了降采样效果不明显,太大了会丢失细节。

// PCL中的体素滤波示例
pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(cloud);
voxel_filter.setLeafSize(0.05f, 0.05f, 0.05f);  // 体素边长5cm
voxel_filter.filter(*cloud_filtered);

4.1.2 直通滤波(PassThrough Filter)

直通滤波更简单——指定一个坐标轴的范围,只保留这个范围内的点。比如,我只想保留前方0~20米的点,或者高度在-1米到2米之间的点。

我在项目中经常用它来剔除地面点和远处的噪点。举个例子,车载激光雷达装在车顶,地面点的高度通常在-0.5米左右。用直通滤波把高度小于0的点去掉,地面就没了。

注意:直通滤波不能乱用。如果你在建图时需要保留地面作为参考,那就别把地面全切掉。我见过有人把地面全滤掉,结果定位时高度信息全丢了,地图直接飘到天上去了。

// 直通滤波:只保留Z轴在0~2米之间的点
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");
pass.setFilterLimits(0.0, 2.0);
pass.filter(*cloud_filtered);

4.1.3 统计滤波(Statistical Outlier Removal)

统计滤波是用来剔除离群点的。它的原理是:计算每个点周围K个邻居的平均距离,如果这个距离明显大于平均值(比如超过3倍标准差),就认为它是离群点。

为什么会存在离群点?激光雷达打到玻璃上、雨滴上、或者飞鸟身上,都会产生一些孤立的噪点。这些点不剔除,建图时就会出现「鬼影」。

避坑指南:统计滤波的参数设置很关键。邻居数K一般取50~100,标准差倍数取1.0~3.0。K太小了,离群点检测不准确;K太大了,会把边缘点也误删。我一般先用K=50、std=1.0试跑一次,看看效果再微调。

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

4.2 点云降采样——给点云「减肥」

降采样和滤波不太一样。滤波是去掉「坏」的点,降采样是减少「好」的点。说白了,就是给点云减肥。

为什么要减肥?你想想看,一个16线激光雷达,每秒产生30万个点。如果每帧都处理这么多点,CPU很快就扛不住了。降采样后,点云数量可以减少到原来的1/10甚至1/20,但形状信息几乎不变。

常用的降采样方法就是前面提到的体素降采样。不过要注意,体素降采样和体素滤波其实是同一个操作——只是目的不同。滤波是为了去噪,降采样是为了减少数据量。

方法 原理 适用场景 参数建议
体素降采样 体素内取重心 通用场景 体素大小0.05~0.1m
随机降采样 随机保留部分点 快速测试 保留比例0.1~0.3
均匀降采样 按空间均匀采样 需要均匀分布时 采样间隔0.05~0.2m

我的习惯:建图时用体素降采样,体素大小0.1m。定位时用更小的体素(0.05m),因为定位需要更精细的匹配。不过具体还得看你的激光雷达线束和场景。

4.3 点云去噪——把「脏东西」清理干净

去噪和滤波其实有重叠。但去噪更强调「清理」——把那些明显不属于场景的点去掉。

常见的噪声来源有:

  • 多路径反射:激光打到玻璃上,反射两次才回来,产生虚假点
  • 雨雪雾:水滴或雪花反射激光,产生漂浮点
  • 边缘效应:物体边缘处,激光束一半打在物体上、一半打在背景上,产生中间值
  • 运动畸变:车辆运动时,一帧内的点云发生拉伸或压缩

去噪的方法除了统计滤波,还有半径滤波(Radius Outlier Removal)。它的原理是:如果一个点周围指定半径内没有足够多的邻居,就把它删掉。

// 半径滤波
pcl::RadiusOutlierRemoval<pcl::PointXYZ> ror;
ror.setInputCloud(cloud);
ror.setRadiusSearch(0.1);   // 搜索半径0.1m
ror.setMinNeighborsInRadius(5); // 至少5个邻居
ror.filter(*cloud_filtered);

我曾经踩过的坑:有一次在隧道里跑数据,隧道墙壁很光滑,激光雷达产生了大量多路径反射点。我用统计滤波怎么调参数都去不掉。后来发现,这些反射点其实有规律——它们都出现在墙壁外侧。最后我用直通滤波把墙壁外侧的区域直接切掉,问题就解决了。所以,去噪不是一招鲜,得结合场景灵活处理。

4.4 预处理流程总结

说了这么多,实际项目中怎么组合使用呢?我一般按这个顺序来:

  1. 直通滤波:先切掉明显不需要的区域(比如远处的天空、地面以下)
  2. 统计滤波:剔除离群噪点
  3. 体素降采样:减少点云数量,同时起到一定的平滑作用

这个顺序不是固定的。如果你点云里噪声特别多,可以先做统计滤波再做直通滤波。如果你对实时性要求高,可以跳过统计滤波,只做直通滤波和体素降采样。

记住:预处理的目标不是「完美」,而是「够用」。点云太干净了反而不好——比如把墙上的纹理点都滤掉了,定位时特征就不够了。我见过有人把点云处理得干干净净,结果建图时匹配失败。所以,适可而止。

好了,点云预处理就聊到这里。下一章我们进入点云配准——怎么把两帧点云拼到一起。那才是SLAM的核心环节。


公众号:蓝海资料掘金营,微信deep3321