2. 体素滤波(Voxel Grid):原理详解、体素大小对结果的影响、PCL实现与参数调优、在激光SLAM中的应用

2.1 体素滤波到底在干什么?

体素滤波,说白了就是用一个个小立方体去“装”你的点云。

你想想看,激光雷达扫一圈下来,动辄几十万个点。这么多点里,其实很多是冗余的——比如一面墙,你扫了1000个点,其实用100个点就能描述清楚它的位置。体素滤波就是干这个的:把空间划分成一个个小格子(体素),每个格子里只保留一个代表点。

我个人习惯把体素滤波理解为“点云的下采样”。它不会改变点云的整体形状,但会大幅减少点的数量。嗯,这里要注意:它和随机下采样不一样,体素滤波会考虑点的空间分布,所以保留下来的点更能代表原始点云的结构。

核心思想:用三维网格(体素)对点云空间进行划分,每个体素内只保留一个重心点或中心点。

2.2 体素大小对结果的影响

体素大小是唯一需要你调的参数。但这个参数的影响,比你想象的要大得多。

体素大小 点数保留比例 形状保持 典型场景
0.01m (1cm) ~80-90% 极好 高精度建模、近距离扫描
0.05m (5cm) ~30-50% 良好 室内SLAM、中等距离
0.1m (10cm) ~10-20% 一般 室外SLAM、远距离
0.2m (20cm) ~3-5% 较差 快速预览、粗配准

我在项目中遇到过这样的情况:体素设得太小(比如1cm),点云还是几十万个点,滤波效果不明显;设得太大(比如20cm),墙角的细节全没了,SLAM建图直接飘了。后来我总结了一个经验:体素大小一般取激光雷达角分辨率的2-3倍。比如16线激光雷达,水平分辨率0.2°,10米外的点间距约3.5cm,那体素设到7-10cm就比较合适。

我的小技巧:如果你不确定体素大小,可以先从0.05m开始试。看滤波后的点云还能不能看清墙角和柱子。能看清,就说明体素大小合适。

2.3 PCL实现与参数调优

PCL里实现体素滤波非常简单,核心就是 pcl::VoxelGrid 这个类。我直接上代码,你一看就明白。

#include <pcl/filters/voxel_grid.h>

// 输入点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 输出点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);

// 创建体素滤波器
pcl::VoxelGrid<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setLeafSize(0.05f, 0.05f, 0.05f);  // 体素大小 5cm
sor.filter(*cloud_filtered);

std::cout << "滤波前点数: " << cloud->size() << std::endl;
std::cout << "滤波后点数: " << cloud_filtered->size() << std::endl;

代码就这么几行。但调优的时候,有几个坑我得提醒你:

  • 体素大小不是越大越好:太大了会丢失特征,SLAM里容易导致匹配失败。
  • 三个方向可以不一样:如果你的点云在Z轴方向变化不大(比如地面),可以把Z轴的体素设大一点,X和Y设小一点。
  • 注意点云密度不均匀:近处的点很密,远处的点很疏。体素滤波对近处降采样多,远处降采样少,这其实是好事——保留了远处的细节。

我曾经踩过的坑:有一次做室外SLAM,我把体素设成了0.2m,结果回环检测一直失败。后来发现是因为体素太大,把一些关键的路标点(比如路灯杆)给滤掉了。从那以后,我养成了一个习惯:滤波后先可视化看一眼,确认关键特征还在不在。

2.4 在激光SLAM中的应用

体素滤波在激光SLAM里几乎是标配。为什么?因为SLAM的核心是帧间匹配,而匹配算法的计算量跟点数成正比。你想想看,一帧10万点的点云,跟一帧1万点的点云,做ICP的速度能差10倍。

我常用的做法是:

  1. 前端匹配前:对当前帧做体素滤波,减少点数,提高匹配速度。
  2. 关键帧提取:对关键帧也做体素滤波,保证地图的均匀性。
  3. 回环检测:回环检测时用更小的体素(比如0.1m),保留更多特征。

具体到不同的SLAM方案:

  • LOAM系列:一般用0.1-0.2m的体素,因为LOAM本身会提取边缘点和平面点,体素滤波只是辅助降采样。
  • ICP-based SLAM:建议用0.05-0.1m,ICP对点的密度比较敏感,体素太小了计算慢,太大了精度下降。
  • NDT-based SLAM:NDT本身对点云密度不敏感,可以用0.2-0.5m的大体素,速度飞快。

我的经验:在SLAM里,体素滤波最好放在预处理的第一步。先降采样,再做其他滤波(比如离群点移除)。这样能大幅减少后续处理的计算量。

2.5 体素滤波的核心逻辑

下面这张图,是我自己画的体素滤波流程。你看一遍就能记住。

体素滤波核心流程 原始点云 步骤1:计算点云包围盒 步骤2:划分体素网格 步骤3:每个体素内取重心/中心 → 滤波后点云

说白了,体素滤波就是三步:算包围盒、划网格、取代表点。简单粗暴,但非常有效。

2.6 什么时候不该用体素滤波?

体素滤波不是万能的。我遇到过一些场景,用了体素滤波反而坏事:

  • 精细建模:如果你要做高精度三维重建,体素滤波会丢失细节,建议用更高级的采样方法。
  • 特征提取前:有些特征(比如NARF、FPFH)对点的密度很敏感,体素滤波会改变局部密度分布,影响特征描述。
  • 点云已经很稀疏:比如远距离的激光点云,本来就没几个点,再滤波就没了。

最后提醒一句:体素滤波的参数调优,没有银弹。不同的传感器、不同的场景、不同的算法,最佳体素大小都不一样。我的建议是:写一个参数调优脚本,跑一遍不同体素大小的结果,可视化对比,选最合适的那个。

可可精品资料库