部分参考:https://blog.csdn.net/qq_25491201/article/details/51104390
1.点的特征
表面法线和曲率可以好的代表一个点的几何特征。然而它们算得很快,而且算法简单,但是它们不能捕获细节,它们只是点的近邻的几何特征的近似估计。作为一个直接的结论,大多数的场景往往会包括很多有着相似特征的点,这会减少它们所带来的消息量,也容易造成混淆和错误的匹配。
这时候就要一个具备更加丰富信息的特征描述方法——点特征直方图Point Feature Histogram
PFH的目标是通过使用一个点周围的多维直方图来编码一个点的k个最近邻的几何属性。这个高维空间提供了一些有用的特征代表,同时关于6位位姿不变的,同时和可以很好的应对不同的采样密度和近邻的噪声水平。
下面的图展示了一个PFH在计算Pq这个点时候的影响区域图,Pq这个点用红色标记并放在半径为r的圆圈的中央,然后它的所有近邻(离点的距离小于半径r)都通过一个网格来间接的相邻着。下面的PFH描述器作为了一个直方图计算了所有匹配的点之间的关系,有一个O(k方)的复杂度。
在这个区域之内,每两个点可以组成一对(Ps, Pt),我们在这一对点中的其中一个点为原点建立UVW坐标系:
u v w三条基向量以Ps为原点,显然是相互垂直的。上图和公式并不一致,比如V的方向。图仅仅是示意。
使用上面的uvw的坐标系,ns法线和ni法线的差可以转化为下面的3个角度的差异(因为他们的模都是一样的):
d是两点距离的平方
这样我们把这四个值
称为“特征值”,四个特征值的集合组成了点Ps的“特征”。但是经常我们会省略d,会获得更好的效果,因此一个点对应三个特征值。
2.将特征放入点特征直方图PFH
对于Ps的邻域,有一系列的Ps、Pt点对,对于每一个Ps就可以计算他对应的四个特征值。
那么如何将特征放在直方图里面呢?对于三个特征值中的每一个具体特征,我将其等分成5份,这样就存在555=125个区间,这125个区间构成了一个125bins的直方图。这样对于一个点的三个特征值,根据每一个特征值的细分,分发到这125个区间中。对目标点的邻域中的所有点按照此种方法进行统计,实现对目标点的直方图特征描述。
在论文《Aligning Point Cloud Views using Persistent Feature Histograms》中,使用了4个特征值,每个特征值分成两份(2^4=16 bins)。对特征分发的结果:
3. 快速点特征直方图FPFH
FPFH是PFH的加速版本,经过实测能提升1/3的速度(大概)。
参考:https://blog.csdn.net/u011736771/article/details/85103293
PFH和FPFH计算方式之间的主要区别总结如下:
FPFH没有对全互连点的所有邻近点的计算参数进行统计,因此可能漏掉了一些重要的点对,而这些漏掉的对点可能对捕获查询点周围的几何特征有贡献。
PFH特征模型是对查询点周围的一个精确的邻域半径内,而FPFH还包括半径r范围以外的额外点对(但不超过2r的范围);
因为采用权重计算的方式,所以FPFH结合SPFH值,重新捕获邻近重要点对的几何信息;
由于大大地降低了FPFH的整体复杂性,因此FPFH有可能使用在实时应用中;
通过分解三元组,简化了合成的直方图。也就是简单生成d分离特征直方图,对每个特征维度来单独绘制,并把它们连接在一起。
不再详述,感兴趣地读者可参考论文:《Fast Point Feature Histograms (FPFH) for 3D Registration》
4. PFH和FPFH的pcl用法
这里仅仅介绍FPFH的用法。
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h> // 法线
#include <pcl/features/fpfh_omp.h> // fpfh计算,omp加速方法
pcl::search::KdTree<pcl::PointXYZI>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZI>());
std::mutex mPFH;
.........一系列操作,获得待计算点云 cloud
mPFH.lock(); // 计算直方图的时候记得锁一下
// pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>); // for PFH
pcl::PointCloud<pcl::PointNormal>::Ptr normals(new pcl::PointCloud<pcl::PointNormal>); // for FPFH
// ================================================================================================
// ================================================================================================
// ================================================================================================
// ================================================================================================
pcl::FPFHEstimationOMP<pcl::PointXYZI, pcl::PointNormal, pcl::FPFHSignature33> fpfh_omp;
pcl::PointCloud<pcl::FPFHSignature33>::Ptr fpfhs(new pcl::PointCloud<pcl::FPFHSignature33>());
// 法线估计
pcl::NormalEstimation<pcl::PointXYZI, pcl::PointNormal> ne;
ne.setInputCloud(cloud); // cloud 是待计算的输入点云
ne.setSearchMethod(tree);
ne.setKSearch(10);
// ne.setRadiusSearch(0.03);
ne.compute(*normals);
// 直方图计算
std::cout<< "法线"<<normals->points[100]<<std::endl;
fpfh_omp.setInputCloud(cloud);
fpfh_omp.setInputNormals(normals);
fpfh_omp.setSearchMethod(tree);
fpfh_omp.setNumberOfThreads(8);
fpfh_omp.setRadiusSearch(0.5);
fpfh_omp.compute(*fpfhs);
mPFH.unlock();
//==================================================================================================
// ================================================================================================
// ================================================================================================
// ================================================================================================