3. 体素滤波:体素网格构建、下采样原理、参数选择对点云密度的影响

体素滤波,说白了就是给点云数据“做减法”。

你想想看,激光雷达扫一圈,动辄几十万个点。这么多点,很多是冗余的。比如一面平整的墙,可能几千个点都在描述同一个平面。全留着?没必要,还拖慢算法速度。

体素滤波就是干这个的——在保持物体几何形状的前提下,大幅降低点云密度。我最早接触它是在做室外大场景重建时,原始点云几百万个点,算法跑得跟蜗牛一样。加了体素滤波后,数据量降到十分之一,效果几乎没变。嗯,从那时起,我就把这步当成标配了。

3.1 体素网格构建

体素,就是“体积像素”。你可以把它想象成三维空间里的小方块。

构建过程很简单:

  1. 确定包围盒:找到点云在X、Y、Z三个方向上的最小和最大值,形成一个长方体。
  2. 设定体素大小:比如每个小方块边长0.1米。这个参数很关键,后面会细说。
  3. 划分网格:把包围盒切成一个个小立方体。每个立方体就是一个体素。
  4. 分配点云:遍历所有点,根据坐标计算它落在哪个体素里。

举个例子。假设点云X范围是0到10米,体素大小设为1米。那X方向就有10个体素。每个点进来,用 floor(x / 体素大小) 就能算出它在哪个格子。

核心思想:体素网格把连续空间离散化了。每个体素是一个“桶”,装着一堆空间上邻近的点。

3.2 下采样原理

体素建好了,怎么“下采样”?

方法其实就两种主流做法:

  • 体素重心法:计算每个体素内所有点的重心(坐标平均值),用这个重心点代表整个体素。这是最常用的方法,精度高。
  • 体素中心法:直接用体素的几何中心点。速度快,但会丢失一些细节。

我个人习惯用重心法。为什么?因为它在降采样时保留了原始点云的局部分布特征。我在做室内SLAM时对比过,重心法建出来的地图,墙面更平整,边缘更清晰。

代码实现其实不复杂。PCL里一行就搞定:

// PCL 体素滤波示例
pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(cloud_input);
voxel_filter.setLeafSize(0.1f, 0.1f, 0.1f);  // 体素大小
voxel_filter.filter(*cloud_filtered);

如果你自己实现,核心逻辑就是:

// 伪代码:体素下采样
for each point in cloud:
    idx_x = floor(point.x / leaf_size_x)
    idx_y = floor(point.y / leaf_size_y)
    idx_z = floor(point.z / leaf_size_z)
    voxel_key = (idx_x, idx_y, idx_z)
    voxel_map[voxel_key].push_back(point)

for each voxel in voxel_map:
    centroid = mean(voxel.points)
    downsampled_cloud.push_back(centroid)

你看,逻辑很直白。但实际工程中,坑都在参数选择上。

3.3 参数选择对点云密度的影响

体素滤波最核心的参数就是体素大小(也叫叶子大小)。

体素大小直接影响输出点云的密度:

体素大小 输出点云密度 适用场景
0.01m (1cm) 极高密度 精细建模、小物体扫描
0.05m (5cm) 中等密度 室内SLAM、一般场景
0.10m (10cm) 较低密度 室外大场景、快速建图
0.20m (20cm) 稀疏 远距离感知、粗略定位

体素设得太小,比如1cm,那降采样效果微乎其微。点云还是那么密,算法该慢还是慢。

体素设得太大,比如50cm,那一个体素可能覆盖了墙面、桌角、地面。重心点算出来,细节全丢了。我曾经在项目里吃过这个亏——体素设太大,结果墙角都变圆了,特征匹配直接失败。

我的经验:体素大小一般取场景中最小物体尺寸的1/5到1/10。比如室内场景,最小物体是椅子腿(约5cm粗),那体素设1cm到2cm比较合适。

除了体素大小,还有两个参数值得注意:

  • 最小点数阈值:有些体素里只有一两个点,可能是噪声。可以设置一个阈值,点数少于N的体素直接丢弃。
  • 是否保留空体素:默认不保留。但如果你做的是占据网格地图,空体素也有意义。

避坑指南:我曾经在动态场景中用过体素滤波,结果移动的人被“平均”到了体素里,产生了一堆幽灵点。后来我加了运动检测,先滤掉动态物体再做体素滤波,问题就解决了。

3.4 体素滤波的变种与进阶

标准的体素滤波虽然好用,但有些场景下不够灵活。这里提几个变种:

  • 自适应体素滤波:体素大小不固定。点云密集的地方用小体素,稀疏的地方用大体素。适合远近距离混合的场景。
  • 近似体素滤波:不精确计算重心,而是用体素内第一个点或随机一个点。速度更快,适合实时性要求高的场景。
  • 体素化+法线估计:先体素滤波,再在每个体素内估计法线。这样降采样后的点云还保留了法线信息,方便后续配准。

我个人在自动驾驶项目中用过自适应体素滤波。近处车体用小体素保留细节,远处路面用大体素减少计算量。效果比固定体素好不少。

3.5 本章知识体系

下面这张图总结了体素滤波的核心逻辑:

体素滤波知识体系 原始点云 步骤1:确定包围盒 & 划分体素网格 步骤2:将点分配到对应体素 步骤3:选择下采样策略 重心法(推荐) 中心法(快速) 降采样点云 关键参数 • 体素大小(核心) • 最小点数阈值 • 是否保留空体素 应用场景 • SLAM前端降采样 • 点云可视化加速 • 特征提取预处理

体素滤波看似简单,但用好它需要你对场景有理解。体素大小怎么选?下采样策略用哪个?这些没有标准答案,全靠经验积累。

我建议你从0.05m开始试,看看效果。如果点云还是太密,就加大体素;如果细节丢了,就减小体素。多调几次,你就能找到感觉。

小技巧:调试时可以把原始点云和降采样后的点云叠加显示。一眼就能看出体素滤波有没有“伤到”关键结构。

好了,体素滤波就讲到这里。记住它的核心:用空间换时间,用精度换效率。在工程中,这往往是性价比最高的预处理步骤。

可可精品资料库