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

PCL点云库入门——PCL库点云特征之PFH点特征直方图(Point Feature Histograms -PHF)

1、算法原理

        PFH点(Point Feature Histogram)特征直方图的原理涉及利用参数化查询点与邻域点之间的空间差异,并构建一个多维直方图以捕捉点的k邻域几何属性。这个高维超空间为特征表示提供了一个可度量的信息空间,对于点云对应曲面的六维姿态,它具有不变性,并且在不同的采样密度或邻域噪声水平下表现出鲁棒性。PFH特征描述表示法基于点与其k邻域之间的关系以及它们的估计法线,简而言之,它考虑了所有估计法线方向之间的相互作用,试图捕捉最佳的样本表面变化情况,以描述样本的几何特征。因此,合成特征超空间依赖于每个点的表面法线估计质量。图1展示了查询点Pq的PFH计算影响区域,Pq以红色标记并置于圆球中心,半径为r,所有与Pq的距离小于半径r的k邻元素(即所有点)都相互连接在一个网络中。最终的PFH特征描述子是通过计算邻域内所有点对之间关系得到的直方图,公用有K*(K+1)/2个点对。

 图1 

        PFH特征描述实现的步骤如下:

        第1步:构建局部坐标系

        为了描述两点Ps和Pt及与它们对应的法线Ns和Nt之间的相对偏差,我们在其中一个点上定义一个固定的 Darboux标系,为了使该坐标架被唯一的确定,如图2所示。

图2 

        其中u,v,w具体如下:

        其中,Ps被定义为源点,Pt被定义为目标点。源点即Ps的选择是使得其关联的法线与连接两点的直线之间的角度最小。 

        第2步:特征描述

        在第1步中的vuw坐标系下,法线Ns和Nt之间的相对偏差可以用一组角度特征描述,定义为下:

        其中α和ϕ为单位向量的内积,取值范围为[-1,1],θ为角度,取值范围[-π/2,π/2];d为两点欧式距离,取值范围为(0,2r)。Pq点的PFH特征为α,θ,ϕ,d组成的四元特征。

        第3步:特征编码:

        为计算查询点Pq的PFH特征,根据点的四元特征(α,θ,ϕ,d)构建频率直方图,具体而言,先将<α,θ,ϕ,d>中的每个特征值范围划分为b个子区间(默认为5,可自行计算并设置),则4个特征共有b^4个区间,然后,统计对应特征值落在每个子区间的点的数目,由于<α,ϕ,θ>三个特征都是法线之间的角度息,则它们可归为同一个空间,最后,计算每一个点对的特征值,直方图中对应于该点对四个特征值区间的统计个数加1,效果图3所示。

图3 

在实际工作中距离特征d通常跟设备捕获2.5D深度数据是相关的,临近点的距离d是从视点开始递增的,在不同视角下,这些值的变化意义不大,所以在扫描局部点密度影响特征时,省略距离d效果会更好。在PCL 点云库中 FPH 特征描述只用了三个角度特征,因而特征矢量是5^3=125 维,对应PCL中的pcl::PFHSignature125数据类型。内容如下:

  struct PFHSignature125
  {
    float histogram[125] = {0.f};
    static constexpr int descriptorSize () { return detail::traits::descriptorSize_v<PFHSignature125>; }

    inline PFHSignature125 () = default;

    friend std::ostream& operator << (std::ostream& os, const PFHSignature125& p);
  };

 2、主要成员函数和变量

        1、主要成员变量

         1)、每个角度特征区间的划分数,默认为5。

    int nr_subdiv_;

          2)、点的PFH特征描述的占位符。

    Eigen::VectorXf pfh_histogram_;

          3)、PFH的4元特征的占位符。

    Eigen::Vector4f pfh_tuple_;

          4)、map数据类型,用于优化冗余计算的效率。

    std::map<std::pair<int, int>, Eigen::Vector4f, std::less<>,
 Eigen::aligned_allocator<std::pair<const std::pair<int, int>, Eigen::Vector4f> > > feature_map_;

        5)、中间计算结果保存的对队列。

     std::queue<std::pair<int, int> > key_list_;

       6)、设置最大内部缓存大小。

      unsigned int max_cache_size_;

       7)、设置为true表示使用缓存中间计算结果,减少冗余计算。

     bool use_cache_;

        2、主要成员函数

        1)、设置最大内部缓存大小。默认值为2GB。

 inline void
      setMaximumCacheSize (unsigned int cache_size);

        2)、设置是否使用内部缓存机制减少冗余计算。

inline void
      setUseInternalCache (bool use_cache);

        3)、计算点三个角度和两点之间的距离的4元特征。

 bool computePairFeatures (const pcl::PointCloud<PointInT> &cloud, const pcl::PointCloud<PointNT> &normals,
                           int p_idx, int q_idx, float &f1, float &f2, float &f3, float &f4);

        4)、基于具有法线的三维点的空间邻域,估计给定点的三个角(f1, f2, f3)特征的PFH特征直方图。

    void computePointPFHSignature (const pcl::PointCloud<PointInT> &cloud, const pcl::PointCloud<PointNT> &normals,
                                const std::vector<int> &indices, int nr_split, Eigen::VectorXf &pfh_histogram);

3、主要部分代码注解

        1、计算点对的四元特征

//1、计算点对的四元特征
//
template <typename PointInT, typename PointNT, typename PointOutT> bool
pcl::PFHEstimation<PointInT, PointNT, PointOutT>::computePairFeatures(
       const pcl::PointCloud<PointInT>& cloud, const pcl::PointCloud<PointNT>&  normals,
       int p_idx, int q_idx, float& f1, float& f2, float& f3, float& f4)
{
       pcl::computePairFeatures(cloud.points[p_idx].getVector4fMap(),  normals.points[p_idx].getNormalVector4fMap(),
              cloud.points[q_idx].getVector4fMap(),  normals.points[q_idx].getNormalVector4fMap(),
              f1, f2, f3, f4);
       return (true);
}

//算法具体实现,对应1中第2步的公式;
bool pcl::computePairFeatures(const Eigen::Vector4f& p1, const Eigen::Vector4f& n1,
       const Eigen::Vector4f& p2, const Eigen::Vector4f& n2,
       float& f1, float& f2, float& f3, float& f4)
{
       //距离特征d
       Eigen::Vector4f dp2p1 = p2 - p1;
       dp2p1[3] = 0.0f;
       f4 = dp2p1.norm();
       if (f4 == 0.0f)
       {
              PCL_DEBUG("[pcl::computePairFeatures] Euclidean distance between points  is 0!\n");
              f1 = f2 = f3 = f4 = 0.0f;
              return (false);
       }
       //计算ϕ角度特征
       Eigen::Vector4f n1_copy = n1,
              n2_copy = n2;
       n1_copy[3] = n2_copy[3] = 0.0f;
       float angle1 = n1_copy.dot(dp2p1) / f4;
       // 根据角度大小来选择源点Ps,选择角度最小的为Ps点,std::acos是递减函数
       float angle2 = n2_copy.dot(dp2p1) / f4;
       if (std::acos(std::fabs(angle1)) > std::acos(std::fabs(angle2)))
       {
              // switch p1 and p2
              n1_copy = n2;
              n2_copy = n1;
              n1_copy[3] = n2_copy[3] = 0.0f;
              dp2p1 *= (-1);
              f3 = -angle2;
       }
       else
              f3 = angle1;
       //创建 Darboux 坐标系 u-v-w
       // u = n1; v = (p_idx - q_idx) x u / || (p_idx - q_idx) x u ||; w = u x v
       Eigen::Vector4f v = dp2p1.cross3(n1_copy);
       v[3] = 0.0f;
       float v_norm = v.norm();
       if (v_norm == 0.0f)
       {
              PCL_DEBUG("[pcl::computePairFeatures] Norm of Delta x U is 0!\n");
              f1 = f2 = f3 = f4 = 0.0f;
              return (false);
       }
       // Normalize v
       v /= v_norm;
       Eigen::Vector4f w = n1_copy.cross3(v);
       // Do not have to normalize w - it is a unit vector by construction
       //计算θ精度特征
       v[3] = 0.0f;
       f2 = v.dot(n2_copy);
       w[3] = 0.0f;
       
       //计算α角度特征 4元特征(α,θ,ϕ,d)
       // Compute f1 = arctan (w * n2, u * n2) i.e. angle of n2 in the x=u, y=w  coordinate system
       f1 = std::atan2(w.dot(n2_copy), n1_copy.dot(n2_copy)); // @todo optimize this
       return (true);
}

        2、计算单点PFH特征信息

