点击上方“3D视觉工坊”,选择“星标”
干货第一时间送达
作者:开着拖拉机唱山歌 链接:https://zhuanlan.zhihu.com/p/103219268 本文转载自知乎,作者已授权,未经许可请勿二次转载。
1. PCL 点云特征描述与提取
2. 常见的特征描述算法
3. PCL 描述三维特征相关基础
4. PCL 法线估计实例 ------ 估计某一点的表面法线
5. PCL 法线估计实例 ------ 估计一个点云的表面法线
6. PCL 使用积分图像进行法线估计
7. 点特征直方图(PFH)描述子
8. 快速点特征直方图(FPFH)描述子
9. 估视点特征直方图(VFH)
10. 从一个深度图像提取NARF特征
PCL 点云特征描述与提取
3D点云特征描述与提取是点云信息处理中最基础也是最关键的一部分。点云的识别、分割、重采样、配准曲面重建等处理,大部分算法都依赖特征描述符提取的结果。从尺度上来分,一般分为局部特征的描述和全局特征的描述,例如局部的法线等几何形状特征的描述,全局的拓朴特征的描述,都属于3D点云特征描述与提取的范畴。
常用的特征描述算法有:
1. 法线和曲率计算 normal_3d_feature 、
2. 特征值分析、
3. PFH 点特征直方图描述子 (统计点法线角度差值row pitch yaw) n*k^2、
4. FPFH 快速点特征直方图描述子 FPFH是PFH的简化形式 n*k
5. 3D Shape Context(3D形状内容描述子)
pcl::ShapeContext3DEstimation< PointInT, PointNT, PointOutT >
实现3D形状内容描述子算法
6. 纹理特征, 2d-3d点对 特征描述子(orb可以)
7. Spin Image
8. VFH 视点特征直方图(Viewpoint Feature Histogram 视角方向与点法线方向夹角)
9. NARF 关键点特征 pcl::NarfKeypoint narf特征 pcl::NarfDescriptor(深度图边缘)
10. RoPs 特征(Rotational Projection Statistics)
11. (GASD)全局一致的空间分布描述子特征 Globally Aligned Spatial Distribution (GASD) descriptors
12. 旋转图像(spin iamge)
旋转图像最早是由johnson提出的特征描述子,主要用于3D场景中的曲面匹配和模型识别。
由于各种不同需求需要进行对比以便能够区分曲面空间的分布情况,应用软件要求更好的特征度量方式,因此作为一个单一实体的三维点概念和笛卡尔坐标系被淘汰了,出现了一个新的概念取而代之:局部描述子(locl descriptor)。文献中对这一概念的描述有许多种不同的命名,如:形状描述子(shape descriptors)或几何特征(geometric features)。
PCL 描述三维特征相关基础
在原始表示形式下,点的定义是用笛卡尔坐标系坐标 x, y, z 相对于一个给定的原点来简单表示的三维映射系统的概念。假定坐标系的原点不随着时间而改变,这里有两个点p1和p2分别在时间t1和t2捕获,有着相同的坐标,对这两个点作比较其实是属于不适定问题(ill-posed problem),因为虽然相对于一些距离测度它们是相等的,但是它们取样于完全不同的表面,因此当把它们和临近的其他环境中点放在一起时,它们表达着完全不同的信息,这是因为在t1和t2之间局部环境有可能发生改变。一些获取设备也许能够提供取样点的额外数据,例如强度或表面反射率等,甚至颜色,然而那并不能完全解决问题。单从两个点之间来,对比仍然是不适定问题。由于各种不同需求需要进行对比以便能够区分曲面空间的分布情况,应用软件要求有更好的特征度量方式,因此作为一个单一实体的三维点概念和笛卡尔坐标系被淘汰了,出现了一个新的概念取而代之:局部描述子(local descriptor)。文献中对这一概念的描述有许多种不同的命名,如:形状描述子(shape descriptors)或几何特征(geometric features),文本中剩余部分都统称为点特征表示。通过包括周围的领域,特征描述子能够表征采样表面的几何性质,它有助于解决不适定的对比问题,理想情况下相同或相似表面上的点的特征值将非常相似(相对特定度量准则),而不同表面上的点的特征描述子将有明显差异。下面几个条件,通过能否获得相同的局部表面特征值,可以判定点特征表示方式的优劣:
平移选转不变性(刚体变换):即三维旋转和三维平移变化 不会影响特征向量F估计,即特征向量具有
抗密度干扰性(改变采样密度):原则上,一个局部表面小块的采样密度无论是大还是小,都应该有相同的特征向量值
对噪声具有稳定性(噪声):数据中有轻微噪声的情况下,点特征表示在它的特征向量中必须保持相同或者极其相似的值
通常,PCL中特征向量利用快速 kd-tree 查询 ,使用近似法来计算查询点的最近邻元素,通常有两种查询类型:K邻域查询,半径搜索两中方法
PCL 法线估计实例 ------ 估计某一点的表面法线
对应点云P中每一个点p得到p点最近邻元素,计算p点的表面的法线N,检查N的方向是否指向视点如果不是则翻转。一旦确定查询点的邻域后,查询点的邻域点可以用来估计一个局部特征描述子,它用查询点周围领域点描述采样面的几何特征,描述几何表面图形的一个重要属性。首先是推断它在坐标系中的方位,也就是估计他的法线(表面法线是表面的一个重要的属性)。
点云渲染:法线信息可以用于光照渲染,有些地方也称着色(立体感)。
如下图所示,左边的点云没有法线信息,右边的点云有法线信息。
比如Phone光照模型里,
漫反射光照符合Lambert余弦定律:漫反射光强与N * L成正比,N为法线方向,L为点到光源的向量。
所以,在模型边缘处,N与L近似垂直,着色会比较暗。
点云的几何属性:法线可用于计算几何相关的信息,
广泛应用于点云注册(配准),点云重建,特征点检测等。
另外法线信息还可以用于区分薄板正反面。
前面说的是二维降到一维时的情况,假如我们有一堆散乱的三维点云,则可以这样计算法线:
1)对每一个点,取临近点,比如取最临近的50个点,当然会用到K-D树
2)对临近点做PCA降维,把它降到二维平面上,
可以想象得到这个平面一定是它的切平面(在切平面上才可以尽可能分散)
3)切平面的法线就是该点的法线了,而这样的法线有两个,
取哪个还需要考虑临近点的凸包方向
代码
#include //法线特征
--------------------------------------------------------------
// 创建法线估计类====================================
pcl::NormalEstimation<:pointxyz pcl::normal> ne;
ne.setInputCloud (cloud_ptr);
// 添加搜索算法 kdtree search 最近的几个点 估计平面 协方差矩阵PCA分解 求解法线
pcl::search::KdTree<:pointxyz>::Ptr tree (new pcl::search::KdTree<:pointxyz> ());
ne.setSearchMethod (tree);
// 输出点云 带有法线描述
pcl::PointCloud<:normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<:normal>);
pcl::PointCloud<:normal>& cloud_normals = *cloud_normals_ptr;
ne.setRadiusSearch (0.03);//半径内搜索临近点
//ne.setKSearch(8); //其二 指定临近点数量
// 计算表面法线特征
ne.compute (cloud_normals);
完整代码
#include
#include
#include //法线估计类头文件
#include
#include
#include
int main ()
{
//打开点云代码
pcl::PointCloud<:pointxyz>::Ptr cloud (new pcl::PointCloud<:pointxyz>);
pcl::io::loadPCDFile ("table_scene_lms400.pcd", *cloud);
//创建法线估计估计向量
pcl::NormalEstimation<:pointxyz pcl::normal> ne;
ne.setInputCloud (cloud);
//创建一个空的KdTree对象,并把它传递给法线估计向量
//基于给出的输入数据集,KdTree将被建立
pcl::search::KdTree<:pointxyz>::Ptr tree (new pcl::search::KdTree<:pointxyz> ());
ne.setSearchMethod (tree);
//存储输出数据
pcl::PointCloud<:normal>::Ptr cloud_normals (new pcl::PointCloud<:normal>);
//使用半径在查询点周围3厘米范围内的所有临近元素
ne.setRadiusSearch (0.03);
//计算特征值
ne.compute (*cloud_normals);
// cloud_normals->points.size ()应该与input cloud_downsampled->points.size ()有相同的尺寸
//可视化
pcl::visualization::PCLVisualizer viewer("PCL Viewer");
viewer.setBackgroundColor (0.0, 0.0, 0.0);
viewer.addPointCloudNormals<:pointxyz>(cloud, cloud_normals);
//视点默认坐标是(0,0,0)可使用 setViewPoint(float vpx,float vpy,float vpz)来更换
while (!viewer.wasStopped ())
{
viewer.spinOnce ();
}
return 0;
}
表面法线是几何体表面一个十分重要的属性,例如:在进行光照渲染时产生符合可视习惯的效果时需要表面法线的信息才能正常进行。对于一个已经已经知道的几何体表面,根据垂直于点表面的的矢量,得出表面某一点的法线方向比较容易,然而由于我们获取的点云的数据集在真实的物体的表面表现为一组点的样本,这样就会有两种方法解决:
1. 使用曲面重建技术,从获取的点云数据中得到采样点对应的曲面,然后从曲面模型中计算出表面法线
2. 直接从点云数据中近似推断表面法线
在确定表面一点法线的问题近似于估计表面的一个相切面法线的问题,因此转换过来就是求一个最小二乘法平面拟合的问题
注意此方法只适用于有序点云
为什么使用积分图像?
在PCL估计点云的表面法向量一文中,可以看到,要估计无序点云的法线,需要先确定待估计点的最近邻集合,然后再用PCA拟合切平面,从而获得表面法线。
而要确定待估计点的最近邻集合,不管是用k近邻还是半径约束的方法,都将是计算量巨大的。如果输入的点云是有序点云,利用这种有序性可以明显加快表面法线估计的速度,积分图像正是为了利用点云有序性提高法线估计的速度而引入的。
什么是积分图像?
在二维图像中,将每一个像素点都用原图像中该像素点与原点构成的矩形中所有像素之和代替,那么生成的新的图像就是积分图像。
设原图像为I(x,y),积分图像为SAT(x,y),则:
#include
#include
#include
#include
int main()
{
//打开点云
pcl::PointCloud<:pointxyz>::Ptr cloud(new pcl::PointCloud<:pointxyz>);
pcl::io::loadPCDFile("table.pcd", *cloud);
//创建法线估计向量
pcl::PointCloud<:normal>::Ptr normals(new pcl::PointCloud<:normal>);
pcl::IntegralImageNormalEstimation<:pointxyz pcl::normal> ne;
/****************************************************************************************
三种法线估计方法
COVARIANCE_MATRIX 模式从具体某个点的局部邻域的协方差矩阵创建9个积分,来计算这个点的法线
AVERAGE_3D_GRADIENT 模式创建6个积分图来计算水平方向和垂直方向的平滑后的三维梯度并使用两个梯度间的向量
积计算法线
AVERAGE_DEPTH——CHANGE 模式只创建了一个单一的积分图,从而平局深度变化计算法线
********************************************************************************************/
ne.setNormalEstimationMethod(ne.AVERAGE_3D_GRADIENT); //设置法线估计的方式AVERAGE_3D_GRADIENT
ne.setMaxDepthChangeFactor(0.02f); //设置深度变化系数
ne.setNormalSmoothingSize(10.0f); //设置法线优化时考虑的邻域的大小
ne.setInputCloud(cloud); //输入的点云
ne.compute(*normals); //计算法线
//可视化
pcl::visualization::PCLVisualizer viewer("PCL Viewer"); //视口的名称
viewer.setBackgroundColor(0.0, 0.0, 0.5); //背景颜色的设置
viewer.addPointCloudNormals<:pointxyz pcl::normal>(cloud, normals); //将法线加入到点云中
while (!viewer.wasStopped())
{
viewer.spinOnce();
}
return 0;
}
正如点特征表示法所示,表面法线和曲率估计是某个点周围的几何特征基本表示法。虽然计算非常快速容易,但是无法获得太多信息,因为它们只使用很少的几个参数值来近似表示一个点的k邻域的几何特征。然而大部分场景中包含许多特征点,这些特征点有相同的或者非常相近的特征值,因此采用点特征表示法,其直接结果就减少了全局的特征信息。那么三维特征描述子中一位成员:点特征直方图(Point Feature Histograms),我们简称为PFH,从PCL实现的角度讨论其实施细节。PFH特征不仅与坐标轴三维数据有关,同时还与表面法线有关。
PFH计算方式通过参数化查询点与邻域点之间的空间差异,并形成一个多维直方图对点的k邻域几何属性进行描述。直方图所在的高维超空间为特征表示提供了一个可度量的信息空间,对点云对应曲面的6维姿态来说它具有不变性,并且在不同的采样密度或邻域的噪音等级下具有鲁棒性。点特征直方图(PFH)表示法是基于点与其k邻域之间的关系以及它们的估计法线,简言之,它考虑估计法线方向之间所有的相互作用,试图捕获最好的样本表面变化情况,以描述样本的几何特征。因此,合成特征超空间取决于每个点的表面法线估计的质量。
blog.csdn.net/zfjBIT/ar参考
点特征直方图(PFH)在PCL中的实现是pcl_features模块的一部分。默认PFH的实现使用5个区间分类(例如:四个特征值中的每个都使用5个区间来统计)
计算法线---计算临近点对角度差值-----直方图--
点特征直方图(PFH)描述子
点特征直方图(Point Feature Histograms)
正如点特征表示法所示,表面法线和曲率估计是某个点周围的几何特征基本表示法。
虽然计算非常快速容易,但是无法获得太多信息,因为它们只使用很少的
几个参数值来近似表示一个点的k邻域的几何特征。然而大部分场景中包含许多特征点,
这些特征点有相同的或者非常相近的特征值,因此采用点特征表示法,
其直接结果就减少了全局的特征信息。
代码
#include
#include //法线特征
---------------------------------------------------
// =====计算法线========创建法线估计类====================================
pcl::NormalEstimation<:pointxyz pcl::normal> ne;
ne.setInputCloud (cloud_ptr);
// 添加搜索算法 kdtree search 最近的几个点 估计平面 协方差矩阵PCA分解 求解法线
pcl::search::KdTree<:pointxyz>::Ptr tree (new pcl::search::KdTree<:pointxyz> ());
ne.setSearchMethod (tree);//设置近邻搜索算法
// 输出点云 带有法线描述
pcl::PointCloud<:normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<:normal>);
pcl::PointCloud<:normal>& cloud_normals = *cloud_normals_ptr;
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);//半价内搜索临近点 3cm
// 计算表面法线特征
ne.compute (cloud_normals);
//=======创建PFH估计对象pfh,并将输入点云数据集cloud和法线normals传递给它=================
pcl::PFHEstimation<:pointxyz pcl::normal pcl::pfhsignature125> pfh;// phf特征估计其器
pfh.setInputCloud (cloud_ptr);
pfh.setInputNormals (cloud_normals_ptr);
//如果点云是类型为PointNormal,则执行pfh.setInputNormals (cloud);
//创建一个空的kd树表示法,并把它传递给PFH估计对象。
//基于已给的输入数据集,建立kdtree
pcl::search::KdTree<:pointxyz>::Ptr tree2 (new pcl::search::KdTree<:pointxyz> ());
//pcl::KdTreeFLANN<:pointxyz>::Ptr tree2 (new pcl::KdTreeFLANN<:pointxyz> ()); //-- older call for PCL 1.5-
pfh.setSearchMethod (tree2);//设置近邻搜索算法
//输出数据集
pcl::PointCloud<:pfhsignature125>::Ptr pfh_fe_ptr (new pcl::PointCloud<:pfhsignature125> ());//phf特征
//使用半径在5厘米范围内的所有邻元素。
//注意:此处使用的半径必须要大于估计表面法线时使用的半径!!!
pfh.setRadiusSearch (0.05);
//计算pfh特征值
pfh.compute (*pfh_fe_ptr);
完整代码
/*
计算法线---计算临近点对角度差值-----直方图--
点特征直方图(PFH)描述子
点特征直方图(Point Feature Histograms)
正如点特征表示法所示,表面法线和曲率估计是某个点周围的几何特征基本表示法。
虽然计算非常快速容易,但是无法获得太多信息,因为它们只使用很少的
几个参数值来近似表示一个点的k邻域的几何特征。然而大部分场景中包含许多特征点,
这些特征点有相同的或者非常相近的特征值,因此采用点特征表示法,
其直接结果就减少了全局的特征信息。
http://www.pclcn.org/study/shownews.php?lang=cn&id=101
通过参数化查询点与邻域点之间的空间差异,并形成一个多维直方图对点的k邻域几何属性进行描述。
直方图所在的高维超空间为特征表示提供了一个可度量的信息空间,
对点云对应曲面的6维姿态来说它具有不变性,
并且在不同的采样密度或邻域的噪音等级下具有鲁棒性。
是基于点与其k邻域之间的关系以及它们的估计法线,
简言之,它考虑估计法线方向之间所有的相互作用,
试图捕获最好的样本表面变化情况,以描述样本的几何特征。
Pq 用红色标注并放在圆球的中间位置,半径为r,
(Pq)的所有k邻元素(即与点Pq的距离小于半径r的所有点)
全部互相连接在一个网络中。最终的PFH描述子通过计
算邻域内所有两点之间关系而得到的直方图,
因此存在一个O(nk^2) 的计算复杂性。
每一点对,原有12个参数,6个坐标值,6个坐标姿态(基于法线)
PHF计算没一点对的 相对坐标角度差值三个值和 坐标点之间的欧氏距离 d
从12个参数减少到4个参数
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);
为查询点创建最终的PFH表示,所有的四元组将会以某种统计的方式放进直方图中,
这个过程首先把每个特征值范围划分为b个子区间,并统计落在每个子区间的点数目,
因为四分之三的特征在上述中为法线之间的角度计量,
在三角化圆上可以将它们的参数值非常容易地归一到相同的区间内。
默认PFH的实现使用5个区间分类(例如:四个特征值中的每个都使用5个区间来统计),
其中不包括距离(在上文中已经解释过了——但是如果有需要的话,
也可以通过用户调用computePairFeatures方法来获得距离值),
这样就组成了一个125浮点数元素的特征向量(35),
其保存在一个pcl::PFHSignature125的点类型中。
*/
#include
#include
#include //点云文件pcd 读写
#include //法线特征
#include //直方图的可视化
#include // 直方图的可视化 方法2
//using namespace std;
using std::cout;
using std::endl;
int main(int argc, char** argv)
{
pcl::PointCloud<:pointxyz>::Ptr cloud_ptr (new pcl::PointCloud<:pointxyz>);
//======【1】 读取点云文件 填充点云对象======
pcl::PCDReader reader;
reader.read( "../../Filtering/table_scene_lms400.pcd", *cloud_ptr);
if(cloud_ptr==NULL) { cout << "pcd file read err" << endl; return -1;}
cout << "PointCLoud size() " << cloud_ptr->width * cloud_ptr->height
<< " data points ( " << pcl::getFieldsList (*cloud_ptr) << "." << endl;
// =====【2】计算法线========创建法线估计类====================================
pcl::NormalEstimation<:pointxyz pcl::normal> ne;
ne.setInputCloud (cloud_ptr);
/*
法线估计类NormalEstimation的实际计算调用程序内部执行以下操作:
对点云P中的每个点p
1.得到p点的最近邻元素
2.计算p点的表面法线n
3.检查n的方向是否一致指向视点,如果不是则翻转
在PCL内估计一点集对应的协方差矩阵,可以使用以下函数调用实现:
//定义每个表面小块的3x3协方差矩阵的存储对象
Eigen::Matrix3fcovariance_matrix;
//定义一个表面小块的质心坐标16-字节对齐存储对象
Eigen::Vector4fxyz_centroid;
//估计质心坐标
compute3DCentroid(cloud,xyz_centroid);
//计算3x3协方差矩阵
computeCovarianceMatrix(cloud,xyz_centroid,covariance_matrix);
*/
// 添加搜索算法 kdtree search 最近的几个点 估计平面 协方差矩阵PCA分解 求解法线
pcl::search::KdTree<:pointxyz>::Ptr tree (new pcl::search::KdTree<:pointxyz> ());
ne.setSearchMethod (tree);//设置近邻搜索算法
// 输出点云 带有法线描述
pcl::PointCloud<:normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<:normal>);
pcl::PointCloud<:normal>& cloud_normals = *cloud_normals_ptr;
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);//半价内搜索临近点 3cm
// 计算表面法线特征
ne.compute (cloud_normals);
//=======【3】创建PFH估计对象pfh,并将输入点云数据集cloud和法线normals传递给它=================
pcl::PFHEstimation<:pointxyz pcl::normal pcl::pfhsignature125> pfh;// phf特征估计其器
pfh.setInputCloud (cloud_ptr);
pfh.setInputNormals (cloud_normals_ptr);
//如果点云是类型为PointNormal,则执行pfh.setInputNormals (cloud);
//创建一个空的kd树表示法,并把它传递给PFH估计对象。
//基于已给的输入数据集,建立kdtree
pcl::search::KdTree<:pointxyz>::Ptr tree2 (new pcl::search::KdTree<:pointxyz> ());
//pcl::KdTreeFLANN<:pointxyz>::Ptr tree2 (new pcl::KdTreeFLANN<:pointxyz> ()); //-- older call for PCL 1.5-
pfh.setSearchMethod (tree2);//设置近邻搜索算法
//输出数据集
pcl::PointCloud<:pfhsignature125>::Ptr pfh_fe_ptr (new pcl::PointCloud<:pfhsignature125> ());//phf特征
//使用半径在5厘米范围内的所有邻元素。
//注意:此处使用的半径必须要大于估计表面法线时使用的半径!!!
pfh.setRadiusSearch (0.05);
//计算pfh特征值
pfh.compute (*pfh_fe_ptr);
cout << "phf feature size : " << pfh_fe_ptr->points.size() << endl;
// 应该与input cloud->points.size ()有相同的大小,即每个点都有一个pfh特征向量
// ========直方图可视化=============================
pcl::visualization::PCLHistogramVisualizer view;//直方图可视化
view.setBackgroundColor(255,0,0);//背景红色
view.addFeatureHistogram<:pfhsignature125> (*pfh_fe_ptr,"pfh",100);
view.spinOnce(); //循环的次数
//view.spin(); //无限循环
// pcl::visualization::PCLPlotter plotter;
//plotter.addFeatureHistogram(*pfh_fe_ptr, 300); //设置的很坐标长度,该值越大,则显示的越细致
//plotter.plot();
return 0;
}
phf点特征直方图 计算复杂度还是太高
计算法线---计算临近点对角度差值-----直方图--
因此存在一个O(nk^2) 的计算复杂性。
k个点之间相互的点对 k×k条连接线
每一点对,原有12个参数,6个坐标值,6个坐标姿态(基于法线)
PHF计算没一点对的 相对坐标角度差值三个值和 坐标点之间的欧氏距离 d
从12个参数减少到4个参数
快速点特征直方图FPFH(Fast Point Feature Histograms)把算法的计算复杂度降低到了O(nk) ,
但是任然保留了PFH大部分的识别特性。
查询点和周围k个点的连线 的4参数特征
也就是1×k=k个线
默认的FPFH实现使用11个统计子区间(例如:四个特征值中的每个都将它的参数区间分割为11个),
特征直方图被分别计算然后合并得出了浮点值的一个33元素的特征向量,
这些保存在一个pcl::FPFHSignature33点类型中。
1.FPFH没有对全互连 点的所有邻近点的计算参数进行统计,从图12-18中可以看到,
因此可能漏掉了一些重要的点对,而这些漏掉的对点可能对捕获查询点周围的几何特征有贡献。
2.PFH特征模型是对查询点周围的一个精确的邻域半径内,而FPFH还包括半径r范围以外的额外点对(不过在2r内);
3.因为重新权重计算的方式,所以FPFH结合SPFH值,重新捕获邻近重要点对的几何信息;
4.由于大大地降低了PFH的整体复杂性,因此FPFH有可能使用在实时应用中;
5.通过分解三元组,简化了合成的直方图。
也就是简单生成d分离特征直方图,对每个特征维度来单独绘制,并把它们连接在一起(见下2图)。
1. 得到 p 的邻居k个点 p_k(0~k-1)
2. 计算每一对 p 、p_k(i) 法线的三个角度差值(row pitch yaw) + 距离
3. 把所有结果统计输出到一个SPFH直方图
4. 得到 p 的邻域元素
5. 使用 p 的每一个SPFH和一个权重计算式,来计算最终 p 的FPFH
头文件
#include
#include //法线特征
----------------------------------------------------
// =====计算法线========创建法线估计类====================================
pcl::NormalEstimation<:pointxyz pcl::normal> ne;
ne.setInputCloud (cloud_ptr);
/*
法线估计类NormalEstimation的实际计算调用程序内部执行以下操作:
对点云P中的每个点p
1.得到p点的最近邻元素
2.计算p点的表面法线n
3.检查n的方向是否一致指向视点,如果不是则翻转
在PCL内估计一点集对应的协方差矩阵,可以使用以下函数调用实现:
//定义每个表面小块的3x3协方差矩阵的存储对象
Eigen::Matrix3fcovariance_matrix;
//定义一个表面小块的质心坐标16-字节对齐存储对象
Eigen::Vector4fxyz_centroid;
//估计质心坐标
compute3DCentroid(cloud,xyz_centroid);
//计算3x3协方差矩阵
computeCovarianceMatrix(cloud,xyz_centroid,covariance_matrix);
*/
// 添加搜索算法 kdtree search 最近的几个点 估计平面 协方差矩阵PCA分解 求解法线
pcl::search::KdTree<:pointxyz>::Ptr tree (new pcl::search::KdTree<:pointxyz> ());
ne.setSearchMethod (tree);//设置近邻搜索算法
// 输出点云 带有法线描述
pcl::PointCloud<:normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<:normal>);
pcl::PointCloud<:normal>& cloud_normals = *cloud_normals_ptr;
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);//半价内搜索临近点 3cm
// 计算表面法线特征
ne.compute (cloud_normals);
//=======创建FPFH估计对象fpfh, 并将输入点云数据集cloud和法线normals传递给它=================
//pcl::PFHEstimation<:pointxyz pcl::normal pcl::pfhsignature125> pfh;// phf特征估计其器
pcl::FPFHEstimation<:pointxyz> fpfh;
// pcl::FPFHEstimationOMP<:pointxyz> fpfh;//多核加速
fpfh.setInputCloud (cloud_ptr);
fpfh.setInputNormals (cloud_normals_ptr);
//如果点云是类型为PointNormal,则执行pfh.setInputNormals (cloud);
//创建一个空的kd树表示法,并把它传递给PFH估计对象。
//基于已给的输入数据集,建立kdtree
pcl::search::KdTree<:pointxyz>::Ptr tree2 (new pcl::search::KdTree<:pointxyz> ());
//pcl::KdTreeFLANN<:pointxyz>::Ptr tree2 (new pcl::KdTreeFLANN<:pointxyz> ()); //-- older call for PCL 1.5-
fpfh.setSearchMethod (tree2);//设置近邻搜索算法
//输出数据集
//pcl::PointCloud<:pfhsignature125>::Ptr pfh_fe_ptr (new pcl::PointCloud<:pfhsignature125> ());//phf特征
pcl::PointCloud<:fpfhsignature33>::Ptr fpfh_fe_ptr (new pcl::PointCloud<:fpfhsignature33>());//fphf特征
//使用半径在5厘米范围内的所有邻元素。
//注意:此处使用的半径必须要大于估计表面法线时使用的半径!!!
fpfh.setRadiusSearch (0.05);
//计算pfh特征值
fpfh.compute (*fpfh_fe_ptr);
完整代码
/*
phf点特征直方图 计算复杂度还是太高
计算法线---计算临近点对角度差值-----直方图--
因此存在一个O(nk^2) 的计算复杂性。
k个点之间相互的点对 k×k条连接线
每一点对,原有12个参数,6个坐标值,6个坐标姿态(基于法线)
PHF计算没一点对的 相对坐标角度差值三个值和 坐标点之间的欧氏距离 d
从12个参数减少到4个参数
快速点特征直方图FPFH(Fast Point Feature Histograms)把算法的计算复杂度降低到了O(nk) ,
但是任然保留了PFH大部分的识别特性。
查询点和周围k个点的连线 的4参数特征
也就是1×k=k个线
默认的FPFH实现使用11个统计子区间(例如:四个特征值中的每个都将它的参数区间分割为11个),
特征直方图被分别计算然后合并得出了浮点值的一个33元素的特征向量,
这些保存在一个pcl::FPFHSignature33点类型中。
*/
#include
//#include
#include
#include //点云文件pcd 读写
#include //法线特征
#include //直方图的可视化
#include // 直方图的可视化 方法2
//using namespace std;
using std::cout;
using std::endl;
int main(int argc, char** argv)
{
pcl::PointCloud<:pointxyz>::Ptr cloud_ptr (new pcl::PointCloud<:pointxyz>);
//======【1】 读取点云文件 填充点云对象======
pcl::PCDReader reader;
reader.read( "../../Filtering/table_scene_lms400.pcd", *cloud_ptr);
if(cloud_ptr==NULL) { cout << "pcd file read err" << endl; return -1;}
cout << "PointCLoud size() " << cloud_ptr->width * cloud_ptr->height
<< " data points ( " << pcl::getFieldsList (*cloud_ptr) << "." << endl;
// =====【2】计算法线========创建法线估计类====================================
pcl::NormalEstimation<:pointxyz pcl::normal> ne;
ne.setInputCloud (cloud_ptr);
/*
法线估计类NormalEstimation的实际计算调用程序内部执行以下操作:
对点云P中的每个点p
1.得到p点的最近邻元素
2.计算p点的表面法线n
3.检查n的方向是否一致指向视点,如果不是则翻转
在PCL内估计一点集对应的协方差矩阵,可以使用以下函数调用实现:
//定义每个表面小块的3x3协方差矩阵的存储对象
Eigen::Matrix3fcovariance_matrix;
//定义一个表面小块的质心坐标16-字节对齐存储对象
Eigen::Vector4fxyz_centroid;
//估计质心坐标
compute3DCentroid(cloud,xyz_centroid);
//计算3x3协方差矩阵
computeCovarianceMatrix(cloud,xyz_centroid,covariance_matrix);
*/
// 添加搜索算法 kdtree search 最近的几个点 估计平面 协方差矩阵PCA分解 求解法线
pcl::search::KdTree<:pointxyz>::Ptr tree (new pcl::search::KdTree<:pointxyz> ());
ne.setSearchMethod (tree);//设置近邻搜索算法
// 输出点云 带有法线描述
pcl::PointCloud<:normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<:normal>);
pcl::PointCloud<:normal>& cloud_normals = *cloud_normals_ptr;
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);//半价内搜索临近点 3cm
// 计算表面法线特征
ne.compute (cloud_normals);
//=======【3】创建FPFH估计对象fpfh, 并将输入点云数据集cloud和法线normals传递给它=================
//pcl::PFHEstimation<:pointxyz pcl::normal pcl::pfhsignature125> pfh;// phf特征估计其器
pcl::FPFHEstimation<:pointxyz> fpfh;
// pcl::FPFHEstimationOMP<:pointxyz> fpfh;//多核加速
fpfh.setInputCloud (cloud_ptr);
fpfh.setInputNormals (cloud_normals_ptr);
//如果点云是类型为PointNormal,则执行pfh.setInputNormals (cloud);
//创建一个空的kd树表示法,并把它传递给PFH估计对象。
//基于已给的输入数据集,建立kdtree
pcl::search::KdTree<:pointxyz>::Ptr tree2 (new pcl::search::KdTree<:pointxyz> ());
//pcl::KdTreeFLANN<:pointxyz>::Ptr tree2 (new pcl::KdTreeFLANN<:pointxyz> ()); //-- older call for PCL 1.5-
fpfh.setSearchMethod (tree2);//设置近邻搜索算法
//输出数据集
//pcl::PointCloud<:pfhsignature125>::Ptr pfh_fe_ptr (new pcl::PointCloud<:pfhsignature125> ());//phf特征
pcl::PointCloud<:fpfhsignature33>::Ptr fpfh_fe_ptr (new pcl::PointCloud<:fpfhsignature33>());//fphf特征
//使用半径在5厘米范围内的所有邻元素。
//注意:此处使用的半径必须要大于估计表面法线时使用的半径!!!
fpfh.setRadiusSearch (0.05);
//计算pfh特征值
fpfh.compute (*fpfh_fe_ptr);
cout << "phf feature size : " << fpfh_fe_ptr->points.size() << endl;
// 应该与input cloud->points.size ()有相同的大小,即每个点都有一个pfh特征向量
// ========直方图可视化=============================
pcl::visualization::PCLHistogramVisualizer view;//直方图可视化
view.setBackgroundColor(255,0,0);//背景红色
view.addFeatureHistogram<:fpfhsignature33> (*fpfh_fe_ptr,"fpfh",100); //对下标为100的点的直方图特征可视化
view.spinOnce(); //循环的次数
//view.spin(); //无限循环
// pcl::visualization::PCLPlotter plotter;
//plotter.addFeatureHistogram(*fpfh_fe_ptr, 300); //设置的很坐标长度,该值越大,则显示的越细致
//plotter.plot();
return 0;
}
它是一种新的特征表示形式,应用在点云聚类识别和六自由度位姿估计问题。
视点特征直方图(或VFH)是源于FPFH描述子.
由于它的获取速度和识别力,我们决定利用FPFH强大的识别力,
但是为了使构造的特征保持缩放不变性的性质同时,
还要区分不同的位姿,计算时需要考虑加入视点变量。
1.扩展FPFH,使其利用整个点云对象来进行计算估计(如2图所示),
在计算FPFH时以物体中心点与物体表面其他所有点之间的点对作为计算单元。
2.添加视点方向与每个点估计法线之间额外的统计信息,为了达到这个目的,
我们的关键想法是在FPFH计算中将视点方向变量直接融入到相对法线角计算当中。
因此新组合的特征被称为视点特征直方图(VFH)。
1.一个视点方向相关的分量
2.一个包含扩展FPFH的描述表面形状的分量
对扩展的FPFH分量来说,默认的VFH的实现使用45个子区间进行统计,
而对于视点分量要使用128个子区间进行统计,这样VFH就由一共308个浮点数组成阵列。
在PCL中利用pcl::VFHSignature308的点类型来存储表示。P
FH/FPFH描述子和VFH之间的主要区别是:
对于一个已知的点云数据集,只一个单一的VFH描述子,
而合成的PFH/FPFH特征的数目和点云中的点数目相同。
#include
#include //法线特征
------------------------------------------------------
// =====计算法线========创建法线估计类====================================
pcl::NormalEstimation<:pointxyz pcl::normal> ne;
ne.setInputCloud (cloud_ptr);
// 添加搜索算法 kdtree search 最近的几个点 估计平面 协方差矩阵PCA分解 求解法线
pcl::search::KdTree<:pointxyz>::Ptr tree (new pcl::search::KdTree<:pointxyz> ());
ne.setSearchMethod (tree);//设置近邻搜索算法
// 输出点云 带有法线描述
pcl::PointCloud<:normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<:normal>);
pcl::PointCloud<:normal>& cloud_normals = *cloud_normals_ptr;
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);//半价内搜索临近点 3cm
// 计算表面法线特征
ne.compute (cloud_normals);
//=======创建VFH估计对象vfh,并把输入数据集cloud和法线normal传递给它================
pcl::VFHEstimation<:pointxyz pcl::normal pcl::vfhsignature308> vfh;
//pcl::PFHEstimation<:pointxyz pcl::normal pcl::pfhsignature125> pfh;// phf特征估计其器
//pcl::FPFHEstimation<:pointxyz> fpfh;// fphf特征估计其器
// pcl::FPFHEstimationOMP<:pointxyz> fpfh;//多核加速
vfh.setInputCloud (cloud_ptr);
vfh.setInputNormals (cloud_normals_ptr);
//如果点云是PointNormal类型,则执行vfh.setInputNormals (cloud);
//创建一个空的kd树对象,并把它传递给FPFH估计对象。
//基于已知的输入数据集,建立kdtree
pcl::search::KdTree<:pointxyz>::Ptr tree2 (new pcl::search::KdTree<:pointxyz> ());
//pcl::KdTreeFLANN<:pointxyz>::Ptr tree2 (new pcl::KdTreeFLANN<:pointxyz> ()); //-- older call for PCL 1.5-
vfh.setSearchMethod (tree2);//设置近邻搜索算法
//输出数据集
//pcl::PointCloud<:pfhsignature125>::Ptr pfh_fe_ptr (new pcl::PointCloud<:pfhsignature125> ());//phf特征
//pcl::PointCloud<:fpfhsignature33>::Ptr fpfh_fe_ptr (new pcl::PointCloud<:fpfhsignature33>());//fphf特征
pcl::PointCloud<:vfhsignature308>::Ptr vfh_fe_ptr (new pcl::PointCloud<:vfhsignature308> ());//vhf特征
//使用半径在5厘米范围内的所有邻元素。
//注意:此处使用的半径必须要大于估计表面法线时使用的半径!!!
//fpfh.setRadiusSearch (0.05);
//计算pfh特征值
vfh.compute (*vfh_fe_ptr);
完整代码
/*
视点特征直方图VFH(Viewpoint Feature Histogram)描述子,
它是一种新的特征表示形式,应用在点云聚类识别和六自由度位姿估计问题。
视点特征直方图(或VFH)是源于FPFH描述子.
由于它的获取速度和识别力,我们决定利用FPFH强大的识别力,
但是为了使构造的特征保持缩放不变性的性质同时,
还要区分不同的位姿,计算时需要考虑加入视点变量。
我们做了以下两种计算来构造特征,以应用于目标识别问题和位姿估计:
1.扩展FPFH,使其利用整个点云对象来进行计算估计(如2图所示),
在计算FPFH时以物体中心点与物体表面其他所有点之间的点对作为计算单元。
2.添加视点方向与每个点估计法线之间额外的统计信息,为了达到这个目的,
我们的关键想法是在FPFH计算中将视点方向变量直接融入到相对法线角计算当中。
通过统计视点方向与每个法线之间角度的直方图来计算视点相关的特征分量。
注意:并不是每条法线的视角,因为法线的视角在尺度变换下具有可变性,
我们指的是平移视点到查询点后的视点方向和每条法线间的角度。
第二组特征分量就是前面PFH中讲述的三个角度,如PFH小节所述,
只是现在测量的是在中心点的视点方向和每条表面法线之间的角度。
因此新组合的特征被称为视点特征直方图(VFH)。
下图表体现的就是新特征的想法,包含了以下两部分:
1.一个视点方向相关的分量
2.一个包含扩展FPFH的描述表面形状的分量
对扩展的FPFH分量来说,默认的VFH的实现使用45个子区间进行统计,
而对于视点分量要使用128个子区间进行统计,这样VFH就由一共308个浮点数组成阵列。
在PCL中利用pcl::VFHSignature308的点类型来存储表示。P
FH/FPFH描述子和VFH之间的主要区别是:
对于一个已知的点云数据集,只一个单一的VFH描述子,
而合成的PFH/FPFH特征的数目和点云中的点数目相同。
*/
#include
//#include
//#include
#include
#include //点云文件pcd 读写
#include //法线特征
#include //直方图的可视化
#include // 直方图的可视化 方法2
// 可视化 https://segmentfault.com/a/1190000006685118
//using namespace std;
using std::cout;
using std::endl;
int main(int argc, char** argv)
{
pcl::PointCloud<:pointxyz>::Ptr cloud_ptr (new pcl::PointCloud<:pointxyz>);
//======【1】 读取点云文件 填充点云对象======
pcl::PCDReader reader;
reader.read( "../../Filtering/table_scene_lms400.pcd", *cloud_ptr);
if(cloud_ptr==NULL) { cout << "pcd file read err" << endl; return -1;}
cout << "PointCLoud size() " << cloud_ptr->width * cloud_ptr->height
<< " data points ( " << pcl::getFieldsList (*cloud_ptr) << "." << endl;
// =====【2】计算法线========创建法线估计类====================================
pcl::NormalEstimation<:pointxyz pcl::normal> ne;
ne.setInputCloud (cloud_ptr);
/*
法线估计类NormalEstimation的实际计算调用程序内部执行以下操作:
对点云P中的每个点p
1.得到p点的最近邻元素
2.计算p点的表面法线n
3.检查n的方向是否一致指向视点,如果不是则翻转
在PCL内估计一点集对应的协方差矩阵,可以使用以下函数调用实现:
//定义每个表面小块的3x3协方差矩阵的存储对象
Eigen::Matrix3fcovariance_matrix;
//定义一个表面小块的质心坐标16-字节对齐存储对象
Eigen::Vector4fxyz_centroid;
//估计质心坐标
compute3DCentroid(cloud,xyz_centroid);
//计算3x3协方差矩阵
computeCovarianceMatrix(cloud,xyz_centroid,covariance_matrix);
*/
// 添加搜索算法 kdtree search 最近的几个点 估计平面 协方差矩阵PCA分解 求解法线
pcl::search::KdTree<:pointxyz>::Ptr tree (new pcl::search::KdTree<:pointxyz> ());
ne.setSearchMethod (tree);//设置近邻搜索算法
// 输出点云 带有法线描述
pcl::PointCloud<:normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<:normal>);
pcl::PointCloud<:normal>& cloud_normals = *cloud_normals_ptr;
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);//半价内搜索临近点 3cm
// 计算表面法线特征
ne.compute (cloud_normals);
//=======【3】创建VFH估计对象vfh,并把输入数据集cloud和法线normal传递给它================
pcl::VFHEstimation<:pointxyz pcl::normal pcl::vfhsignature308> vfh;
//pcl::PFHEstimation<:pointxyz pcl::normal pcl::pfhsignature125> pfh;// phf特征估计其器
//pcl::FPFHEstimation<:pointxyz> fpfh;// fphf特征估计其器
// pcl::FPFHEstimationOMP<:pointxyz> fpfh;//多核加速
vfh.setInputCloud (cloud_ptr);
vfh.setInputNormals (cloud_normals_ptr);
//如果点云是PointNormal类型,则执行vfh.setInputNormals (cloud);
//创建一个空的kd树对象,并把它传递给FPFH估计对象。
//基于已知的输入数据集,建立kdtree
pcl::search::KdTree<:pointxyz>::Ptr tree2 (new pcl::search::KdTree<:pointxyz> ());
//pcl::KdTreeFLANN<:pointxyz>::Ptr tree2 (new pcl::KdTreeFLANN<:pointxyz> ()); //-- older call for PCL 1.5-
vfh.setSearchMethod (tree2);//设置近邻搜索算法
//输出数据集
//pcl::PointCloud<:pfhsignature125>::Ptr pfh_fe_ptr (new pcl::PointCloud<:pfhsignature125> ());//phf特征
//pcl::PointCloud<:fpfhsignature33>::Ptr fpfh_fe_ptr (new pcl::PointCloud<:fpfhsignature33>());//fphf特征
pcl::PointCloud<:vfhsignature308>::Ptr vfh_fe_ptr (new pcl::PointCloud<:vfhsignature308> ());//vhf特征
//使用半径在5厘米范围内的所有邻元素。
//注意:此处使用的半径必须要大于估计表面法线时使用的半径!!!
//fpfh.setRadiusSearch (0.05);
//计算pfh特征值
vfh.compute (*vfh_fe_ptr);
cout << "phf feature size : " << vfh_fe_ptr->points.size() << endl;
// 应该 等于 1
// ========直方图可视化=============================
//pcl::visualization::PCLHistogramVisualizer view;//直方图可视化
//view.setBackgroundColor(255,0,0);//背景红色
//view.addFeatureHistogram<:vfhsignature308> (*vfh_fe_ptr,"vfh",0);
//view.spinOnce(); //循环的次数
//view.spin(); //无限循环
pcl::visualization::PCLPlotter plotter;
plotter.addFeatureHistogram(*vfh_fe_ptr, 300); //设置的很坐标长度,该值越大,则显示的越细致
plotter.plot();
return 0;
}
从深度图像(RangeImage)中提取NARF关键点 pcl::NarfKeypoint
然后计算narf特征 pcl::NarfDescriptor
边缘提取
直接把三维的点云投射成 二维的图像不就好了。
这种投射方法叫做range_image.
#include // RangeImage 深度图像
#include // narf关键点检测
#include // narf特征
------------------------------------------------------
// ======从点云数据,创建深度图像=====================
// 直接把三维的点云投射成二维的图像
float noise_level = 0.0;
//noise level表示的是容差率,因为1°X1°的空间内很可能不止一个点,
//noise level = 0则表示去最近点的距离作为像素值,如果=0.05则表示在最近点及其后5cm范围内求个平均距离
//minRange表示深度最小值,如果=0则表示取1°X1°的空间内最远点,近的都忽略
float min_range = 0.0f;
//bordersieze表示图像周边点
int border_size = 1;
boost::shared_ptr<:rangeimage> range_image_ptr (new pcl::RangeImage);//创建RangeImage对象(智能指针)
pcl::RangeImage& range_image = *range_image_ptr; //RangeImage的引用
//半圆扫一圈就是整个图像了
range_image.createFromPointCloud (point_cloud, angular_resolution, pcl::deg2rad (360.0f), pcl::deg2rad (180.0f),
scene_sensor_pose, coordinate_frame, noise_level, min_range, border_size);
range_image.integrateFarRanges (far_ranges);//整合远距离点云
if (setUnseenToMaxRange)
range_image.setUnseenToMaxRange ();
// ====================提取NARF关键点=======================
pcl::RangeImageBorderExtractor range_image_border_extractor;//创建深度图像的边界提取器,用于提取NARF关键点
pcl::NarfKeypoint narf_keypoint_detector (&range_image_border_extractor);//创建NARF对象
narf_keypoint_detector.setRangeImage (&range_image);//设置点云对应的深度图
narf_keypoint_detector.getParameters ().support_size = support_size;// 感兴趣点的尺寸(球面的直径)
//narf_keypoint_detector.getParameters ().add_points_on_straight_edges = true;
//narf_keypoint_detector.getParameters ().distance_for_additional_points = 0.5;
pcl::PointCloud keypoint_indices;//用于存储关键点的索引 PointCloud
narf_keypoint_detector.compute (keypoint_indices);//计算NARF关键
//========================提取 NARF 特征 ====================
// ------------------------------------------------------
std::vector keypoint_indices2;//用于存储关键点的索引 vector
keypoint_indices2.resize (keypoint_indices.points.size ());
for (unsigned int i=0; i keypoint_indices2[i] = keypoint_indices.points[i];//narf关键点 索引
pcl::NarfDescriptor narf_descriptor (&range_image, &keypoint_indices2);//narf特征描述子
narf_descriptor.getParameters().support_size = support_size;
narf_descriptor.getParameters().rotation_invariant = rotation_invariant;
pcl::PointCloud<:narf36> narf_descriptors;
narf_descriptor.compute (narf_descriptors);
完整代码
/*
NARF
从深度图像(RangeImage)中提取NARF关键点 pcl::NarfKeypoint
然后计算narf特征 pcl::NarfDescriptor
1. 边缘提取
对点云而言,场景的边缘代表前景物体和背景物体的分界线。
所以,点云的边缘又分为三种:
前景边缘,背景边缘,阴影边缘。
就是点a 和点b 如果在 rangImage 上是相邻的,然而在三维距离上却很远,那么多半这里就有边缘。
在提取关键点时,
边缘应该作为一个重要的参考依据。
但一定不是唯一的依据。
对于某个物体来说关键点应该是表达了某些特征的点,而不仅仅是边缘点。
所以在设计关键点提取算法时,需要考虑到以下一些因素:
边缘和曲面结构都要考虑进去;
关键点要能重复;
关键点最好落在比较稳定的区域,方便提取法线。
图像的Harris角点算子将图像的关键点定义为角点。
角点也就是物体边缘的交点,
harris算子利用角点在两个方向的灰度协方差矩阵响应都很大,来定义角点。
既然关键点在二维图像中已经被成功定义且使用了,
看来在三维点云中可以沿用二维图像的定义
不过今天要讲的是另外一种思路,简单粗暴,
直接把三维的点云投射成二维的图像不就好了。
这种投射方法叫做range_image.
*/
#include //标准输入输出流
#include
#include // RangeImage 深度图像
#include //PCL的PCD格式文件的输入输出头文件
#include
#include
#include
#include // narf关键点检测
#include // narf特征
#include //解析 命令行 参数
//定义别名
typedef pcl::PointXYZ PointType;
// --------------------
// -----参数 Parameters-----
// --------------------
//参数 全局变量
float angular_resolution = 0.5f;//角坐标分辨率
float support_size = 0.2f;//感兴趣点的尺寸(球面的直径)
pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;//坐标框架:相机框架(而不是激光框架)
bool setUnseenToMaxRange = false;//是否将所有不可见的点 看作 最大距离
bool rotation_invariant = true;//
// --------------
// -----打印帮助信息 Help-----
// --------------
//当用户输入命令行参数-h,打印帮助信息
void
printUsage (const char* progName)
{
std::cout << "\n\n用法 Usage: "<\n\n"
<< "Options:\n"
<< "-------------------------------------------\n"
<< "-r 角度 angular resolution in degrees (default "< << "-c 坐标系 coordinate frame (default "<< (int)coordinate_frame< << "-m Treat all unseen points as maximum range readings\n"
<< "-s support size for the interest points (diameter of the used sphere - "
<< "default "< << "-o <0/1> switch rotational invariant version of the feature on/off"
<< "-h this help\n"
<< "\n\n";
}
//void
//setViewerPose (pcl::visualization::PCLVisualizer& viewer, const Eigen::Affine3f& viewer_pose)
//{
//Eigen::Vector3f pos_vector = viewer_pose * Eigen::Vector3f (0, 0, 0);
//Eigen::Vector3f look_at_vector = viewer_pose.rotation () * Eigen::Vector3f (0, 0, 1) + pos_vector;
//Eigen::Vector3f up_vector = viewer_pose.rotation () * Eigen::Vector3f (0, -1, 0);
//viewer.setCameraPosition (pos_vector[0], pos_vector[1], pos_vector[2],
//look_at_vector[0], look_at_vector[1], look_at_vector[2],
//up_vector[0], up_vector[1], up_vector[2]);
//}
// --------------
// -----Main-----
// --------------
int
main (int argc, char** argv)
{
// --------------------------------------
// ----- 解析 命令行 参数 Parse Command Line Arguments-----
// --------------------------------------
if (pcl::console::find_argument (argc, argv, "-h") >= 0)//help参数
{
printUsage (argv[0]);//程序名
return 0;
}
if (pcl::console::find_argument (argc, argv, "-m") >= 0)
{
setUnseenToMaxRange = true;//将所有不可见的点 看作 最大距离
cout << "Setting unseen values in range image to maximum range readings.\n";
}
if (pcl::console::parse (argc, argv, "-o", rotation_invariant) >= 0)
cout << "Switching rotation invariant feature version "<< (rotation_invariant ? "on" : "off")< int tmp_coordinate_frame;//坐标框架:相机框架(而不是激光框架)
if (pcl::console::parse (argc, argv, "-c", tmp_coordinate_frame) >= 0)
{
coordinate_frame = pcl::RangeImage::CoordinateFrame (tmp_coordinate_frame);
cout << "Using coordinate frame "<< (int)coordinate_frame< }
// 感兴趣点的尺寸(球面的直径)
if (pcl::console::parse (argc, argv, "-s", support_size) >= 0)
cout << "Setting support size to "< // 角坐标分辨率
if (pcl::console::parse (argc, argv, "-r", angular_resolution) >= 0)
cout << "Setting angular resolution to "< angular_resolution = pcl::deg2rad (angular_resolution);
// ------------------------------------------------------------------
// -----Read pcd file or create example point cloud if not given-----
// ------------------------------------------------------------------
//读取pcd文件;如果没有指定文件,就创建样本点
pcl::PointCloud::Ptr point_cloud_ptr(new pcl::PointCloud);//点云对象指针
pcl::PointCloud& point_cloud = *point_cloud_ptr;//引用 上面点云的别名 常亮指针
pcl::PointCloud<:pointwithviewpoint> far_ranges;//带视角的点云
Eigen::Affine3f scene_sensor_pose (Eigen::Affine3f::Identity ());//仿射变换
//检查参数中是否有pcd格式文件名,返回参数向量中的索引号
std::vector pcd_filename_indices = pcl::console::parse_file_extension_argument (argc, argv, "pcd");
if (!pcd_filename_indices.empty())
{
std::string filename = argv[pcd_filename_indices[0]];
if (pcl::io::loadPCDFile (filename, point_cloud) == -1)//如果指定了pcd文件,读取pcd文件
{
std::cerr << "Was not able to open file \""< printUsage (argv[0]);
return 0;
}
//设置传感器的姿势
scene_sensor_pose = Eigen::Affine3f (Eigen::Translation3f (point_cloud.sensor_origin_[0],
point_cloud.sensor_origin_[1],
point_cloud.sensor_origin_[2])) *
Eigen::Affine3f (point_cloud.sensor_orientation_);
//读取远距离文件?
std::string far_ranges_filename = pcl::getFilenameWithoutExtension (filename)+"_far_ranges.pcd";
if (pcl::io::loadPCDFile (far_ranges_filename.c_str (), far_ranges) == -1)
std::cout << "Far ranges file \""< }
else//没有指定pcd文件,生成点云,并填充它
{
setUnseenToMaxRange = true;//将所有不可见的点 看作 最大距离
cout << "\nNo *.pcd file given => Genarating example point cloud.\n\n";
for (float x=-0.5f; x<=0.5f; x+=0.01f)
{
for (float y=-0.5f; y<=0.5f; y+=0.01f)
{
PointType point; point.x = x; point.y = y; point.z = 2.0f - y;
point_cloud.points.push_back (point);//设置点云中点的坐标
}
}
point_cloud.width = (int) point_cloud.points.size ();
point_cloud.height = 1;
}
// -----------------------------------------------
// -----Create RangeImage from the PointCloud-----
// -----------------------------------------------
// ======从点云数据,创建深度图像=====================
// 直接把三维的点云投射成二维的图像
float noise_level = 0.0;
//noise level表示的是容差率,因为1°X1°的空间内很可能不止一个点,
//noise level = 0则表示去最近点的距离作为像素值,如果=0.05则表示在最近点及其后5cm范围内求个平均距离
//minRange表示深度最小值,如果=0则表示取1°X1°的空间内最远点,近的都忽略
float min_range = 0.0f;
//bordersieze表示图像周边点
int border_size = 1;
boost::shared_ptr<:rangeimage> range_image_ptr (new pcl::RangeImage);//创建RangeImage对象(智能指针)
pcl::RangeImage& range_image = *range_image_ptr; //RangeImage的引用
//从点云创建深度图像
//rangeImage也是PCL的基本数据结构
//pcl::RangeImage rangeImage;
// 球坐标系
//角分辨率
//float angularResolution = (float) ( 1.0f * (M_PI/180.0f)); // 1.0 degree in radians 弧度
//phi可以取360°
// float maxAngleWidth = (float) (360.0f * (M_PI/180.0f)); // 360.0 degree in radians
//a取180°
// float maxAngleHeight = (float) (180.0f * (M_PI/180.0f)); // 180.0 degree in radians
//半圆扫一圈就是整个图像了
range_image.createFromPointCloud (point_cloud, angular_resolution, pcl::deg2rad (360.0f), pcl::deg2rad (180.0f),
scene_sensor_pose, coordinate_frame, noise_level, min_range, border_size);
range_image.integrateFarRanges (far_ranges);//整合远距离点云
if (setUnseenToMaxRange)
range_image.setUnseenToMaxRange ();
// --------------------------------------------
// -----Open 3D viewer and add point cloud-----
// --------------------------------------------
// 3D点云显示
pcl::visualization::PCLVisualizer viewer ("3D Viewer");
viewer.setBackgroundColor (1, 1, 1);//背景颜色 白色
pcl::visualization::PointCloudColorHandlerCustom<:pointwithrange> range_image_color_handler (range_image_ptr, 0, 0, 0);
viewer.addPointCloud (range_image_ptr, range_image_color_handler, "range image");//添加点云
viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "range image");
//viewer.addCoordinateSystem (1.0f, "global");
//PointCloudColorHandlerCustom point_cloud_color_handler (point_cloud_ptr, 150, 150, 150);
//viewer.addPointCloud (point_cloud_ptr, point_cloud_color_handler, "original point cloud");
viewer.initCameraParameters ();
//setViewerPose (viewer, range_image.getTransformationToWorldSystem ());
// --------------------------
// -----Show range image-----
// --------------------------
//显示深度图像(平面图)
pcl::visualization::RangeImageVisualizer range_image_widget ("Range image");
range_image_widget.showRangeImage (range_image);
// --------------------------------
// -----Extract NARF keypoints-----
// --------------------------------
// ==================提取NARF关键点=======================
pcl::RangeImageBorderExtractor range_image_border_extractor;//创建深度图像的边界提取器,用于提取NARF关键点
pcl::NarfKeypoint narf_keypoint_detector (&range_image_border_extractor);//创建NARF对象
narf_keypoint_detector.setRangeImage (&range_image);//设置点云对应的深度图
narf_keypoint_detector.getParameters ().support_size = support_size;// 感兴趣点的尺寸(球面的直径)
//narf_keypoint_detector.getParameters ().add_points_on_straight_edges = true;
//narf_keypoint_detector.getParameters ().distance_for_additional_points = 0.5;
pcl::PointCloud keypoint_indices;//用于存储关键点的索引 PointCloud
narf_keypoint_detector.compute (keypoint_indices);//计算NARF关键
std::cout << "Found找到关键点:"<
// ----------------------------------------------
// -----Show keypoints in range image widget-----
// ----------------------------------------------
//在range_image_widget中显示关键点
//for (size_t i=0; i //range_image_widget.markPoint (keypoint_indices.points[i]%range_image.width,
//keypoint_indices.points[i]/range_image.width);
// -------------------------------------
// -----Show keypoints in 3D viewer-----
// -------------------------------------
//在3D图形窗口中显示关键点
pcl::PointCloud<:pointxyz>::Ptr keypoints_ptr (new pcl::PointCloud<:pointxyz>);//创建关键点指针
pcl::PointCloud<:pointxyz>& keypoints = *keypoints_ptr;//引用
keypoints.points.resize (keypoint_indices.points.size ());//初始化大小
for (size_t i=0; i keypoints.points[i].getVector3fMap () = range_image.points[keypoint_indices.points[i]].getVector3fMap ();
pcl::visualization::PointCloudColorHandlerCustom<:pointxyz> keypoints_color_handler (keypoints_ptr, 255, 0, 0);//红色
viewer.addPointCloud<:pointxyz> (keypoints_ptr, keypoints_color_handler, "keypoints");//添加显示关键点
viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "keypoints");
//渲染属性,可视化工具,3维数据, 其中PCL_VISUALIZER_POINT_SIZE表示设置点的大小为7
// ------------------------------------------------------
//========================提取 NARF 特征 ====================
// ------------------------------------------------------
std::vector keypoint_indices2;//用于存储关键点的索引 vector
keypoint_indices2.resize (keypoint_indices.points.size ());
for (unsigned int i=0; i keypoint_indices2[i] = keypoint_indices.points[i];//narf关键点 索引
pcl::NarfDescriptor narf_descriptor (&range_image, &keypoint_indices2);//narf特征描述子
narf_descriptor.getParameters().support_size = support_size;
narf_descriptor.getParameters().rotation_invariant = rotation_invariant;
pcl::PointCloud<:narf36> narf_descriptors;
narf_descriptor.compute (narf_descriptors);
cout << "Extracted "< <
//--------------------
// -----Main loop-----
//--------------------
while (!viewer.wasStopped ())
{
range_image_widget.spinOnce (); // process GUI events 处理 GUI事件
viewer.spinOnce ();
pcl_sleep(0.01);
}
}
转载:
1. https://github.com/Ewenwan/MVision/blob/master/PCL_APP/3_点云特征和特征描述%20Features.md
2. https://blog.csdn.net/wolfcsharp/article/details/94154375?ops_request_misc=%7B%22request_id%22%3A%22158271172919724847055026%22%2C%22scm%22%3A%2220140713.130056874..%22%7D&request_id=158271172919724847055026&biz_id=0&utm_source=distribute.pc_search_result.none-task
推荐阅读:
吐血整理|3D视觉系统化学习路线
那些精贵的3D视觉系统学习资源总结(附书籍、网址与视频教程)
超全的3D视觉数据集汇总
大盘点|6D姿态估计算法汇总(上)
大盘点|6D姿态估计算法汇总(下)
机器人抓取汇总|涉及目标检测、分割、姿态识别、抓取点检测、路径规划
重磅!3DCVer-学术论文写作投稿 交流群已成立
扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊等写作与投稿事宜。同时也可申请加入我们的细分方向交流群,目前主要有3D视觉、CV&深度学习、SLAM、三维重建、点云后处理、自动驾驶、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流等微信群,请扫描下面微信号加群,备注:”研究方向+学校/公司+昵称“,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进去相关微信群。原创投稿也请联系。
▲长按加微信群或投稿▲长按关注公众号
3D视觉从入门到精通知识星球:针对3D视觉领域的知识点汇总、入门进阶学习路线、最新paper分享、疑问解答四个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近1000+星球成员为创造更好的AI世界共同进步,知识星球入口: