一、体素滤波:点云下采样的基本功
体素滤波,说白了就是用一个个小立方体把点云空间切碎。每个立方体里只留一个代表点。我刚开始接触点云时,觉得这玩意儿太简单了——不就是降采样嘛。后来踩了不少坑才明白,体素大小选不对,后面SLAM建图直接崩。
嗯,咱们先看原理,再聊实战。
1.1 原理与参数
体素滤波的核心思想:把三维空间划分成固定大小的网格。每个网格内所有点,用它们的重心或中心点代替。
关键参数就两个:
- 体素大小(leaf size):每个小立方体的边长。单位通常和点云一致(米或毫米)。
- 下采样方式:一般用重心近似(更准)或中心点近似(更快)。
我个人习惯用重心近似。为什么?因为中心点可能落在没有点的位置,而重心是真实存在的点。我在项目中遇到过,用中心点近似导致边缘出现空洞,后续配准直接失败。
核心公式:每个体素内的点集 P = {p₁, p₂, ..., pₙ},输出点 p_out = (1/n) * Σ p_i
1.2 下采样对精度的影响
你想想看,下采样本质上是信息丢失。体素越大,丢得越多。但丢得少不一定好——点太多,计算扛不住。
我做过一个实验:用同一组激光雷达数据,分别用0.05m、0.1m、0.2m的体素滤波,然后跑ICP配准。结果如下:
| 体素大小 | 点数(万) | 配准误差(cm) | 耗时(ms) |
|---|---|---|---|
| 0.05m | 12.8 | 0.3 | 85 |
| 0.10m | 3.2 | 0.8 | 22 |
| 0.20m | 0.8 | 2.1 | 6 |
看到了吧?体素从0.05m翻到0.1m,点数降了4倍,误差只涨了0.5cm。但再翻到0.2m,误差直接跳到2cm以上。这就是典型的边际效应——体素太大,结构特征被抹平了。
避坑指南:我曾经在室外大场景用0.5m体素,结果地面点被滤得七零八落,平面拟合直接报错。后来我养成了习惯——先看场景尺度,再定体素大小。
1.3 体素大小选择策略
体素大小怎么选?没有万能公式。但我总结了一套经验法则:
- 看传感器:激光雷达点云,0.1m起步;深度相机点云,0.02m起步。
- 看场景:室内小场景,0.02-0.05m;室外大场景,0.1-0.3m。
- 看任务:配准需要保留细节,体素偏小;建图可以粗一点,体素偏大。
- 看算力:嵌入式平台(如Jetson),体素至少0.1m以上,否则帧率撑不住。
我个人的调试流程:先取场景中最小物体尺寸的1/10作为初始值。比如室内桌子腿直径2cm,体素就设2mm。然后逐步放大,直到精度满足要求为止。
小技巧:如果你不确定体素大小,可以先用0.05m跑一次,看点数。如果点数超过10万,就翻倍。如果低于1万,就减半。迭代两三次就能找到合适值。
1.4 实战代码与性能对比
下面是我常用的PCL体素滤波代码。注意,这里有个坑——很多人忘了设置滤波器对象,导致结果不对。
#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.1f, 0.1f, 0.1f); // 体素大小
sor.filter(*cloud_filtered);
// 输出结果
std::cout << "原始点数: " << cloud->size() << std::endl;
std::cout << "滤波后点数: " << cloud_filtered->size() << std::endl;
这段代码看着简单,但实际部署时要注意三点:
- 体素大小必须三个维度一致?不一定。如果点云在Z轴分布很窄(比如地面点),可以设Z轴体素大一些,加速处理。
- 记得检查输出点云是否为空。体素太大时,可能所有点都被滤掉了。我遇到过这种bug,排查了半天。
- 多线程加速:PCL的VoxelGrid默认单线程。如果你用OpenMP,可以手动并行化,速度提升2-3倍。
性能对比上,我实测过不同体素大小在Jetson Orin上的表现:
| 体素大小 | 原始点数 | 滤波后点数 | 耗时(ms) | 内存占用(MB) |
|---|---|---|---|---|
| 0.05m | 30万 | 8.5万 | 12 | 45 |
| 0.10m | 30万 | 2.1万 | 5 | 18 |
| 0.20m | 30万 | 0.5万 | 2 | 6 |
你看,0.05m到0.1m,耗时降了一半多,内存也省了一大截。但0.1m到0.2m,收益就没那么明显了。所以实际部署时,我一般推荐0.1m作为默认值——平衡了精度和性能。
总结一句话:体素滤波不是越大越好,也不是越小越好。找到那个「够用」的点,才是真本事。