3、点云预处理:体素滤波降采样、统计滤波去噪、半径滤波去噪、直通滤波

各位同学,咱们今天聊聊点云预处理。说实话,很多刚入行的朋友拿到ToF点云就直接往上怼配准算法,结果跑出来一堆鬼影。我当年也干过这种傻事——在实验室里对着一个布满噪点的点云图发呆,配准结果歪得离谱。后来才明白,预处理这一步要是没做好,后面全是白费功夫。

点云预处理,说白了就是给原始数据「洗个澡」。ToF传感器采集到的点云,通常有几个毛病:数据量太大、噪声点多、离群点乱飞。咱们今天要讲的四种滤波方法,就是专门治这些毛病的。

点云预处理四大滤波方法 原始ToF点云 体素滤波降采样 减少数据量 统计滤波去噪 去除稀疏离群点 半径滤波去噪 基于邻域密度 直通滤波 范围裁剪

3.1 体素滤波降采样——给点云「减肥」

体素滤波,我习惯叫它「点云减肥术」。你想想看,一个ToF传感器扫一帧,动辄几十万个点。这么多点直接拿去配准,计算量大到离谱。体素滤波的思路很简单:把空间切成一个个小立方体(体素),每个立方体里只保留一个代表点。

这个代表点怎么选?通常取体素内所有点的重心,或者直接取中心点。我个人更推荐用重心,因为能保留更多几何信息。我在一个仓储机器人项目里试过,体素边长设成0.02米,点云从30万点直接降到2万点,配准精度几乎没掉。

核心参数:体素边长(leaf size)。边长越大,降采样越狠,但细节丢失也越多。一般建议设为点云平均点间距的2-3倍。
// C++ PCL 示例
pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(cloud);
voxel_filter.setLeafSize(0.02f, 0.02f, 0.02f); // 体素边长2cm
voxel_filter.filter(*cloud_filtered);
我的小技巧:如果你不确定体素边长设多少,可以先算一下点云的平均点间距。用k-d树找每个点的最近邻,取距离的平均值。体素边长设成这个值的2倍,效果通常不错。

3.2 统计滤波去噪——揪出「孤魂野鬼」

统计滤波,专门对付那些飘在空中的孤立噪点。ToF传感器在边缘或者反光表面容易产生一些「幽灵点」,这些点周围通常没什么邻居。统计滤波的原理就是:对每个点,计算它到k个最近邻的平均距离。如果这个距离太大,说明它是离群点,直接干掉。

为什么会这样?因为有效点云通常聚集在一起,而噪声点往往是孤立的。我记得有一次在户外扫一个金属雕塑,阳光干扰导致一堆飞点。统计滤波一把梭哈,干净多了。

注意:k值(邻居数量)和标准差倍数(stddev mul)这两个参数要配合着调。k太小了容易误删有效点,k太大了又漏掉噪声。我一般先设k=50,标准差倍数=1.0,看效果再微调。
// C++ PCL 示例
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50);          // 考虑50个最近邻
sor.setStddevMulThresh(1.0); // 标准差倍数
sor.filter(*cloud_filtered);
避坑指南:我曾经在一个高噪声场景里把标准差倍数设成0.5,结果把墙上的点都删了,配准时直接崩了。记住,参数越严格,删得越多,但不一定越好。

3.3 半径滤波去噪——另一种「查户口」方式

半径滤波和统计滤波有点像,但思路不同。它是在每个点周围画一个固定半径的球,数一数球里有多少个邻居。如果邻居数量少于某个阈值,就认为这个点是离群点。

你想想看,这招特别适合处理那些「扎堆」的噪声。比如ToF传感器在玻璃边缘产生的噪点,它们往往是一小撮一小撮出现的。统计滤波可能把它们当成有效点,但半径滤波一看——周围邻居太少,直接干掉。

关键区别:统计滤波看的是「距离分布」,半径滤波看的是「绝对密度」。前者适合均匀分布的噪声,后者适合局部稀疏的噪声。
// C++ PCL 示例
pcl::RadiusOutlierRemoval<pcl::PointXYZ> ror;
ror.setInputCloud(cloud);
ror.setRadiusSearch(0.05);   // 搜索半径5cm
ror.setMinNeighborsInRadius(10); // 至少10个邻居
ror.filter(*cloud_filtered);
我的经验:半径滤波的参数设置和点云密度强相关。如果点云很密,半径可以设小一点,邻居阈值设高一点。反之亦然。我一般先看点云的平均密度,再反推参数。

3.4 直通滤波——「切蛋糕」一样简单

直通滤波,说白了就是切掉你不需要的部分。比如你只关心地面以上1米到2米范围内的点云,那就设一个z轴范围,把外面的全扔掉。

这招在预处理里特别实用。我记得做AGV小车导航时,天花板上的点云全是干扰,直通滤波一刀切掉z>2.5米的部分,计算量直接减半。而且它不会改变点云的几何结构,只是裁剪范围。

注意:直通滤波虽然简单,但别乱用。如果你把有效点云也切掉了,后面配准肯定出问题。建议先可视化看一下点云分布,再确定裁剪范围。
// C++ PCL 示例
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");      // 沿z轴裁剪
pass.setFilterLimits(0.5, 2.0);    // 保留0.5m到2.0m范围
pass.filter(*cloud_filtered);
避坑指南:我曾经在直通滤波时忘了设置setFilterLimitsNegative(false),结果把范围内的点全删了,只保留了范围外的。嗯,这个小细节坑了我一下午。

3.5 四种滤波的搭配使用

在实际项目中,我很少只用一种滤波。通常的流程是:

  1. 先直通滤波:把明显不需要的区域切掉,减少后续计算量。
  2. 再体素滤波:降采样,把点云数量压到可控范围。
  3. 最后统计滤波或半径滤波:根据噪声类型选一个,或者两个都用。

这个顺序是我踩过坑之后总结出来的。如果你先做统计滤波再做体素滤波,那些稀疏的离群点被体素滤波一平均,反而可能混进有效点里。顺序很重要。

总结一下:预处理不是越复杂越好,关键是「对症下药」。体素滤波管数据量,统计滤波和半径滤波管噪声,直通滤波管范围。把这四个工具用好,你的点云配准成功率至少提升50%。

好了,这一节的内容就到这里。预处理这块看似基础,但真到了实战中,参数调优往往是最耗时的环节。别急,多试几次就有感觉了。