PCL_3D物体识别之描述符_descriptors

   http://robotica.unileon.es/index.php/PCL/OpenNI_tutorial_4:_3D_object_recognition_(descriptors)

3D对象识别是点云处理最主要的应用之一,类似于2D识别,找到点云的关键点或特征点,与之前保存好的点云进行匹配。但3D相对于2D会有优势,譬如,我们能相当精确的估计出物体相对于传感器的准确位置和方向;3D对象识别往往对杂乱(前面的对象遮挡背景中的对象的拥挤场景)更加鲁棒;最后,获得物体形状的信息将有助于避免碰撞或grasping operations。

 normals是feature的一个例子,编码点的附近的信息,这样计算时会考虑到附近的点,让我们对周围的表面有所了解。但这仍不够。对于最佳特征,要满足以下标准:

  1. 对转换具有鲁棒性:刚体变换的平移和旋转不影响特征
  2. 它必须能抵抗噪音:导致噪声的测量误差不应该改变特征估计太多
  3. 它必须是分辨率不变的:如果以不同的密度进行采样(如在执行下采样之后),结果必须相同或相似

于是就出现了descriptors,它们是点的更复杂(和更精确)的特征,编码了许多关于周围几何的信息。目的是在多个点云中明确地识别一个点,而不管噪声、分辨率或变换。此外,他们中的一些捕捉关于他们所属对象的附加数据,例如视点(让我们检索姿势)。

 pcl中有很多描述符。有的利用点和其近邻点的法线的角度的差异,有的利用点之间的距离。对于不同的目的,描述符的性能或好或坏,给定的描述符可能是比例不变的,而另一个描述符可能更适合对象的遮挡和部分视图,你选择哪一个取决于你想做什么。

描述符可以分为两类:局部和全局。每种计算和使用都不同。


Local descriptors:

局部描述符仅仅描述点周围的局部几何,对整体的对象是啥没有概念,一般要你来选择为哪些点(关键点)计算描述符。

PFH:

PFH ( Point Feature Histogram)点特征描述符。PFH试图通过分析附近法线方向之间的差异来捕捉点周围的几何信息(因此,不精确的法线估计可能会产生低质量的描述符)。首先,该算法将所有邻接点配对,对于每对,从法向量计算一个固定的坐标框架(fixed coordinator frame),在这个框架下,用三个角度(angular)变量编码法线之间的差异。保存这些变量以及点之间的欧几里德距离,然后在计算完所有对后将其合并到直方图中。最后描述符是每个变量直方图的串联(总共4个)。

  

#include <pcl/features/normal_3d.h>
#include <pcl/features/pfh.h>

pcl::PointCloud<pcl::PFHSignature125>::Ptr descriptors(new pcl::PointCloud<pcl::PFHSignature125>());

// PFH estimation object.
pcl::PFHEstimation<pcl::PointXYZ, pcl::Normal, pcl::PFHSignature125> pfh;
pfh.setInputCloud(cloud);
pfh.setInputNormals(normals);
pfh.setSearchMethod(kdtree);
// Search radius, to look for neighbors. Note: the value given here has to be
// larger than the radius used to estimate the normals.
pfh.setRadiusSearch(0.05);
pfh.compute(*descriptors);

可以看到用"PFHSignature125 "保存pfh描述符,说明描述符的大小是125(特征向量的维度  5的三次方)。(将D维空间的一个特征分成B个部分需要B的D次方个面元(bins))。

*************************************************************************************************************************************************

FPFH:

https://blog.csdn.net/yamgyutou/article/details/105407902 我的笔记

FPFH(Fast Point Feature Histogram)。PFH计算成本高,无法实时提取,带有k个邻居的n个关键点的点云复杂度是O(nk2)。仅考虑当前关键点与其邻居之间的直接连接,去除邻节点之间额外的连接,复杂度变为O(nk),得到的直方图称为SPFH(Simplified Point Feature Histogram),参考坐标系和角度变量将照常计算。

为了考虑这些额外连接的丢失,在计算完所有的直方图之后,还要进行一个额外的步骤:将一个点的相邻点的SPFHs与其自身的SPFHs“合并”,根据距离进行加权。这就给定了点的表面信息,这些点的距离是所用半径的2倍。最后,连接3个直方图(不使用距离)来组成最终的描述符。

#include <pcl/features/normal_3d.h>
#include <pcl/features/fpfh.h>

pcl::PointCloud<pcl::FPFHSignature33>::Ptr descriptors(new pcl::PointCloud<pcl::FPFHSignature33>());

// FPFH estimation object.
pcl::FPFHEstimation<pcl::PointXYZ, pcl::Normal, pcl::FPFHSignature33> fpfh;
fpfh.setInputCloud(cloud);
fpfh.setInputNormals(normals);
fpfh.setSearchMethod(kdtree);
// Search radius, to look for neighbors. Note: the value given here has to be
// larger than the radius used to estimate the normals.
fpfh.setRadiusSearch(0.05);
fpfh.compute(*descriptors);

FPFH估计的另一个实现利用了多线程优化(使用OpenMP应用编程接口),在“FPFHEstimationOMP”类中提供。它的接口与标准的非优化实现相同。使用它将会大大提高多核系统的性能,意味着更快的计算时间,头文件“fpfh_omp.h”。 

*************************************************************************************************************************************************

SHOT:

https://blog.csdn.net/yamgyutou/article/details/105116100  我的笔记

SHOT(Signature of Histograms of Orientations)。对于球形支撑结构内的拓扑(表面)信息进行编码。这个球体分为32个面元或体积,其中8个面元沿方位角,2个面元沿仰角,2个面元沿半径。对于每个体积,计算一维局部直方图,变量是关键点的法线和该体积内的当前点之间的角度(准确地说,是余弦)。

SHOT采用局部参考框架,具有旋转不变性,它对噪音和混乱也很鲁棒。

#include <pcl/features/normal_3d.h>
#include <pcl/features/shot.h>

pcl::PointCloud<pcl::SHOT352>::Ptr descriptors(new pcl::PointCloud<pcl::SHOT352>());

// SHOT estimation object.
pcl::SHOTEstimation<pcl::PointXYZ, pcl::Normal, pcl::SHOT352> shot;
shot.setInputCloud(cloud);
shot.setInputNormals(normals);
// The radius that defines which of the keypoint's neighbors are described.
// If too large, there may be clutter, and if too small, not enough points may be found.
shot.setRadiusSearch(0.02);
shot.compute(*descriptors);

像FPFH一样 ,"SHOTEstimationOMP"可以实现多线程优化,头文件“shot_omp.h”。“SHOTColorEstimation”使用纹理进行匹配。

*************************************************************************************************************************************************

NARF:

https://blog.csdn.net/yamgyutou/article/details/105080379  我的笔记

NARF(Normal Aligned Radial Feature)是一个非点云作为输入的描述符,用于深度图像(range image)。距离图像是一种常见的RGB图像,其中到对应于某个像素的点的距离被编码为可见光谱中的颜色值:更靠近相机的点将是紫色的,而最大传感器范围附近的点将是红色的。

NARF需要合适关键点作为描述符,NARF关键点一般都在对象的角附近,因此还需要查找边界,对深度图像来说很容易。

首先,要创建距离图像,两种方法。spherical projection适用于LIDAR传感器获取的点云,planar projection适用于Kinect或 Xtion等类似于相机的传感器。

边缘提取,NARF关键点位于距离图像中对象的边缘附近,必须提取边界。边缘被定义为从前景到背景的突然变化,在距离图像中很容易看到,因为两个相邻像素的深度值存在“跳跃”。有三种边界:边界由位于对象边缘的像素(或点)组成;阴影边界是背景中遮挡边缘上的点(背景中的空白区域,因为前面的物体覆盖了它们);面纱点(veil points)是出现在激光雷达传感器扫描中的前两个点之间的插值点,在这里不必担心它们。

    

关键点,对关键点的要求有:必须考虑边界信息和表面结构;必须选择即使从另一个角度观察物体也能可靠检测的位置;这些点必须位于为正常估计或一般描述符计算提供稳定区域的位置上。过程如下:对于深度图像中的每个点,计算一个score,该score表示曲面在其邻域中的变化程度(这是用支持大小σ调整的,σ是用于寻找相邻点的球体的直径)。此外,还计算了这种变化的主导方向,将这个方向与邻居的方向进行比较,试图找出该点的稳定程度(如果方向非常不同,这意味着该点不稳定,周围的表面变化很大),靠近物体角落的点(但不是正好在边缘上)将是好的关键点,但是足够稳定。

#include <pcl/io/pcd_io.h>
#include <pcl/range_image/range_image_planar.h>
#include <pcl/features/range_image_border_extractor.h>
#include <pcl/keypoints/narf_keypoint.h>
#include <pcl/visualization/range_image_visualizer.h>

int
main(int argc, char** argv)
{
	// Object for storing the point cloud.
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
	// Object for storing the keypoints' indices.
	pcl::PointCloud<int>::Ptr keypoints(new pcl::PointCloud<int>);

	// Read a PCD file from disk.
	if (pcl::io::loadPCDFile<pcl::PointXYZ>(argv[1], *cloud) != 0)
	{
		return -1;
	}

	// Convert the cloud to range image.
	int imageSizeX = 640, imageSizeY = 480;
	float centerX = (640.0f / 2.0f), centerY = (480.0f / 2.0f);
	float focalLengthX = 525.0f, focalLengthY = focalLengthX;
	Eigen::Affine3f sensorPose = Eigen::Affine3f(Eigen::Translation3f(cloud->sensor_origin_[0],
								 cloud->sensor_origin_[1],
								 cloud->sensor_origin_[2])) *
								 Eigen::Affine3f(cloud->sensor_orientation_);
	float noiseLevel = 0.0f, minimumRange = 0.0f;
	pcl::RangeImagePlanar rangeImage;
	rangeImage.createFromPointCloudWithFixedSize(*cloud, imageSizeX, imageSizeY,
			centerX, centerY, focalLengthX, focalLengthX,
			sensorPose, pcl::RangeImage::CAMERA_FRAME,
			noiseLevel, minimumRange);

	pcl::RangeImageBorderExtractor borderExtractor;
	// Keypoint detection object.
	pcl::NarfKeypoint detector(&borderExtractor);
	detector.setRangeImage(&rangeImage);
	// The support size influences how big the surface of interest will be,
	// when finding keypoints from the border information.
	detector.getParameters().support_size = 0.2f;

	detector.compute(*keypoints);

	// Visualize the keypoints.
	pcl::visualization::RangeImageVisualizer viewer("NARF keypoints");
	viewer.showRangeImage(rangeImage);
	for (size_t i = 0; i < keypoints->points.size(); ++i)
	{
		viewer.markPoint(keypoints->points[i] % rangeImage.width,
						 keypoints->points[i] / rangeImage.width,
						 // Set the color of the pixel to red (the background
						 // circle is already that color). All other parameters
						 // are left untouched, check the API for more options.
						 pcl::visualization::Vector3ub(1.0f, 0.0f, 0.0f));
	}

	while (!viewer.wasStopped())
	{
		viewer.spinOnce();
		// Sleep 100ms to go easy on the CPU.
		pcl_sleep(0.1);
	}
}

给出的结果是这,但我跑下来显示不出结果。 

 NARF描述符编码关于点周围表面变化的信息。首先,围绕该点创建局部深度面片,它就像一个小的深度图像,以那个点为中心,与法线对齐(看起来就像我们沿着法线看这个点),一个有n个光束的星形图案被覆盖在补片上,也以该点为中心。对于每个光束,都会计算一个值,该值反映其下的表面变化程度,变化越强,越靠近中心,最终值就越高。n个结果值组成了最终的描述符。

#include <pcl/io/pcd_io.h>
#include <pcl/range_image/range_image_planar.h>
#include <pcl/features/range_image_border_extractor.h>
#include <pcl/keypoints/narf_keypoint.h>
#include <pcl/features/narf_descriptor.h>

