一、算法原理
均匀采样滤波器与体素滤波器类似,都需要将点云空间划分为规则的3D网格(体素voxel)。不同之处在于,均匀采样滤波器不是保留体素内所有点的质心,而是选择距离体素中心最近的点作为代表点输出,能在空间上实现更均匀的采样效果。在实现上,Uniform Sampling算法采用unordered_map数据结构来存储体素信息,算法流程如下所述:
1. 设置点云在空间维度上的采样半径, 对应分辨率
2. 根据点云包围盒边界计算每个维度划分的网格数
3. 对于点云中的每个点,确定每个点的网格索引
4. 对于落在同一体素网格内的点,选择离网格中心最近的点输出
二、底层细节
1. 对于点云中的每个点计算网格索引后,将网格索引和累加器存储到unordered_map类型的leaves_变量中
struct Leaf
{
Leaf() = default;
int idx{ -1 };
unsigned int count{ 0 };
};
/** \brief The 3D grid leaves. */
std::unordered_map<std::size_t, Leaf> leaves_;
Eigen::Vector4i ijk = Eigen::Vector4i::Zero();
ijk[0] = static_cast<int> (std::floor((*input_)[cp].x * inverse_leaf_size_[0]));
ijk[1] = static_cast<int> (std::floor((*input_)[cp].y * inverse_leaf_size_[1]));
ijk[2] = static_cast<int> (std::floor((*input_)[cp].z * inverse_leaf_size_[2]));
// Compute the leaf index
int idx = (ijk - min_b_).dot(divb_mul_);
Leaf& leaf = leaves_[idx];
// Increment the count of points in this voxel
++leaf.count;
2. 对于同一网格索引的点,选择最靠近体素中心的点输出
// Compute the voxel center
Eigen::Vector4f voxel_center = (ijk.cast<float>() + Eigen::Vector4f::Constant(0.5)) * search_radius_;
voxel_center[3] = 0;
// Check to see if this point is closer to the leaf center than the previous one we saved
float diff_cur = ((*input_)[cp].getVector4fMap() - voxel_center).squaredNorm();
float diff_prev = ((*input_)[leaf.idx].getVector4fMap() - voxel_center).squaredNorm();
// If current point is closer, copy its index instead
if (diff_cur < diff_prev)
{
if (extract_removed_indices_)
(*removed_indices_)[rii++] = leaf.idx;
leaf.idx = cp;
}
else
{
if (extract_removed_indices_)
(*removed_indices_)[rii++] = cp;
}
}
removed_indices_->resize(rii);
// Second pass: go over all leaves and copy data
indices.resize(leaves_.size());
for (const auto& leaf : leaves_)
{
if (leaf.second.count >= min_points_per_voxel_)
indices[oii++] = leaf.second.idx;
}
三、PCL代码示例
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/uniform_sampling.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <iostream>
int main(int argc, char** argv)
{
// 1. 加载点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("truck.pcd", *cloud) == -1) {
PCL_ERROR("无法读取 truck.pcd 文件\n");
return -1;
}
std::cout << "原始点云数量: " << cloud->points.size() << std::endl;
// 2. 均匀采样滤波
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
pcl::UniformSampling<pcl::PointXYZ> uniform;
uniform.setInputCloud(cloud);
uniform.setRadiusSearch(0.2f); // 半径 (单位: 米)
uniform.filter(*cloud_filtered); // 直接生成输出点云
std::cout << "滤波后点云数量: " << cloud_filtered->points.size() << std::endl;
// 3. 可视化对比
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("Uniform Sampling Filter Demo"));
int v1(0), v2(1);
// 左窗口:原始点云
viewer->createViewPort(0.0, 0.0, 0.5, 1.0, v1);
viewer->setBackgroundColor(0, 0, 0, v1);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> color_orig(cloud, 255, 255, 255);
viewer->addPointCloud(cloud, color_orig, "original_cloud", v1);
// 右窗口:滤波后点云
viewer->createViewPort(0.5, 0.0, 1.0, 1.0, v2);
viewer->setBackgroundColor(0, 0, 0, v2);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> color_uniform(cloud_filtered, 0, 255, 0);
viewer->addPointCloud(cloud_filtered, color_uniform, "filtered_cloud", v2);
// 设置点大小
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "original_cloud");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "filtered_cloud");
// 循环显示
viewer->initCameraParameters();
while (!viewer->wasStopped()) {
viewer->spinOnce(100);
}
return 0;
}
四、效果展示
五、总结
均匀采样滤波器通过在空间中规则采样,使点云分布更均匀,特别适合特征提取、配准和重建等对点分布要求较高的场景。