当前位置: 首页 > article >正文

PCL点云库入门——PCL库点云滤波算法之体素滤波(VoxelGrid)

1、体素滤波算法原理

        体素滤波VoxelGrid算法是一种专门用于三维点云数据简化的有效方法。其核心思想是通过将三维空间划分为一系列等大的立方体单元,即体素(Voxel),来减少点云数据的密度。具体来说,算法首先设定一个体素的大小,这个大小决定了数据简化的程度。体素越大,保留的点云数据越少,数据简化程度越高;反之,体素越小,保留的点云数据越多,数据简化程度越低。然后,算法将原始的点云数据映射到这些体素中。对于每个体素,算法计算其内部所有点的质心(或平均位置),并将该质心点作为该体素的代表点。这样,原始的点云数据就被压缩为一系列体素的代表点,从而实现了数据的简化。具体的执行步骤如下:

        第1步:根据输入的点云数据,获取在X、Y、Z三个方向上的边界值Xmax、Ymax、Zmax和Xmin、Ymin、Zmin。

        第2步:根据在X、Y、Z坐标轴上的最大值和最小值,计算出点云数据的AABB包围盒边长Lx、Ly和Lz。

        第3步:根据设置体素的边长Cx、Cy、Cz,将点云数据在X、Y、Z坐标轴平均分层M、N、O分,则点云数据被分为M*N*O个体素,Vnum=M*N*O。

其中,表示向下取整,Vnum为总的体素个数。 

        第4步:计算每个点云数据所属那个体素,体素的三维索引号为(i,j,k),其中:

        为了方便算法的后序计算,常将体素的三维索引号转换为的成一维 索引Idx(类似于HashMap的操作):

Idx=i*A+j*B+k*C

其中:i,j,k为三维索引号,A、B、C设置的值,具体看情况而设定。

        第5步:对每个体素内包含的点云数据进行处理,计算每个体素点云数据的重心Gijk,以重心代替该体素内的所有点;如重心点不存在,则用体素内距离所求重心最近的数据点代替该体素内的所有点,至此完成整个滤波算法过程。其中Gijk计算式子为:

2、主要成员变量和函数

        1、主要成员变量

        1)、 划分体素的尺寸大小

      Eigen::Vector4f leaf_size_;
    
      //出于效率考虑,存储体素大小的倒数
      Eigen::Array4f inverse_leaf_size_;

        2)、是否所有字段进行下采样,则设置为true,如果只是XYZ,则设置为false

      bool downsample_all_data_;

        3)、是否要将体素局部信息需要保存在 leaf_layout中,则要则设置为true。

      bool save_leaf_layout_;

        4)、保存体素局部信息,用于在当前位置快速访问领域信息

       std::vector<int> leaf_layout_;

        5)、 滤波名字

        std::string filter_field_name_;

        6)、 最小、大允许的过滤值

      double filter_limit_min_;
   
      //最大允许的过滤值
      double filter_limit_max_;

        7)、如果我们想要滤波相反的数据结果(filter_limit_min_;  filter_limit_max_),则设置为true。默认值:false

      bool filter_limit_negative_;

        8)、 计算时质心每个体素钟的最小的点云数目

      unsigned int min_points_per_voxel_;

        9)、将三维体素索引转换为一维索引需要的参数,其中min_b_, max_b_为划分位置的起始(包含x,y,z)方向,div_b_为划分的数,对应上面原理中的M、N、O,divb_mul为上面原理中A、B、C值,其中:A=1;B=M;C=M*N。

      Eigen::Vector4i min_b_, max_b_, div_b_, divb_mul_;

      2、主要成员函数

       1)、设置体素网格大小

     inline void setLeafSize (float lx, float ly, float lz)
   
     inline void setLeafSize (const Eigen::Vector4f &leaf_size)

3、主要实现代码注解

template <typename PointT> void
pcl::VoxelGrid<PointT>::applyFilter(PointCloud& output)
{
       // Has the input dataset been set already?
       if (!input_)
       {
              PCL_WARN("[pcl::%s::applyFilter] No input dataset given!\n",  getClassName().c_str());
              output.width = output.height = 0;
              output.points.clear();
              return;
       }
       // Copy the header (and thus the frame_id) + allocate enough space for points
       output.height = 1;                    // downsampling breaks the organized  structure
       output.is_dense = true;                 // we filter out invalid points
       Eigen::Vector4f min_p, max_p;
       // 获取点云数据的在三个坐标轴上的最小(Xmin,Ymin,Zmin)最大值(Xmax,Ymax,Zmax)
       if (!filter_field_name_.empty()) // If we don't want to process the entire  cloud...
              getMinMax3D<PointT>(input_, *indices_, filter_field_name_,  static_cast<float> (filter_limit_min_), static_cast<float> (filter_limit_max_), min_p,  max_p, filter_limit_negative_);
       else
              getMinMax3D<PointT>(*input_, *indices_, min_p, max_p);
       // 在给定体素尺寸大小的情况下,检查设置的参数是否太小
       std::int64_t dx = static_cast<std::int64_t>((max_p[0] - min_p[0]) *  inverse_leaf_size_[0]) + 1;
       std::int64_t dy = static_cast<std::int64_t>((max_p[1] - min_p[1]) *  inverse_leaf_size_[1]) + 1;
       std::int64_t dz = static_cast<std::int64_t>((max_p[2] - min_p[2]) *  inverse_leaf_size_[2]) + 1;
       if ((dx * dy * dz) >  static_cast<std::int64_t>(std::numeric_limits<std::int32_t>::max()))
       {
              PCL_WARN("[pcl::%s::applyFilter] Leaf size is too small for the input  dataset. Integer indices would overflow.", getClassName().c_str());
              output = *input_;
              return;
       }
       // 计算最小和最大边界框值,向下取整
       min_b_[0] = static_cast<int> (std::floor(min_p[0] * inverse_leaf_size_[0]));
       max_b_[0] = static_cast<int> (std::floor(max_p[0] * inverse_leaf_size_[0]));
       min_b_[1] = static_cast<int> (std::floor(min_p[1] * inverse_leaf_size_[1]));
       max_b_[1] = static_cast<int> (std::floor(max_p[1] * inverse_leaf_size_[1]));
       min_b_[2] = static_cast<int> (std::floor(min_p[2] * inverse_leaf_size_[2]));
       max_b_[2] = static_cast<int> (std::floor(max_p[2] * inverse_leaf_size_[2]));
       // 计算沿所有轴所需的划分数,如上公式中的(M、N、O)
       div_b_ = max_b_ - min_b_ + Eigen::Vector4i::Ones();
       div_b_[3] = 0;
       // 计算体素1维idx所需要的参数
       divb_mul_ = Eigen::Vector4i(1, div_b_[0], div_b_[0] * div_b_[1], 0);
       // 用于点和所属体素的存储
       std::vector<cloud_point_index_idx> index_vector;
       index_vector.reserve(indices_->size());
       // 如果不想处理整个云,而是先过滤远离视点的点,相当于预处理
       if (!filter_field_name_.empty())
       {
              // 获取滤波的字段
              std::vector<pcl::PCLPointField> fields;
              int distance_idx = pcl::getFieldIndex<PointT>(filter_field_name_,  fields);
              if (distance_idx == -1)
                      PCL_WARN("[pcl::%s::applyFilter] Invalid filter field name.  Index is %d.\n", getClassName().c_str(), distance_idx);
              // First pass: go over all points and insert them into the index_vector  vector
              // with calculated idx. Points with the same idx value will contribute  to the
              // same point of resulting CloudPoint
              for (std::vector<int>::const_iterator it = indices_->begin(); it !=  indices_->end(); ++it)
              {
                      if (!input_->is_dense)
                             // Check if the point is invalid
                             if (!std::isfinite(input_->points[*it].x) ||
                                    !std::isfinite(input_->points[*it].y) ||
                                    !std::isfinite(input_->points[*it].z))
                                    continue;
                      // 获取字段的值
                      const std::uint8_t* pt_data = reinterpret_cast<const  std::uint8_t*> (&input_->points[*it]);
                      float distance_value = 0;
                      memcpy(&distance_value, pt_data + fields[distance_idx].offset,  sizeof(float));
                      //对字段的值根据设置的限制进行滤波,类似于直通滤波操作
                      if (filter_limit_negative_)
                      {
                             // Use a threshold for cutting out points which inside  the interval
                             if ((distance_value < filter_limit_max_) &&  (distance_value > filter_limit_min_))
                                    continue;
                      }
                      else
                      {
                             // Use a threshold for cutting out points which are too  close/far away
                             if ((distance_value > filter_limit_max_) ||  (distance_value < filter_limit_min_))
                                    continue;
                      }
                      //计算点所属体素的三维ID号(i,j,l)
                      int ijk0 = static_cast<int> (std::floor(input_->points[*it].x *  inverse_leaf_size_[0]) - static_cast<float> (min_b_[0]));
                      int ijk1 = static_cast<int> (std::floor(input_->points[*it].y *  inverse_leaf_size_[1]) - static_cast<float> (min_b_[1]));
                      int ijk2 = static_cast<int> (std::floor(input_->points[*it].z *  inverse_leaf_size_[2]) - static_cast<float> (min_b_[2]));
                      // 计算点所属体素的1维ID号(idx),并将点云和所属体素的1维idx保存起来
                      int idx = ijk0 * divb_mul_[0] + ijk1 * divb_mul_[1] + ijk2 *  divb_mul_[2];
                      index_vector.emplace_back(static_cast<unsigned int> (idx), *it);
              }
       }
       // No distance filtering, process all data
       else
       {
       //第一步:遍历所有点并将它们插入到index_vector容器中
              for (std::vector<int>::const_iterator it = indices_->begin(); it !=  indices_->end(); ++it)
              {
                      if (!input_->is_dense)
                             // Check if the point is invalid
                             if (!std::isfinite(input_->points[*it].x) ||
                                    !std::isfinite(input_->points[*it].y) ||
                                    !std::isfinite(input_->points[*it].z))
                                    continue;
                      //计算点所属体素的三维ID号(i,j,l)
                      int ijk0 = static_cast<int> (std::floor(input_->points[*it].x *  inverse_leaf_size_[0]) - static_cast<float> (min_b_[0]));
                      int ijk1 = static_cast<int> (std::floor(input_->points[*it].y *  inverse_leaf_size_[1]) - static_cast<float> (min_b_[1]));
                      int ijk2 = static_cast<int> (std::floor(input_->points[*it].z *  inverse_leaf_size_[2]) - static_cast<float> (min_b_[2]));
                      // 计算点所属体素的1维ID号(idx),并将点云和所属体素的1维idx保存起来
                      int idx = ijk0 * divb_mul_[0] + ijk1 * divb_mul_[1] + ijk2 *  divb_mul_[2];
                      index_vector.emplace_back(static_cast<unsigned int> (idx), *it);
              }
       }
       //第二步:对index_vector容器进行排序,具体相同信息的所有点将彼此相邻,因为idx相近
       std::sort(index_vector.begin(), index_vector.end(),  std::less<cloud_point_index_idx>());
       // 第三步: 计算输出体素信息
       // 要跳过所有体素idx相同的
       unsigned int total = 0;
       unsigned int index = 0;
       // first_and_last_indices_vector[i] represents the index in index_vector of the  first point in
       // index_vector belonging to the voxel which corresponds to the i-th output  point,
       // and of the first point not belonging to.
       std::vector<std::pair<unsigned int, unsigned int> >  first_and_last_indices_vector;
       // Worst case size
       first_and_last_indices_vector.reserve(index_vector.size());
       while (index < index_vector.size())
       {
              unsigned int i = index + 1;
              //跳过体素idx相同的
              while (i < index_vector.size() && index_vector[i].idx ==  index_vector[index].idx)
                      ++i;
              
              if (i - index >= min_points_per_voxel_)
              {
                      ++total;
                      //将有相同体素idx的点云索引起点和终点保存起来
                      first_and_last_indices_vector.emplace_back(index, i);
              }
              index = i;
       }
       // 第四步:计算质心,将其插入最终位置
       output.points.resize(total);
       if (save_leaf_layout_)
       {
              //保存邻域信息初始化
              try
              {
                      // Resizing won't reset old elements to -1.  If leaf_layout_ has  been used previously, it needs to be re-initialized to -1
                      std::uint32_t new_layout_size = div_b_[0] * div_b_[1] *  div_b_[2];
                      //This is the number of elements that need to be re-initialized  to -1
                      std::uint32_t reinit_size = std::min(static_cast<unsigned int>  (new_layout_size), static_cast<unsigned int> (leaf_layout_.size()));
                      for (std::uint32_t i = 0; i < reinit_size; i++)
                      {
                             leaf_layout_[i] = -1;
                      }
                      leaf_layout_.resize(new_layout_size, -1);
              }
              catch (std::bad_alloc&)
              {
                      throw PCLException("VoxelGrid bin size is too low; impossible to  allocate memory for layout",
                             "voxel_grid.hpp", "applyFilter");
              }
              catch (std::length_error&)
              {
                      throw PCLException("VoxelGrid bin size is too low; impossible to  allocate memory for layout",
                             "voxel_grid.hpp", "applyFilter");
              }
       }
       index = 0;
       for (const auto& cp : first_and_last_indices_vector)
       {
              // 计算输入点的质心值,在index_vector数组中具有相同的idx值
              unsigned int first_index = cp.first;
              unsigned int last_index = cp.second;
              // 保存质心的索引
              if (save_leaf_layout_)
                      leaf_layout_[index_vector[first_index].idx] = index;
              //非全采样
              if (!downsample_all_data_)
              {
                      // 计算值质心
                      Eigen::Vector4f centroid(Eigen::Vector4f::Zero());
                      for (unsigned int li = first_index; li < last_index; ++li)
                             centroid +=  input_->points[index_vector[li].cloud_point_index].getVector4fMap();
                      centroid /= static_cast<float> (last_index - first_index);
                      output.points[index].getVector4fMap() = centroid;
                      
              }
              else
              {
                      CentroidPoint<PointT> centroid;
                      // 计算值质心
                      for (unsigned int li = first_index; li < last_index; ++li)
                             centroid.add(input_->points[index_vector[li].cloud_point_index]);
                      //将质心结果保存到输出数据中
                      centroid.get(output.points[index]);
              }
              ++index;
       }
       output.width = static_cast<std::uint32_t> (output.points.size());
}