//2、计算单点PFH特征信息
//
template <typename PointInT, typename PointNT, typename PointOutT> void
pcl::PFHEstimation<PointInT, PointNT, PointOutT>::computePointPFHSignature(
       const pcl::PointCloud<PointInT>& cloud, const pcl::PointCloud<PointNT>&  normals,
       const std::vector<int>& indices, int nr_split, Eigen::VectorXf& pfh_histogram)
{
       int h_index, h_p;
       //清楚上一次的PFH结果
       pfh_histogram.setZero();
       // 分解常数
       float hist_incr = 100.0f / static_cast<float> (indices.size() * (indices.size()  - 1) / 2);
       std::pair<int, int> key;
       bool key_found = false;
       // Iterate over all the points in the neighborhood
       for (std::size_t i_idx = 0; i_idx < indices.size(); ++i_idx)
       {
              for (std::size_t j_idx = 0; j_idx < i_idx; ++j_idx)
              {
                      // 无序点检测,是则跳过
                      if (!isFinite(cloud.points[indices[i_idx]]) ||  !isFinite(cloud.points[indices[j_idx]]))
                             continue;
                      //使用缓存来加速计算
                      if (use_cache_)
                      {
                             // 创建map需要的键值,为点云的索引
                             int p1, p2;
                             //      if (indices[i_idx] >= indices[j_idx])
                             //      {
                             p1 = indices[i_idx];
                             p2 = indices[j_idx];
                             //      }
                             //      else
                             //      {
                             //        p1 = indices[j_idx];
                             //        p2 = indices[i_idx];
                             //      }
                             key = std::pair<int, int>(p1, p2);
                             // 检查是否已经在map中已经估计的点对,就不需要重新计算,避免重复计算
                             std::map<std::pair<int, int>, Eigen::Vector4f,  std::less<>, Eigen::aligned_allocator<std::pair<const std::pair<int, int>,  Eigen::Vector4f> > >::iterator fm_it = feature_map_.find(key);
                             if (fm_it != feature_map_.end())
                             {
                                    //已经有计算的直接用计算的结果
                                    pfh_tuple_ = fm_it->second;
                                    key_found = true;
                             }
                             else
                             {
                                    //没有被估计过,需要计算,这对新的点对的4元特征
                                    if (!computePairFeatures(cloud, normals,  indices[i_idx], indices[j_idx],
                                           pfh_tuple_[0], pfh_tuple_[1],  pfh_tuple_[2], pfh_tuple_[3]))
                                           continue;
                                    key_found = false;
                             }
                      }
                      else//不使用缓存,直接计算所有两对点之间的4元特征
                             if (!computePairFeatures(cloud, normals, indices[i_idx],  indices[j_idx],
                                    pfh_tuple_[0], pfh_tuple_[1], pfh_tuple_[2],  pfh_tuple_[3]))
                                    continue;
                      // 将f1, f2, f3特征归一化,并将它们存储到直方图
                      f_index_[0] = static_cast<int> (std::floor(nr_split *  ((pfh_tuple_[0] + M_PI) * d_pi_)));
                      if (f_index_[0] < 0)         f_index_[0] = 0;
                      if (f_index_[0] >= nr_split) f_index_[0] = nr_split - 1;
                      f_index_[1] = static_cast<int> (std::floor(nr_split *  ((pfh_tuple_[1] + 1.0) * 0.5)));
                      if (f_index_[1] < 0)         f_index_[1] = 0;
                      if (f_index_[1] >= nr_split) f_index_[1] = nr_split - 1;
                      f_index_[2] = static_cast<int> (std::floor(nr_split *  ((pfh_tuple_[2] + 1.0) * 0.5)));
                      if (f_index_[2] < 0)         f_index_[2] = 0;
                      if (f_index_[2] >= nr_split) f_index_[2] = nr_split - 1;
                      // 特征直方图统计
                      h_index = 0;
                      h_p = 1;
                      for (const int& d : f_index_)
                      {
                             h_index += h_p * d;
                             h_p *= nr_split;
                      }
                      //统计直方图结果
                      pfh_histogram[h_index] += hist_incr;
                      //缓存新计算点对的4元特征
                      if (use_cache_ && !key_found)
                      {
                             //保存4元特征信息到map中,根据Key由点的索引值
                             feature_map_[key] = pfh_tuple_;
                             // 将计算过的点对保存起来,为了防止冗余计算
                             key_list_.push(key);
                             // 如果缓存的量操过设置的最大值,需要清除最先缓存的值
                             if (key_list_.size() > max_cache_size_)
                             {
                                    //删除最先缓存的元素。
                                    feature_map_.erase(key_list_.front());
                                    key_list_.pop();
                             }
                      }
              }
       }
}

        3、计算所有点的PFH特征信息

