我这里不介绍pcl里面的类的命名规范,及点的类型。为什么呢?pcl里面类的命名规范,比较繁琐,而且主要是针对要对pcl这个库要开源的人士,而pcl里面的点的类型太多,足够满足你的要求。所以我们要走一条具有中国特色的pcl主义道路。
今天来介绍一下pcl里面的3D特征。
点是用来给一个给定区域来用笛卡尔坐标系x,y,z来进行描绘的一种方式。假定坐标系统的坐标原点没有变,可能会有两个点比如p1和p2,成为t1和t2,但是它们的坐标是没变的。
比较这些点云是不稳定的,因为即使他们有相同的距离,但是它们可能采自不同的表面,因此他们可能会代表不同的信息,当把它们和表面上临近的点结合在一起的时候。这是因为很难确保现实世界里面的t1和t2没有变化。有一些获取点云的装置可能会获取一些额外的数据,比如亮度(intensity),比如表面光滑度(翻译不一定对,surface remission value),和颜色等等,但是以上的这些往往还是不能解决问题。
程序往往需要更好的特征或者是矩来区别几何表面。所以把3D点看成笛卡尔坐标系里面的一个单一的实体这个概念消失了,我们引入了一个局部坐标系。文化是丰富多彩,森罗万象的,我们可以对同样的概念取很多的命名组合,比如形状描述,几何特征,而在pcl里面是用点的特征来描述。
通过包含周边的点,采样表面的几何特征可以被我们给推理或者捕获,这将解决我们蛋疼的比较问题。理想情况下,同一个或者相似表面上的点的特征是很相似的。那么怎么来找到一个比较好的,描述点的特征呢?当这个特征在下面这3个因素的影响下,还能或得同样的表面特点的时候就是好的特征。
1.刚体转换。这包括了3D旋转与3D转换,好的特征应该不会被此影响,特征向量F(feature)不会改变。
2.不同的采样密度。总体来说,一个局部的表面区域,不管以高密度还是低密度的采样,应该具有相同的特征向量。
3.噪声。这个不需要过多的解释。
总体来说,pcl里面使用相似的方法来查询指定点的最近邻,如使用kd-trees。下面是我们感兴趣的2个查询时需要的类型;
1.k。即查询时的最近邻的点的个数。
2.r。即搜索半径。
专业术语:
term | explanation |
---|---|
Foo | a class named Foo //英文里面很多随便取的类叫foo或bar类似张三,李四 |
FooPtr | a boost shared pointer to a class Foo,//boost共享指针 e.g., boost::shared_ptr<Foo> |
FooConstPtr | a const boost shared pointer to a classFoo,//boost共享常量指针 e.g., const boost::shared_ptr<const Foo> |
怎么进行输入?
pcl里面基本上所有的类都继承自pcl::PCLBase这个类,所有pcl::Feature有两个方式来获取输入数据。
1.输入为整个点云数据集,setInputCloud(PointCloudConstPtr&) 强制的
这将计算每一个输入点的特征。
2.输入为一个点云数据集的子集。setInputCloud(PointCloudPtr&)和setIndices(IndicesConstPtr&) 后面那个是可选的。
这将计算给定下标的输入点的特征。默认情况下下标是不会给的,也就意味着每个点的特征都会被计算。
除此之外,我们还可以通过别的调用,如setSearchSurface(PointCloudConstPtr &)来搜索表面上的点。
1.setIndices()=false,setSearchSurface()=false
这是pcl里面用的最多的,不用任何的特征来排除点云,即包含所有的点。
因为我们不希望根据是否有下标或者是否有表面来确定不同的点云的拷贝,pcl内部创造了一系列的向量,来指向整个数据集。
最左边的那幅图,它先找到p1的最近邻,再找p2的最近邻,直到 把P这个点云里面的点穷尽。
2.setIndices()=true,setSearchSurface()=false
这只会计算在给定的下标向量里面的下标所对应点的特征,比如p1是属于下标向量里面的,而p2不属于,则只会计算p1附近的点的特征。
3.setIndices()=false,setSearchSurface()=true
只会计算潜在表面的最近邻的点的特征,而不是整个点云。比如P和Q是两个点云,然后把P作为要被搜索的表面(潜在的表面),则Q的每个元素(q1,q2)会在P中找到最近的几个点。
4.setIndices()=true,setSearchSurface=true.这可能是最稀少的情况了,因为这既要满足下标,又要满足表面搜索。最后一幅图里面,先通过下标把q2给排除,然后再搜索q1在P里面的最近邻。
我们使用setSearchSurface()最多的例子,是当我们有一个高密度的输入数据集的时候,但是我们不想去计算里面所有点的特征,在这个例子里面我们通过setSearchSurface()来解决这个问题,而不是通过downsample(降低采样,往往通过pcl::VoxelGrid<T> filter)或者keypoints(关键采样)。我们把downsampled/keypoints作为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>); 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 () }
还有通过下标来降低采样的,下面这个例子
#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 () }