int
main(int argc, char** argv)
{
	// Object for storing the point cloud.
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
	// Object for storing the keypoints' indices.
	pcl::PointCloud<int>::Ptr keypoints(new pcl::PointCloud<int>);
	// Object for storing the NARF descriptors.
	pcl::PointCloud<pcl::Narf36>::Ptr descriptors(new pcl::PointCloud<pcl::Narf36>);

	// Read a PCD file from disk.
	if (pcl::io::loadPCDFile<pcl::PointXYZ>(argv[1], *cloud) != 0)
	{
		return -1;
	}

	// Convert the cloud to range image.
	int imageSizeX = 640, imageSizeY = 480;
	float centerX = (640.0f / 2.0f), centerY = (480.0f / 2.0f);
	float focalLengthX = 525.0f, focalLengthY = focalLengthX;
	Eigen::Affine3f sensorPose = Eigen::Affine3f(Eigen::Translation3f(cloud->sensor_origin_[0],
								 cloud->sensor_origin_[1],
								 cloud->sensor_origin_[2])) *
								 Eigen::Affine3f(cloud->sensor_orientation_);
	float noiseLevel = 0.0f, minimumRange = 0.0f;
	pcl::RangeImagePlanar rangeImage;
	rangeImage.createFromPointCloudWithFixedSize(*cloud, imageSizeX, imageSizeY,
			centerX, centerY, focalLengthX, focalLengthX,
			sensorPose, pcl::RangeImage::CAMERA_FRAME,
			noiseLevel, minimumRange);

	// Extract the keypoints.
	pcl::RangeImageBorderExtractor borderExtractor;
	pcl::NarfKeypoint detector(&borderExtractor);
	detector.setRangeImage(&rangeImage);
	detector.getParameters().support_size = 0.2f;
	detector.compute(*keypoints);

	// The NARF estimator needs the indices in a vector, not a cloud.
	std::vector<int> keypoints2;
	keypoints2.resize(keypoints->points.size());
	for (unsigned int i = 0; i < keypoints->size(); ++i)
		keypoints2[i] = keypoints->points[i];
	// NARF estimation object.
	pcl::NarfDescriptor narf(&rangeImage, &keypoints2);
	// Support size: choose the same value you used for keypoint extraction.
	narf.getParameters().support_size = 0.2f;
	// If true, the rotation invariant version of NARF will be used. The histogram
	// will be shifted according to the dominant orientation to provide robustness to
	// rotations around the normal.
	narf.getParameters().rotation_invariant = true;

	narf.compute(*descriptors);
}

*************************************************************************************************************************************************

ROPS:

https://blog.csdn.net/yamgyutou/article/details/105431186  我的笔记

ROPS(The Rotational Projection Statistics ),旋转投影统计特征与三角形网格有关,所以要先从点云中生成三角网格。

为了计算ROPS关键点,根据支持半径裁剪局部表面,因此只考虑位于内部的点和三角形,然后,计算局部参考帧(LRF),赋予描述符旋转不变性。创建一个坐标系,以该点为原点,轴与LRF对齐。然后,对于每个轴,执行几个步骤。 首先,局部曲面围绕当前轴旋转。角度由其中一个参数决定,该参数设置旋转的次数。然后,将局部曲面中的所有点投影到XY、XZ和YZ平面上。对于每一个,计算关于投影点分布的统计信息,并连接以形成最终的描述符。

// Perform triangulation.
	pcl::concatenateFields(*cloud, *normals, *cloudNormals);
	pcl::search::KdTree<pcl::PointNormal>::Ptr kdtree2(new pcl::search::KdTree<pcl::PointNormal>);
	kdtree2->setInputCloud(cloudNormals);
	pcl::GreedyProjectionTriangulation<pcl::PointNormal> triangulation;
	pcl::PolygonMesh triangles;
	triangulation.setSearchRadius(0.025);
	triangulation.setMu(2.5);
	triangulation.setMaximumNearestNeighbors(100);
	triangulation.setMaximumSurfaceAngle(M_PI / 4); // 45 degrees.
	triangulation.setNormalConsistency(false);
	triangulation.setMinimumAngle(M_PI / 18); // 10 degrees.
	triangulation.setMaximumAngle(2 * M_PI / 3); // 120 degrees.
	triangulation.setInputCloud(cloudNormals);
	triangulation.setSearchMethod(kdtree2);
	triangulation.reconstruct(triangles);

	// Note: you should only compute descriptors for chosen keypoints. It has
	// been omitted here for simplicity.

	// RoPs estimation object.
	pcl::ROPSEstimation<pcl::PointXYZ, ROPS135> rops;
	rops.setInputCloud(cloud);
	rops.setSearchMethod(kdtree);
	rops.setRadiusSearch(0.03);
	rops.setTriangles(triangles.polygons);
	// Number of partition bins that is used for distribution matrix calculation.
	rops.setNumberOfPartitionBins(5);
	// The greater the number of rotations is, the bigger the resulting descriptor.
	// Make sure to change the histogram size accordingly.
	rops.setNumberOfRotations(3);
	// Support radius that is used to crop the local surface of the point.
	rops.setSupportRadius(0.025);

	rops.compute(*descriptors);

Global descriptors:

VFH (viewpoint feature Histogram)视点特征直方图。用于聚类识别。PFH和FPFH与VFH的主要区别是,对于一个给定的点云数据集,只有一个单一的VFH描述器被预估,而PFH/FPFH将有和点云里面相同的点的数量的输入。

猜你喜欢

转载自blog.csdn.net/yamgyutou/article/details/105714733