//3、计算所有点的PFH特征信息
//
template <typename PointInT, typename PointNT, typename PointOutT> void
pcl::PFHEstimation<PointInT, PointNT, PointOutT>::computeFeature(PointCloudOut&  output)
{
       // 清除特征map信息
       feature_map_.clear();
       std::queue<std::pair<int, int> > empty;
       std::swap(key_list_, empty);
       //初始化PFH特征维度大小5^3=125,并初始化为{0}
       pfh_histogram_.setZero(nr_subdiv_ * nr_subdiv_ * nr_subdiv_);
    //分配需要参数vector的大小
       std::vector<int> nn_indices(k_);
       std::vector<float> nn_dists(k_);
       output.is_dense = true;
       // 稠密点云数据
       if (input_->is_dense)
       {
              // 对每一个索引值进行遍历执行
              for (std::size_t idx = 0; idx < indices_->size(); ++idx)
              {
                      //点的最近邻域搜索
                      if (this->searchForNeighbors((*indices_)[idx],  search_parameter_, nn_indices, nn_dists) == 0)
                      {
                             //如果最近邻域搜索失败,则将该点的PFH值重置为NAN无效值,跳过该点
                             for (Eigen::Index d = 0; d < pfh_histogram_.size(); ++d)
                                    output.points[idx].histogram[d] =  std::numeric_limits<float>::quiet_NaN();
                             output.is_dense = false;
                             continue;
                      }
                      // 估计每个patch的PFH特征
                      computePointPFHSignature(*surface_, *normals_, nn_indices,  nr_subdiv_, pfh_histogram_);
                      // 将PFH特征特征信息拷贝到输出的数据中
                      for (Eigen::Index d = 0; d < pfh_histogram_.size(); ++d)
                             output.points[idx].histogram[d] = pfh_histogram_[d];
              }
       }
       else
       {
              // Iterating over the entire index vector
              for (std::size_t idx = 0; idx < indices_->size(); ++idx)
              {
                      //点的最近邻域搜索和无效值判断
                      if (!isFinite((*input_)[(*indices_)[idx]]) ||
                             this->searchForNeighbors((*indices_)[idx],  search_parameter_, nn_indices, nn_dists) == 0)
                      {
                             //如果最近邻域搜索失败,则将该点的PFH值重置为NAN无效值,跳过该点
                             for (Eigen::Index d = 0; d < pfh_histogram_.size(); ++d)
                                    output.points[idx].histogram[d] =  std::numeric_limits<float>::quiet_NaN();
                             output.is_dense = false;
                             continue;
                      }
                      // 估计每个patch的PFH特征
                      computePointPFHSignature(*surface_, *normals_, nn_indices,  nr_subdiv_, pfh_histogram_);
                      // 将PFH特征特征信息拷贝到输出的数据中
                      for (Eigen::Index d = 0; d < pfh_histogram_.size(); ++d)
                             output.points[idx].histogram[d] = pfh_histogram_[d];
              }
       }
}

4、算法使用示例