4、代码示例

        新建文件PCLVoxelGridmain.cpp,内容如下:

/*****************************************************************//**
* \file   PCLVoxelGridmain.cpp
* \brief
*
* \author YZS
* \date   December 2024
*********************************************************************/
#include<iostream>
#include <vector>
#include <ctime>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/auto_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/voxel_grid.h>
using namespace std;


void PCLVoxelGrid()
{
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new  pcl::PointCloud<pcl::PointXYZRGB>());
	std::string fileName = "E:/PCLlearnData/10/fragment.pcd";
	pcl::io::load(fileName, *cloud);
	std::cout << "Cloud Size:" << cloud->points.size() << std::endl;
	//保存滤波后的结果
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloudFilter(new  pcl::PointCloud<pcl::PointXYZRGB>());
	pcl::VoxelGrid<pcl::PointXYZRGB> voxelFilter;// 体素滤波器类对象
	voxelFilter.setInputCloud(cloud);
	voxelFilter.setLeafSize(0.05f, 0.05f, 0.05f);//设置体素尺寸大小
	voxelFilter.filter(*cloudFilter); // 执行滤波,并且保存结果到cloudFilter中

	std::cout << "filter Cloud Size:" << cloudFilter->points.size() << std::endl;
	//结果可视化
// PCLVisualizer对象
	pcl::visualization::PCLVisualizer viewer("FilterVIS");
	//创建左右窗口的ID v1和v2
	int v1(0);
	int v2(1);
	//设置V1窗口尺寸和背景颜色
	viewer.createViewPort(0.0, 0.0, 0.5, 1, v1);
	viewer.setBackgroundColor(0, 0, 0, v1);
	//设置V2窗口尺寸和背景颜色
	viewer.createViewPort(0.5, 0.0, 1, 1, v2);
	viewer.setBackgroundColor(0.1, 0.1, 0.1, v2);
	// 添加2d文字标签
	viewer.addText("v1", 10, 10, 20, 1, 0, 0, "Txtv1", v1);
	viewer.addText("v2", 10, 10, 20, 0, 1, 0, "Txtv2", v2);
	//设置cloud1的渲染属性,点云的ID和指定可视化窗口v1
	viewer.addPointCloud(cloud, "cloud1", v1);
	viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud1");
	//设置cloud2的渲染属性,点云的ID和指定可视化窗口v2
	viewer.addPointCloud(cloudFilter, "cloud2", v2);
	viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud2");
	// 可视化循环主体
	while (!viewer.wasStopped())
	{
		viewer.spinOnce();
	}
}
int main(int argc, char* argv[])
{
	PCLVoxelGrid();
	std::cout << "Hello World!" << std::endl;
	std::system("pause");
	return 0;
}

        结果:

        注意:由于使用质心近似替代原始点,可能会导致点的位置发生微小移动,这可能会影响某些应用中对位置精度要求较高的场景。

        至此完成第十节PCL库点云滤波算法之体素滤波(VoxelGrid)学习,下一节我们将进入《PCL库中点云滤波之半径滤波(RadiusOutlierRemoval)》的学习。  


http://www.kler.cn/a/459860.html

相关文章:

  • LoRA微调系列笔记
  • Linux实验报告12-Apache服务器的配置
  • 【MATLAB第111期】基于MATLAB的sobol全局敏感性分析方法二阶指数计算
  • gitlab 还原合并请求
  • 数据可视化分析详解
  • 手机更换屏幕后,会被防控软件识别为模拟器!!
  • Unity学习笔记(四)如何实现角色攻击、组合攻击
  • Jenkins使用记录
  • FPGA多路MIPI转FPD-Link视频缩放拼接显示,基于IMX327+FPD953架构,提供2套工程源码和技术支持
  • 使用 Scrapy 抓取网页数据
  • WebRTC:实现浏览器与移动应用的实时通信
  • 【Unity】 HTFramework框架(五十七)通过Tag、Layer批量搜索物体
  • Perl语言的软件工程
  • 自动化办公-将 Excel 的 Sheet 页拆分成单独的 Excel 文件
  • chatgpt model spec 2024
  • ubuntu20.04 调试bcache源码
  • 【C++】BC89 包含数字9的数
  • Docker搭建MySQL
  • C++ 设计模式:命令模式(Command Pattern)
  • Python 网络爬虫 全面解析
  • 基于Spring Boot的宠物领养系统的设计与实现(代码+数据库+LW)
  • 2025新一代智能终端发展全面解析:技术创新、应用拓展与产业生态演变
  • window如何将powershell以管理员身份添加到右键菜单?(按住Shift键显示)
  • git将本地项目上传到远程仓库
  • HCIA笔记10--VLAN间互访、PPPoE协议
  • 把Huggingface下载的arrow数据集转化为json格式