在之前的两篇文章中,我们介绍了VoxelGrid和ApproximateVoxelGrid两种体素下采样方法。两者均采用质心近似策略——用体素内所有点的质心替代原始点。但某些场景下,我们需要保留原始点而非计算质心,此时均匀采样滤波器(UniformSampling)便是不二之选。
1. 均匀采样
均匀采样滤波器同样基于体素网格实现,其核心原理与VoxelGrid相似——将三维空间划分为大小相等的体素网格。但两者在代表点选择策略上存在本质区别:
| 特性 |
VoxelGrid |
UniformSampling |
| 代表点选择 |
体素内所有点的质心 |
距离体素中心最近的原始点 |
| 输出点类型 |
新计算的点(质心) |
原始点云中的点 |
| 属性保留 |
需计算属性平均值 |
直接保留原始属性 |
| 计算复杂度 |
需累加求平均 |
仅需距离比较 |
| 适用场景 |
需要平滑效果 |
需保留原始点属性 |
UniformSampling的核心原理——用距离体素中心最近的原始点替代体素内所有点。算法的执行步骤如下:
- 网格划分:根据设定的体素尺寸(搜索半径),将点云的包围盒空间划分为三维体素网格;
- 点云分配:遍历点云中的每个点,根据其坐标计算所属体素索引,将点分配到对应体素中;
- 代表点选择:对于每个非空体素,计算体素内每个点到体素中心的距离,选择距离最近的点作为代表点;
- 点云输出:输出所有代表点,形成下采样后的点云。
值得注意的是,UniformSampling输出的点是原始点云中的真实点,而非计算得到的新点。这一特性在需要保留原始点属性(如法向量、颜色、强度等)的场景中尤为重要。
pcl::UniformSampling类继承自Filter基类,除基类方法外,仅新增了1个核心方法用于设置体素尺寸。
//设置体素尺寸(搜索半径)
//@param radius 体素尺寸,三个方向使用相同值
void setRadiusSearch(double radius);
与VoxelGrid的setLeafSize(lx, ly, lz)不同,UniformSampling仅支持各向同性的体素尺寸,即X、Y、Z三个方向使用相同的尺寸值。这一设计简化了参数设置,但也限制了在非均匀采样场景下的灵活性。
UniformSampling不同于VoxelGrid的std::vector的实现方式,也不同于ApproximateVoxelGrid的固定尺寸的哈希表实现,它采用了 unordered_map 这种数据结构。unordered_map内部其实也是采用哈希表实现。UniformSampling使用体素索引作为哈希键,将点分配到对应体素中。每个体素内仅存储距离体素中心最近的点的索引,而不是所有点的索引。这一设计显著减少了内存占用,同时保持了高效的代表点选择。
3. 参数调整建议
结合算法原理分析该如何调整核心参数,以获得最佳的下采样效果。
- 体素尺寸(radius):该值决定了下采样的粒度,直接影响输出点云的密度和均匀程度。
- 值越大,体素越大,下采样程度越高,点数减少越多,但空间分布越均匀;
- 值越小,体素越小,下采样程度越低,点数保留越多,几何细节保留越好;
- 一般建议设置为点云平均点间距的3-10倍,根据应用需求在点数与精度间权衡。
我常用的参数调整策略是:
- 估算点云密度:计算点云包围盒体积,除以点数得到平均体积,再开立方得到平均点间距;
- 确定初始值:将体素尺寸设为平均点间距的3-5倍作为初始值;
- 迭代调整:
- 若下采样后点数仍然过多,适当增大体素尺寸;
- 若几何细节丢失严重,适当减小体素尺寸;
- 若点云分布不够均匀,检查是否存在密度差异过大的区域,可能需要分区处理。
4. 代码示例
为了更直观地展示UniformSampling的使用方法,整理示例代码如下:
4.1 基本示例:标准均匀采样
#include <pcl/filters/uniform_sampling.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
int main() {
// 加载点云数据
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("input.pcd", *cloud_in);
// 1. 初始化均匀采样滤波器对象
pcl::UniformSampling<pcl::PointXYZ> us;
us.setInputCloud(cloud_in);
us.setRadiusSearch(0.01);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_out(new pcl::PointCloud<pcl::PointXYZ>);
us.filter(*cloud_out);
// 保存滤波结果
pcl::io::savePCDFile("output.pcd", *cloud_out);
return 0;
}
4.2 高级示例:获取被剔除点的索引
#include <pcl/filters/uniform_sampling.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
int main() {
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("input.pcd", *cloud_in);
// 初始化滤波器,启用被剔除索引提取
pcl::UniformSampling<pcl::PointXYZ> us(true); // 参数true表示提取被剔除索引
us.setInputCloud(cloud_in);
us.setRadiusSearch(0.01);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_out(new pcl::PointCloud<pcl::PointXYZ>);
us.filter(*cloud_out);
// 获取被剔除点的索引
std::vector<int> removed_indices;
us.getRemovedIndices(removed_indices);
std::cout << "Removed points: " << removed_indices.size() << std::endl;
pcl::io::savePCDFile("output.pcd", *cloud_out);
return 0;
}
5. 对比记忆
为了更清晰地理解UniformSampling的特点,将其与VoxelGrid、ApproximateVoxelGrid进行全面对比:
| 对比维度 |
VoxelGrid |
ApproximateVoxelGrid |
UniformSampling |
| 代表点类型 |
质心(新计算点) |
质心(新计算点) |
最近点(原始点) |
| 体素尺寸设置 |
支持各向异性(lx, ly, lz) |
支持各向异性(lx, ly, lz) |
仅支持各向同性(radius) |
| 属性处理 |
可选择平均值或首点值 |
可选择平均值或首点值 |
直接保留原始属性 |
| 数据结构 |
std::vector + 排序 |
固定大小哈希表 |
std::unordered_map |
| 字段过滤 |
支持 |
不支持 |
不支持 |
| 最小点数阈值 |
支持 |
不支持 |
不支持 |
| 布局保存 |
支持 |
不支持 |
不支持 |
| 平滑效果 |
有(质心平滑) |
有(质心平滑) |
无(保留原始点) |
| 计算效率 |
O(n log n) |
O(n) 平均 |
O(n) 平均 |
| 内存占用 |
O(n) 存储所有点索引 |
O(1) 固定大小缓冲区 |
O(m) 仅存储代表点索引 |
| 结果精度 |
精确(无冲突) |
近似(可能哈希冲突) |
精确(无冲突) |
5.1 选择建议
- 选择UniformSampling的场景:
- 需要保留原始点的精确属性(如法向量、颜色、标签等);
- 后续处理依赖原始点索引(如点云配准、特征匹配);
- 需要避免质心计算带来的属性模糊;
- 点云属性不可平均(如分类标签)。
- 选择VoxelGrid的场景:
- 需要精确的下采样结果;
- 需要平滑效果以减少噪声影响;
- 需要各向异性下采样(不同方向不同尺寸);
- 需要字段过滤或最小点数阈值等高级功能;
- 点云属性可以合理平均(如RGB颜色)。
- 选择ApproximateVoxelGrid的场景:
- 追求处理效率且对精度要求不高;
- 大规模点云处理,需要O(n)时间复杂度;
- 内存受限场景,需要固定内存占用;
- 实时处理需求(如实时预览、流式处理)。
6. 总结
本文重点介绍了pcl::UniformSampling类的核心原理、主要参数及与VoxelGrid的差异,并提供了从基础到高级的示例代码。均匀采样滤波器的核心特点是保留原始点而非计算质心,这一特性使其在需要精确保留点属性的场景中具有独特优势。合理选择UniformSampling、VoxelGrid、ApproximateVoxelGrid,可以更好地满足不同应用场景的下采样需求。想了解更多关于后端架构与底层算法的深度解析,欢迎关注云栈社区,共同探讨技术细节。