本文档介绍 3D 特征估计 PCL 中的方法,并作为用户或开发人员的指南: 对 PCL::要素类的内部结构感兴趣。
理论入门
来自[Rusu论文]:
在其原生表示中, 3D 映射系统概念中定义的点仅使用其相对于给定原点的笛卡尔坐标 x、y、z 表示。假设坐标系的原点不随时间变化,则在t1和t2处获得的两个点p1和p2可能具有相同的坐标。然而,比较这些点是一个不合适的问题,因为即使它们相对于某些距离测量(例如欧几里得度量)相等,它们也可以在完全不同的表面上采样,因此当与附近的其他周围点一起时表示完全不同的信息。这是因为不能保证世界在t1和t2之间没有变化。一些采集设备可能会为采样点提供额外的信息,例如强度或表面反射值,甚至是颜色,但这并不能完全解决问题,并且比较仍然模糊不清。
由于各种原因需要比较点的应用程序需要更好的特性和度量,以便能够区分几何表面。因此,3D点作为具有笛卡尔坐标的单一实体的概念消失了,取而代之的是一个新的概念,即局部描述符。文献中有大量不同的命名方案 描述相同的概念化,例如形状描述符或几何特征,但对于本文档的其余部分,它们将称为点特征表示。
...
通过包括周围的邻居,可以在特征公式中推断和捕获底层采样表面几何,这有助于解决模糊性比较问题。理想情况下,对于位于相同或相似表面上的点,生成的要素将非常相似(相对于某些度量),而对于位于不同表面上的点,生成的要素将不同,如下图所示。 好的点要素表示与坏点要素表示区分开来,因为它能够在存在以下条件的情况下捕获相同的局部表面特征:
- 刚性变换 - 即数据中的 3D 旋转和 3D 平移不应影响生成的特征向量 F 估计;
- 不同的采样密度 - 原则上,或多或少密集采样的局部表面斑块应具有相同的特征矢量特征;
- 噪声 - 如果数据中存在轻度噪声,则点要素表示必须在其特征向量中保留相同或非常相似的值。
通常,PCL 功能使用快速 kd 树查询来计算查询点的最近邻。我们感兴趣的查询类型有两种:
- 确定查询点的 k(用户给定参数)邻居(也称为 k 搜索));
- 确定半径为 R 的球体内查询点的所有相邻要素(也称为半径搜索)。
注意
有关正确的k或r值应该是什么的讨论,请参阅[RusuDissertation]。
术语
在本文的其余部分,我们将进行某些缩写和 引入某些符号,以简化文本中的解释。请看 下表提供了所用每个术语的参考。
术语 | 解释 |
---|---|
傅 | 一个名为 Foo 的类 |
福普特 | 指向类 Foo 的共享指针, 例如,shared_ptr<福> |
FooConstPtr | 一个指向类 Foo 的常量共享指针, 例如,const shared_ptr<const Foo> |
如何传递输入
由于 PCL 中几乎所有从基本 pcl::P CLBase 类继承的类, pcl::要素类以两种不同的方式接受输入数据:
通过 setInputCloud (PointCloudConstPtr &) 给出的整个点云数据集 - 强制性
任何要素估计类都将尝试在给定输入云中的每个点估计要素。
点云数据集的子集,通过 setInputCloud (PointCloudConstPtr &) 和 setIndices (IndicesConstPtr &) 给出 - 可选
任何要素估计类都将尝试在给定输入云中具有给定索引列表中的索引的每个点估计要素。默认情况下,如果没有给出一组索引,则将考虑云中的所有点。
此外,要使用的点邻居集可以通过附加调用 setSearchSurface (PointCloudConstPtr &) 来指定。此调用是可选的,如果未给出搜索表面,则默认使用输入点云数据集。
因为 setInputCloud() 总是必需的,所以我们最多可以使用 <setInputCloud()、setIndex()、setSearchSurface()> 创建四种组合。假设我们有两个点云,P={p_1,p_2,...p_n} 和 Q={q_1, q_2, ..., q_n}.下图显示了所有四种情况:
-
setIndices() = false, setSearchSurface() = false - 毫无疑问,这是PCL中最常用的情况,其中用户只是输入单个PointCloud数据集,并期望在云中的所有点估计某个特征。
由于我们不希望根据是否给出一组索引和/或搜索表面来维护不同的实现副本,因此每当索引 = false 时,PCL 都会创建一组内部索引(作为 std::vector<int>),这些索引基本上指向整个数据集(index=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() 中给出的搜索表面信息,仅估计<输入、索引>对中的一个子集的特征。
最后,在上图中,这对应于最后一个(最右边)情况。在这里,我们假设q_2的索引不是为 Q 给出的索引向量的一部分,因此在 q2 时不会估计任何邻居或特征。
应该使用 setSearchSurface() 时最有用的例子是,当我们有一个非常密集的输入数据集时,但我们不想估计其中所有点的特征,而是使用pcl_keypoints中的方法发现的一些关键点,或者在云的缩减采样版本(例如,使用 pcl::体素网格<T>过滤器)。在这种情况下,我们通过 setInputCloud() 传递下采样/关键点输入,并将原始数据作为 setSearchSurface() 传递。
正态估计示例
确定后,查询点的相邻点可用于估计局部要素制图表达,该局部要素制图表达捕获查询点周围基础采样表面的几何。描述表面几何形状的一个重要问题是首先推断其在坐标系中的方向,即估计其法线。表面法线是表面的重要属性,在许多领域(如计算机图形应用)中大量使用,以应用正确的光源来生成阴影和其他视觉效果(有关更多信息,请参阅[RusuDissertation])。
以下代码片段将估计输入数据集中所有点的一组表面法线。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#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->size () should have the same size as the input cloud->size ()
}
|
以下代码片段将估计输入数据集中点子集的一组表面法线。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#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 (std::floor (cloud->size () / 10));
for (std::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
pcl::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->size () should have the same size as the input indicesptr->size ()
}
|
最后,以下代码片段将估计输入数据集中所有点的一组表面法线,但将使用另一个数据集估计它们的最近邻。如前所述,一个很好的用例是当输入是表面的缩减采样版本时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
#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->size () should have the same size as the input cloud_downsampled->size ()
}
|
[鲁苏论文] | (1, 2, 3) http://mediatum.ub.tum.de/doc/800632/941254.pdf |
注意
@PhDThesis{RusuDoctoralDisstheation, 作者 = {拉杜·波格丹·鲁苏}, title = {用于人类生活环境中日常操作的语义 3D 对象映射}, 学校 = {德国慕尼黑工业大学计算机科学系}, 年 = {2009}, 月 = {十月} }