3. 点云预处理:体素滤波(Voxel Grid Filter)降采样原理与实现

大家好,我是你们的点云处理讲师。今天我们来聊聊点云预处理里最基础、也最常用的一招——体素滤波降采样。

说实话,我刚入行那会儿,拿到一个激光雷达点云,第一反应就是“哇,数据真多”。但真跑起算法来,CPU直接拉满,帧率掉到个位数。后来我师父跟我说:“你试试体素滤波,把点云‘瘦身’一下。” 嗯,从那以后,我就再也没离开过这个滤波器。

3.1 为什么要降采样?

你想想看,一个64线激光雷达,一秒钟能产生上百万个点。这么多点,直接拿去跑SLAM或者做地面分割,计算量太大了。而且很多点其实是冗余的——比如一面平整的墙壁,你只需要几十个点就能描述它的位置,没必要存几万个点。

降采样的目的,说白了就是:在尽量保留几何结构的前提下,减少点的数量。

体素滤波就是干这个的。它不挑点,不删点,而是把空间划分成一个个小立方体,每个立方体里只保留一个代表点。

3.2 体素滤波的核心原理

体素滤波的原理其实特别直观。我习惯用“切豆腐”来比喻:

  1. 建立三维网格:把整个点云空间,按照你设定的尺寸(比如0.1米),切成一个个小立方体。这些立方体就叫“体素”(Voxel)。
  2. 分配点云:每个点根据它的三维坐标,找到它属于哪个体素。
  3. 计算重心:每个体素里,计算所有点的重心(也就是坐标平均值)。
  4. 输出代表点:用这个重心点,代替体素里所有的原始点。

这样做的好处很明显:体素内的点被“压缩”成了一个点,整体点数大幅下降。而且由于用的是重心,几何形状保留得还不错。

关键参数:体素大小

体素大小直接决定了降采样的力度。设得越大,点越少,但细节丢失也越多。设得太小,降采样效果不明显。我个人经验是:对于室内场景,0.05米到0.1米比较合适;室外大场景,0.2米到0.5米都可以试试。

3.3 体素滤波的数学表达

别怕,数学很简单。假设我们有一个体素,里面包含了 \( n \) 个点,每个点的坐标为 \( (x_i, y_i, z_i) \)。那么滤波后的代表点 \( (X, Y, Z) \) 就是:

X = (x1 + x2 + ... + xn) / n
Y = (y1 + y2 + ... + yn) / n
Z = (z1 + z2 + ... + zn) / n

就这么简单。不过要注意,如果体素里只有一个点,那代表点就是它自己。

3.4 代码实现(Python + Open3D)

在实际项目中,我一般用Open3D库来做体素滤波。它封装得很好,几行代码就能搞定。下面是我常用的代码片段:

import open3d as o3d
import numpy as np

# 读取点云
pcd = o3d.io.read_point_cloud("input.pcd")
print(f"原始点数: {len(pcd.points)}")

# 体素滤波,设置体素大小为0.1米
voxel_size = 0.1
downsampled_pcd = pcd.voxel_down_sample(voxel_size)

print(f"降采样后点数: {len(downsampled_pcd.points)}")

# 保存结果
o3d.io.write_point_cloud("output_downsampled.pcd", downsampled_pcd)

你看,核心就一行 pcd.voxel_down_sample(voxel_size)。但这里有个坑,我踩过好几次:

我曾经踩过的坑:

体素大小设置不当,导致地面点被过度平滑。比如你设了0.5米的体素,地面上的小坑洼、小坡度全被抹平了。后来做地面分割时,算法把斜坡当成了障碍物。所以,体素大小一定要根据你的应用场景来调,别偷懒用默认值。

3.5 体素滤波的优缺点

优点 缺点
实现简单,计算速度快 体素大小需要手动调参
能有效减少点数,保留整体结构 会丢失体素内部的细节信息
对噪声有一定的平滑作用 体素边界处可能出现锯齿效应
不依赖点云密度,通用性强 对于稀疏点云,效果不明显

3.6 体素滤波的变种

除了用重心,还有几种常见的变种:

  • 随机采样:每个体素里随机选一个点。速度更快,但稳定性不如重心法。
  • 中心点采样:用体素的几何中心代替重心。计算量小,但精度略低。
  • 最近邻采样:选离体素中心最近的那个点。能保留原始点的测量值,适合需要保留强度信息的场景。

我个人习惯用重心法,因为它对噪声的鲁棒性最好。但如果你的点云里每个点都带有强度值(比如激光雷达的反射强度),那最近邻采样可能更合适——毕竟强度值不能平均。

3.7 知识体系流程图

下面我用一张SVG图,把体素滤波的核心逻辑串起来,方便你理解:

体素滤波降采样核心流程 输入原始点云 划分体素网格 点云分配到体素 计算体素内重心 输出降采样点云 关键参数:体素大小 室内:0.05~0.1m 室外:0.2~0.5m 代表点计算方法: 1. 重心法(常用) 2. 随机采样 3. 中心点采样 4. 最近邻采样

3.8 实际项目中的建议

最后,分享几个我在实际项目中积累的经验:

  • 先降采样,再处理:我习惯把体素滤波放在预处理的第一步。先降采样,后面的地面分割、聚类、特征提取都会快很多。
  • 体素大小要跟场景匹配:室内小场景,体素设0.05米;室外大场景,可以设0.2米甚至更大。别一个参数打天下。
  • 注意边界效应:体素网格的边界处,点可能会被分配到相邻的体素,导致几何结构出现微小偏移。如果你做高精度建图,可以考虑用重叠体素或者八叉树来缓解。
  • 别忘了保存原始点云:降采样后的点云不可逆。如果你需要原始精度,记得备份一份。

小技巧:

如果你不确定体素大小该设多少,可以先用一个较大的值(比如0.2米)快速跑一遍,看看效果。然后逐步减小,直到找到精度和速度的平衡点。我一般会试3~5个值,选一个最合适的。

好了,体素滤波的原理和实现就讲到这里。说白了,它就是点云处理里的“瘦身操”,简单但实用。下一节我们会聊另一种常用的滤波器——直通滤波,到时候见。

可可精品资料库