/*****************************************************************//**
* \file   PCLPFHFeaturemain.cpp
* \brief  
*
* \author YZS
* \date   January 2025
*********************************************************************/
#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/features/normal_3d.h>
#include <pcl/features/pfh.h>//包含PFH计算的模块
using namespace std;
void PCLPFHFeature()
{
       //加载点云数据
       pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new  pcl::PointCloud<pcl::PointXYZRGB>());
       std::string fileName = "E:/fragment.pcd";
       pcl::io::load(fileName, *cloud);
       std::cout << "Cloud Size:" << cloud->points.size() << std::endl;
      

 //计算点云数据的法向量
       pcl::NormalEstimation<pcl::PointXYZRGB, pcl::Normal> ne;
       ne.setInputCloud(cloud);
       pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree(new  pcl::search::KdTree<pcl::PointXYZRGB>());
       ne.setSearchMethod(tree);
       pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new  pcl::PointCloud<pcl::Normal>);
       ne.setRadiusSearch(0.02);
       ne.compute(*cloud_normals);
    

   //计算点云数据的PFH特征信息
       //pcl::PFHEstimation PFH特征估计对象
       pcl::PFHEstimation<pcl::PointXYZRGB, pcl::Normal, pcl::PFHSignature125>  pfh_estimation;
       // 设置需要计算PFH的点云数据
       pfh_estimation.setInputCloud(cloud);
       // 设置需要计算PFH的点云数据对应的法向量
       pfh_estimation.setInputNormals(cloud_normals);
    //设置最近KDtree方法
       pfh_estimation.setSearchMethod(tree);
       //创建保存PFH特征信息的对象pfh_features
       pcl::PointCloud<pcl::PFHSignature125>::Ptr pfh_features(new  pcl::PointCloud<pcl::PFHSignature125>);
       //设置搜索的邻域半径
       pfh_estimation.setRadiusSearch(0.08);
       //设置启用缓存计算结果,加快速度
       pfh_estimation.setUseInternalCache(true);
       // 计算PFH特征信息将结构保存到pfh_features中
       pfh_estimation.compute(*pfh_features);
      

 // 输出索引为2000的PFH特征信息
       pcl::PFHSignature125 descriptor = (*pfh_features)[2000];
       std::cout << descriptor << std::endl;
}
int main(int argc, char* argv[])
{
       PCLPFHFeature();
       std::cout << "Hello PCL World!" << std::endl;
       std::system("pause");
       return 0;
}

结果:只输出部分

5、PFH特征的的优缺点

1、PFH的优点

        1、该方法的优点在于对噪声和遮挡具有较强的抗干扰能力,适用于复杂场景下的三维物体识别与分类。

2、PFH的缺点:

        1、计算复杂度较高。假定点云P由n个点构成,且点云分布均匀,每个点在半径为r的邻域内平均能找到k个近邻点。那么,对于每个点计算点特征直方图(PFH)的时间复杂度为O(k²),因此,整个点云的PFH理论计算复杂度为O(nk²)。在实时或接近实时的应用场景中,对于密集点云的PFH计算,O(nk²)的计算复杂度显然不尽如人意,成为了一个显著的性能瓶颈。

        2、存在大量重复计算。在计算PFH的三个特征元素时,邻域内任意两点都需要分别计算其三个特征元素值。即便相邻点的邻域可能不完全相同,但它们往往包含许多相同的近邻点(随着邻域半径的增加,这种重复的近邻点会更多)。这些共同的近邻点会被重复配对并计算特征元素值。尽管相关论文和PCL库中引入了高速缓存机制,将重复计算的开销转化为查找开销,但整体效率提升并不显著。

        至此完成第十四节PCL库点云特征之PFH点特征直方图学习,下一节我们将进入《PCL库中点云特征之FPFH特征描述》的学习。 


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

相关文章:

  • Excel中公式和函数的区别
  • 蓝桥杯 第十五届 研究生组 第二题 召唤数学精灵
  • 【linux系统之redis6】redis的安装与初始化
  • 51单片机——共阴数码管实验
  • Mybatis(day09)
  • 【HarmonyOS应用开发——ArkTS语言】购物商城的实现【合集】
  • 什么是 pybind11?
  • Docker学习记录:装tomcat
  • CSS——16. nth—child序列选择器1
  • 数学建模入门——描述性统计分析
  • VS [Visual Studio] “共享组件、工具和SDK“路径修改方法
  • 学习笔记 : MySQL进阶篇一之架构和日志文件
  • win中编译ffmpeg libheif x264等库
  • Zookeeper是如何保证事务的顺序一致性的?
  • 在 .Net 8.0 中使用 AJAX 在 ASP.NET Core MVC 中上传文件
  • 数字的进制表示及转换
  • 59.在 Vue 3 中使用 OpenLayers 绘制正方形、矩形、六芒星
  • CatBoost算法详解与PyTorch实现
  • el-table-fixed滚动条被遮挡导致滚动条无法拖动
  • 并行计算-申请、创建图像界面虚拟服务器【VNC Viewer 连接灰屏问题 (能够连接上,但全是灰点,没有任何菜单、按钮,鼠标变为x)】
  • 尝试飞桨的PaddleHelix螺旋桨生物计算框架(失败)
  • 前端笔记:vscode Vue nodejs npm
  • 分布式系统中的CAP理论(也称为 Brewer‘s 定理)
  • Oracle job(定时任务)
  • 基于Matlab的变压器仿真模型建模方法(13):单相升压自耦变压器的等效电路和仿真模型
  • git merge rebase