欢迎点云相关产学研的学者和团体加入我们。
本小节介绍点云库(PCL)中的三维特征描述子工作原理,以及在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)——数据中有轻微噪音的情况下,点特征表示在它的特征向量中必须保持相同或者及其相似的值,即特征向量对点云噪声具有鲁棒性。
图1点特征描述子示意图
通常,PCL中特征向量利用快速kd-tree查询,使用近似法来计算查询点的最近邻元素,有两种常用的查询类型:
决定一个查询点的k邻域元素(k为用户已给参数)(也称为k-搜索);
在半径r的范围内,确定一个查询点的所有邻元素(也称为半径-搜索)注意:关于k或r应该如何取值的讨论,请见[RusuDissertation]。
因为几乎所有点云库中的类都继承来自基类pcl::PCLBase,pcl::Feature类接受以下两种不同方式的输入数据:
一个完整的点云数据集,由setInputCloud (PointCloudConstPtr &)给出——此函数必需设置,后续特征算子才能正常计算,任何可以进行特征描述子估计的类,为给定的输入点云中的每个点估计一个特征向量。
点云数据集的一个子集,由setInputCloud (PointCloudConstPtr &)和setIndices (IndicesConstPtr &)给出—后面setIndices函数为可选设置。如果传入IndicesConstPtr参数,则任何可以进行特征估计的类将为给定输入点云中的索引对应的点估计一个特征,默认情况下,如果没有给出一组索引,点云中的所有点参与计算。
此外,通过一个附加调用程序,可以明确指定搜索时使用的点邻域集合setSearchSurface (PointCloudConstPtr &),这个调用是可选的,当搜索点邻域集合未给出时,则输入点云数据集为默认的搜索空间。因为总是需要setInputCloud(),所以我们可以使用
图2点云输入组合
setIndices() = false, setSearchSurface() = false——毫无疑问这是点云库中最常用的情况,用户只需要输入一个单一的点云数据集,并且为点云中的所有点估计一个特征向量。不论一组索引和(或)搜索点云是否给定,都不希望保存不同的实现副本,无论何时,即使indices = false,PCL都会创建一组内部索引(为std::vector
setIndices() = true, setSearchSurface() = false –如前面所提到的,特征估计方法只计算已给索引的点的特征。对应上图的第二种情况,这里,我们假设p_2的索引不在已给的索引向量中,因此在p2点处,没有估计邻元素或者特征向量。
setIndices() = false, setSearchSurface() = true –如第一种情况,对所有已给点进行特征向量估计,但是,在setSearchSurface()中给出的采样面点云将用来为输入点获取最近邻元素,而不是输入点云本身。上述对应上图第三种情况。如果Q={q_1, q_2}作为输入,是不同于P的另一个给出的点云,P是Q的搜索表面,那么将从P中计算两个点q_1 和 q_2的近邻。
setIndices() = true, setSearchSurface() = true –这种组合可能是最少见的情况,索引和搜索点云都给定。这种情况下,将使用setSearchSurface()中给出的搜索点云,只对中的子集进行特征向量估计。上述对应上图中最后(最右端)一种情况,这里,我们假设q_2的索引没有在Q的已给索引向量中,因此在q2点处,没有估计其邻元素或者特征。
在使用setSearchSurface()时,最有用的案例是:当有一个非常密集的输入点云数据集时,但是我们不想对它里面的所有点处进行特征估计,而是希望在找到的一些关键点处(使用pcl_keypoints中的方法进行估计),或者在点云的下采样版本中(如:使用pcl::VoxelGrid
一旦确定邻域以后,查询点的邻域点可用来估计一个局部特征描述子,它用查询点周围邻域点描述采样面的几何特征,描述几何表面图形的一个重要问题,首先是推断它在坐标系中的方位,也就是估计它的法线,表面法线是表面的一个重要属性,在许多领域都有重要应用,如使用光源来生成符合视觉效果的渲染等(更多信息,详见[RusuDissertation])。下面的代码段对所有输入点云数据集中的点,估计一组表面法线。
#include
pcl::PointCloud<pcl::PointXYZ>::Ptrcloud(newpcl::PointCloud<pcl::PointXYZ>);
...//打开点云代码
//创建法线估计对象,并将输入数据集传递给这个对象
pcl::NormalEstimation<pcl::PointXYZ,pcl::Normal>ne;
ne.setInputCloud(cloud);
//创建一个空的kdtree对象,并把它传递给法线估计对象
//基于给出的输入数据集,kdtree将被建立
pcl::search::KdTree
ne.setSearchMethod(tree);
//存储输出数据集
pcl::PointCloud<pcl::Normal>::Ptrcloud_normals(newpcl::PointCloud<pcl::Normal>);
//使用半径在查询点周围3厘米范围内的所有邻元素
ne.setRadiusSearch(0.03);
//计算特征值
ne.compute(*cloud_normals);
//cloud_normals->points.size ()应该与input cloud_downsampled->points.size ()有相同尺寸
下面这段代码将为输入点云数据集的子集估计一组表面法线。
pcl::PointCloud<pcl::PointXYZ>::Ptrcloud(newpcl::PointCloud<pcl::PointXYZ>);
//创建一组使用的索引,为简单起见,我们打算使用点云中前10%的点。
std::vector<int>indices(floor(cloud->points.size()/10));
for(size_ti=0;indices.size();++i)indices[i]=i;
//创建法线估计类,并把输入数据集传递给它。
pcl::NormalEstimation<pcl::PointXYZ,pcl::Normal>ne;
ne.setInputCloud(cloud);
//传递索引
boost::shared_ptr<std::vector<int>>indicesptr(newstd::vector<int>(indices));
ne.setIndices(indicesptr);
//创建一个空的kdtree,并把它传递给法线估计对象
最后,下面这段代码将为所有输入数据集中的点估计一组表面法线,但是使用另一个数据集来估计它们的近邻,正如之前提到的,当输入是一个表面的下采样版本时,这样提高了程序的运行效率。
pcl::PointCloud<pcl::PointXYZ>::Ptrcloud(newpcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptrcloud_downsampled(newpcl::PointCloud<pcl::PointXYZ>);
// 创建法线估计对象,并把输入数据集传递给它
pcl::NormalEstimation<pcl::PointXYZ,pcl::Normal>ne;
ne.setInputCloud(cloud_downsampled);
//传递原始数据(下采样前)作为搜索点云集合
ne.setSearchSurface(cloud);
敬请关注PCL(Point Cloud Learning)中国更多的点云库PCL(Point Cloud Library)相关官方教程。
参考文献:
1.朱德海、郭浩、苏伟.点云库PCL学习教程(ISBN 978-7-5124-0954-5)北京航空航天出版社 2012-10
法线估计实例