pcl::Feature类的两种输入方法及四种使用模式
理论基础
- 在原始表示形式下,点的定义是用笛卡尔坐标系坐标x,y,z相对于一个给定的原点来简单表示的三维映射系统的概念。假定坐标系的原点不随着时间而改变,这里有两个点p1和p2,分别在时间t1和t2捕获,有着相同的坐标。对这两个点做比较其实是属于不适定问题(ill-posed problem),因为虽然相对于一些距离测度(如:欧几里得度量)它们是相等的,但是它们取样于完全不同的表面,因此当把它们和邻近的其他环境中的点放在一起时,它们表达着完全不同的信息,这是因为在t1和t2之间局部环境有可能发生改变。一些获取设备也许能够提供取样点的额外数据,例如:强度或表面反射率等,甚至颜色,然而那并不能完全解决问题,单从两个点之间来对比仍然是不适定问题。由于各种不同需求需要进行对比以便能够区分曲面空间的分布情况,应用软件要求更好的特征度量方式,因此作为一个单一实体的三维点概念和笛卡尔坐标系被淘汰了,出现了一个新的概念取而代之:局部描述子(local descriptor)。文献中对这一概念的描述有许多不同的命名,如:形状描述子(shape descriptors)或几何特征(geometric features),本文中剩余部分都统称之为点特征表示(point feature representations)。
- 通过包括周围的邻域,特征描述子能够表征采样表面的几何性质,它有助于解决不适定的对比问题。如图1所示,理想情况下,相同或相似表面上的点的特征值将非常相似(相对特定度量准则),而不同表面上的点的特征描述子将有明显差异。下面几个条件,通过能否获得相同的局部表面特征值,可以判定点特征表示方式的优劣:
- 刚体变换(rigid transformations)——即三维旋转和三维平移变化不会影响特征向量F估计,即特征向量具有平移旋转不变性;
- 改变采样密度(varying sampling density)——原则上,一个局部表面小块的采样密度无论是大还是小,都应该有相同的特征向量值,即特征向量具有抗密度干扰性;
- 噪音(noise)——数据中有轻微噪音的情况下,点特征表示在它的特征向量中必须保持相同或者及其相似的值,即特征向量对点云噪声具有鲁棒性。
- 通常,PCL中特征向量利用快速kd-tree查询,使用近似法来计算查询点的最近邻元素,有两种常用的查询类型:
- 决定一个查询点的k邻域元素(k为用户已给参数)(也称为k-搜索);
- 在半径r的范围内,确定一个查询点的所有邻元素(也称为半径-搜索)。
pcl::Feature类输入数据的传入方法
- 因为几乎所有点云库中的类都继承来自基类
pcl::PCLBase
,pcl::Feature
类接收以下两种不同方式的输入数据:
- 一个完整的点云数据集,由
setInputCloud (PointCloudConstPtr &)
给出——此函数必需设置,后续特征算子才能正常计算,任何可以进行特征描述子估计的类,为给定的输入点云中的每个点估计一个特征向量。 - 点云数据集的一个子集,由s
etInputCloud (PointCloudConstPtr &)
和setIndices (IndicesConstPtr &)
给出—后面setIndices
函数为可选设置。如果传入IndicesConstPtr
参数,则任何可以进行特征估计的类将为给定输入点云中的索引对应的点估计一个特征,默认情况下,如果没有给出一组索引,点云中的所有点参与计算。
- 此外,通过一个附加调用程序,可以明确指定搜索时使用的点邻域集合
setSearchSurface (PointCloudConstPtr &)
,这个调用是可选的,当搜索点邻域集合未给出时,则输入点云数据集为默认的搜索空间。第一种点的特征,用第二种点来描述,这就是特征估计。
pcl::Feature类的四种使用模式
- 因为总是需要
setInputCloud()
,所以我们可以使用<setInputCloud(),setIndices(),setSearchSurface()>
来创建四个组合。 - 假如我们有两个点云,
P={p_1, p_2, ...p_n}
和Q={q_1, q_2, ..., q_n}
,如图2所示,表示了所有的四种情况:从左到右边依次为:未指定索引和搜索点云集合、只指定了索引、只指定了搜索点云集合、指定了索引和搜索点云集合。
-
setIndices() = false, setSearchSurface() = false
毫无疑问这是点云库中最常用的情况,第一类点是输入的全体点云数据,第二类点也是输入的全体点云数据。这种应用模式,将对输入的所有点进行特征估计,用来特征表示的相邻点集也都来自输入点云。当给定一个待估计的点,便用输入点云自身的其他点表示待估计点的特征。用户只需要输入一个单一的点云数据集,并且为点云中的所有点估计一个特征向量。不论一组索引和(或)搜索点云是否给定,都不希望保存不同的实现副本,无论何时,即使indices = false,PCL都会创建一组内部索引(为std::vector),这个索引集是指向整个数据集的(indices=1…N,N是点云中点的数目)。上述与图2中最左边的情况对应,首先,我们估计了p_1的最近邻元素,然后是p_2的最近邻元素,以此类推,直到我们估计完P中的所有点。
-
setIndices()= true,setSearchSurface()= false
这种应用模式下,第一类点由输入点云数据与索引列表共同决定,即只估计输入点云中索引号在索引列表中的那些点,第二类的点则是完整的输入点云数据集。对应上图的第二种情况,这里,我们假设p_2的索引不在已给的索引向量中,因此在p2点处,没有估计邻元素或者特征向量。
-
setIndices()= false,setSearchSurface()= true
这种应用模式下,第一类点是输入的全体点云数据,第二类点是则由setSearchSurface()
指定,在setSearchSurface()
中给出的采样面点云将用来为输入点获取最近邻元素,而不是输入点云本身。对应上图第三种情况,假设Q = {q_1,q_2}是第一类点,P是第二类点,即P是Q的搜索表面,则q_1和q_2的相邻点集或特征将用P中的点进行表示。
-
setIndices()= true,setSearchSurface()= true
这可能是最罕见的情况,索引和搜索表面都给定。在这种应用模式下,第一类点由输入点云数据与索引列表共同决定,第二类点由setSearchSurface()指定。对应上图第四种情况,假设q_2的索引不是为Q给出的索引向量的一部分,因此在q_2处不会估计相邻点集或者特征信息。
在使用setSearchSurface()
时,最有用的案例是:当有一个非常密集的输入点云数据集时,但是我们不想对它里面的所有点处进行特征估计,而是希望在找到的一些关键点处(使用pcl_keypoints中的方法进行估计),或者在点云的下采样版本中(如:使用pcl::VoxelGrid过滤而获得的)进行特征估计。这种情况下,我们通过setInputCloud()
来把下采样后的点云/关键点传递给特征估计算法,而把原始数据通过setSearchSurface()
设置为搜索集合,从而提高程序的运行效率。
法线估计实例
- 一旦确定邻域以后,查询点的邻域点可用来估计一个局部特征描述子,它用查询点周围邻域点描述采样面的几何特征。
- 描述几何表面图形的一个重要问题,首先是推断它在坐标系中的方位,也就是估计它的法线,表面法线是表面的一个重要属性,在许多领域都有重要应用,如使用光源来生成符合视觉效果的渲染等。
- 下面的代码段对所有输入点云数据集中的点,估计一组表面法线。
#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h>
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
... read, pass in or create a point cloud ...
// Create the normal estimation class, and pass the input dataset to it
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud (cloud);
// Create an empty kdtree representation, and pass it to the normal estimation object.
// Its content will be filled inside the object, based on the given input dataset (as no other search surface is given).
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());
ne.setSearchMethod (tree);
// Output datasets
pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);
// Compute the features
ne.compute (*cloud_normals);
// cloud_normals->points.size () should have the same size as the input cloud->points.size ()
}
- 下面这段代码将为输入点云数据集的子集估计一组表面法线。
#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h>
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
... read, pass in or create a point cloud ...
// Create a set of indices to be used. For simplicity, we're going to be using the first 10% of the points in cloud
std::vector<int> indices (floor (cloud->points.size () / 10));
for (size_t i = 0; i < indices.size (); ++i) indices[i] = i;
// Create the normal estimation class, and pass the input dataset to it
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud (cloud);
// Pass the indices
boost::shared_ptr<std::vector<int> > indicesptr (new std::vector<int> (indices));
ne.setIndices (indicesptr);
// Create an empty kdtree representation, and pass it to the normal estimation object.
// Its content will be filled inside the object, based on the given input dataset (as no other search surface is given).
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());
ne.setSearchMethod (tree);
// Output datasets
pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);
// Compute the features
ne.compute (*cloud_normals);
// cloud_normals->points.size () should have the same size as the input indicesptr->size ()
}
- 估计输入数据集中的所有点的曲面法线,但使用另一个数据集估计其相邻点集。 即估计下采样版本点云中所有点的法向量的实现方法,这样提高了程序的运行效率。
#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h>
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_downsampled (new pcl::PointCloud<pcl::PointXYZ>);
... read, pass in or create a point cloud ...
... create a downsampled version of it ...
// Create the normal estimation class, and pass the input dataset to it
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud (cloud_downsampled);
// Pass the original data (before downsampling) as the search surface
ne.setSearchSurface (cloud);
// Create an empty kdtree representation, and pass it to the normal estimation object.
// Its content will be filled inside the object, based on the given surface dataset.
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());
ne.setSearchMethod (tree);
// Output datasets
pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);
// Compute the features
ne.compute (*cloud_normals);
// cloud_normals->points.size () should have the same size as the input cloud_downsampled->points.size ()
}
转自: