3D功能如何在PCL中工作(How 3D Features work in PCL)

本文档介绍了PCL中的三维特征估计方法,并作为对pcl::Feature类内部感兴趣的用户或开发人员的指南。

#理论入门

在它们的原始表示中,3D映射系统的概念中所定义的仅使用其笛卡尔坐标x,y,z相对于给定原点来表示。假设坐标系的原点不随时间变化,则在t1和t2获得的两个点p1和p2具有相同的坐标。然而,比较这些点是一个不适当的问题,因为即使它们相对于一些距离度量(例如欧几里德度量)是相等的,它们可以在完全不同的表面上被采样,因此当与另一个表面一起被采用时表示完全不同的信息周围的点附近。那是因为没有保证世界在t1和t2之间没有变化。某些采集设备可能会为采样点提供额外的信息,如强度或表面缓解值,甚至是颜色,但是这并不能完全解决问题,并且比较仍然不明确。

由于各种原因需要比较点的应用需要更好的特征和度量来区分几何表面。作为具有笛卡尔坐标的单一实体的3D点的概念因此消失,并且本地描述符的新概念代替它。文献中丰富的描述相同概念化的不同命名方案,如形状描述符或几何特征,但对于本文档的其余部分,将被称为点特征表示。

通过包含周围邻居,可以在特征表达式中推断和捕获底层采样表面几何,这有助于解决歧义比较问题。 理想情况下,所得到的特征对于驻留在相同或类似表面上的点是非常相似的(相对于一些度量),对于在不同表面上找到的点不同,如下图所示。 一个好的点特征表示区别于一个坏的特征,能够捕捉到相同的局部表面特征:
提出修改建议

刚性转换 - 也就是说,数据中的3D旋转和3D转换不应该影响结果特征向量F估计;
变化的采样密度 - 原则上,或多或少密集地采样的局部曲面片应该具有相同的特征向量签名;
噪声 - 在数据中存在轻微噪声的情况下,点要素表示必须在其特征向量中保留相同或非常相似的值。

_images/good_features.jpg

一般来说,PCL特征使用近似方法来使用快速kdtree查询来计算查询点的最近邻居。 有两种我们感兴趣的查询类型:

确定查询点(也称为k-search)的k个(用户给定参数)邻居;
确定半径为r的球体内的查询点的所有邻居(也称为半径搜索)。

注意

有关正确的k值或r值的讨论,请参见Rusu论文

#术语
For the reminder of this article,我们会做一些缩写,并且介绍一些符号,以简化文中的解释。 请参阅下面的表格,了解每个使用的术语。

(项目)term (解释)explanation
Foo a class named Foo
FooPtr a boost shared pointer to a class Foo,
e.g., boost::shared_ptr<Foo>
FooConstPtr a const boost shared pointer to a class Foo,
e.g., const boost::shared_ptr<const Foo>

#如何通过输入
由于PCL中几乎所有继承自pcl::PCLBase类的类,pcl::Feature类都以两种不同的方式接受输入数据:

1、一个完整的点云数据集,通过setInputCloud(PointCloudConstPtr&)给出 - 是强制性的
任何特征估计类,试图估计给定输入云中每个点的特征。

2、点云数据集的一个子集,通过setInputCloud (PointCloudConstPtr &)setIndices (IndicesConstPtr &)给出 - 可选
任何特征估计类将尝试估计给定输入云中在给定索引列表中具有索引的每个点处的特征。默认情况下,如果没有指定一组索引,则将考虑云中的所有点。*

另外,要使用的点邻点的集合可以通过额外的调用setSearchSurface (PointCloudConstPtr &)来指定。 这个调用是可选的,当没有给出搜索表面时,默认使用输入点云数据集。

由于始终需要setInputCloud(),因此最多可以使用<setInputCloud(), setIndices(), setSearchSurface()>创建四个组合。 假设我们有两个点云,P={p_1, p_2, …p_n} and Q={q_1, q_2, …, q_n}。 下图显示了所有四种情况:

_images/features_input_explained.png
setIndices() = false, setSearchSurface() = false - 这是毫无疑问的PCL中最常用的情况,用户只需要提供一个PointCloud数据集,并期望在云中的所有点上估计某个特征。

由于我们不希望根据是否给定一组索引和/或搜索表面来维护不同的实现拷贝,所以每当indices = false时,PCL创建一组内部索引(作为std::vector<int>),基本上指向整个数据集(索引= 1…N,其中N是云中的点数)。

在上图中,这对应于最左边的情况。首先,我们估计p_1的最近邻居,然后估计p_2的最近邻居,依此类推,直到我们耗尽了P中的所有点。

setIndices() = true, setSearchSurface() = false - 如前所述,特征估计方法将只计算给定索引向量中具有索引的那些点的特征;

在上图中,这对应于第二种情况。在这里,我们假设p_2的索引不是给定的索引向量的一部分,所以在p2处不会估计邻居或特征。

setIndices() = false, setSearchSurface() = true - 就像在第一种情况下一样,将对所有给定的点作为输入来估计特征,但是在setSearchSurface()中给出的底层相邻曲面将被用于获得输入的最近邻居点,而不是输入云本身;

在上图中,这对应于第三种情况。如果Q={q_1, q_2} 是另一个作为输入的云,与P不同,P是Q的搜索表面,则q_1和q_2的邻居将从P中计算出来。

setIndices() = true, setSearchSurface() = true - 这可能是最罕见的情况,其中既有索引又有搜索表面。在这种情况下,使用setSearchSurface()中给出的搜索表面信息,将仅对<input, indices>对中的一个子集估计要素。

最后,如上图所示,这对应于最后(最右边)的情况。在这里,我们假设q_2的指数不是Q给出的指数矢量的一部分,所以在q2处不会估计邻居或特征。

当使用setSearchSurface()时,最有用的例子是当我们有一个非常密集的输入数据集时,但是我们不想估计它在所有点上的特征,而是在使用pcl_keypoints中的方法发现的一些关键点 在云的下采样版本(例如,使用pcl::VoxelGrid<T>滤波器获得)。 在这种情况下,我们通过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; 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 ()
}

Note

       @PhDThesis{RusuDoctoralDissertation, author = {Radu Bogdan Rusu}, title = {Semantic 3D Object Maps for Everyday Manipulation in Human Living Environments}, school = {Computer Science department, Technische Universitaet Muenchen, Germany}, year = {2009}, month = {October} }

How 3D Features work in PCL

猜你喜欢

转载自blog.csdn.net/RuoQiQingCheDi/article/details/83959266
pcl