各特征描述详见以上大佬博客。
3D点云特征描述与提取是点云信息处理中最基础也是最关键的一部分,点云的识别,分割,重采样,配准,曲面重建等处理大部分算法,都严重依赖特征描述与提取的结果。从尺度上来分,一般分为
原理:
在原始表示形式下,点的定义是用笛卡尔坐标系坐标 x, y, z 相对于一个给定的原点来简单表示的三维映射系统的概念,假定坐标系的原点不随着时间而改变,这里有两个点p1和p2分别在时间t1和t2捕获,有着相同的坐标,对这两个点作比较其实是属于不适定问题(ill—posed problem),因为虽然相对于一些距离测度它们是相等的,但是它们取样于完全不同的表面,因此当把它们和临近的其他环境中点放在一起时,它们表达着完全不同的信息,这是因为在t1和t2之间局部环境有可能发生改变。一些获取设备也许能够提供取样点的额外数据,例如强度或表面反射率等,甚至颜色,然而那并不能完全解决问题,单从两个点之间来 对比仍然是不适定问题。
由于各种不同需求需要进行对比以便能够区分曲面空间的分布情况,应用软件要求更好的特征度量方式,因此作为一个单一实体的三维点概念和笛卡尔坐标系被淘汰了,出现了一个新的概念取而代之:局部描述子(locl descriptor)。
文献中对这一概念的描述有许多种不同的命名,如:形状描述子(shape descriptors)或几何特征(geometric features),文本中剩余部分都统称为点特征表示。通过包括周围的领域,特征描述子能够表征采样表面的几何性质,它有助于解决不适定的对比问题,理想情况下相同或相似表面上的点的特征值将非常相似(相对特定度量准则),而不同表面上的点的特征描述子将有明显差异。下面几个条件,通过能否获得相同的局部表面特征值,可以判定点特征表示方式的优劣:
(1)刚体变换-----即三维旋转和三维平移变化 不会影响特征向量F估计,即特征向量具有平移旋转不变性。
(2)改变采样密度-----原则上,一个局部表面小块的采样密度无论是大还是小,都应该有相同的特征向量值,即特征向量具有抗密度干扰性。
(3)噪声---数据中有轻微噪声的情况下,点特征表示在它的特征向量中必须保持相同或者极其相似的值,即特征向量对点云噪声具有稳定性。
通常,PCL中特征向量利用快速kd-tree查询 ,使用近似法来计算查询点的最近邻元素,通常有两种查询类型:K邻域查询,半径搜索两种方法
1.法向量估计与快速点特征直方图(FPFH)描述子
法向量估计:
- //打开点云代码
- pcl::PointCloud
::Ptr cloud (new pcl::PointCloud) ; - pcl::io::loadPCDFile ("../table_scene_lms400.pcd", *cloud);
-
- //创建法线估计估计向量
- pcl::NormalEstimation
ne; - ne.setInputCloud(cloud);
- //创建一个空的KdTree对象,并把它传递给法线估计向量
- //基于给出的输入数据集,KdTree将被建立
- pcl::search::KdTree
::Ptr tree (new pcl::search::KdTree ()) ; - ne.setSearchMethod(tree);
- //存储输出数据
- pcl::PointCloud
::Ptr cloud_normals (new pcl::PointCloud) ; - //使用半径在查询点周围3厘米范围内的所有临近元素
- ne.setRadiusSearch(0.03);
- //计算特征值
- ne.compute(*cloud_normals);
法向量估计后计算fpfh:
- #pragma once
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- using PointT = pcl::PointCloud
; - using PointN = pcl::PointCloud
; - using Normal = pcl::PointCloud
; -
- void test_demo()
- {
- pcl::PointCloud
::Ptr cloud(new pcl::PointCloud) ; - pcl::PointCloud
::Ptr voxeled_cloud(new pcl::PointCloud) ; - pcl::io::loadPCDFile("rabbit.pcd", *cloud);
- //下采样
- pcl::VoxelGrid
voxel; - voxel.setInputCloud(cloud);
- voxel.setLeafSize(0.1f, 0.1f,0.1f);
-
- voxel.filter(*voxeled_cloud);
-
- //法向量
- pcl::NormalEstimationOMP
ne; - ne.setNumberOfThreads(4);
- ne.setInputCloud(voxeled_cloud);
- pcl::search::KdTree
::Ptr tree; - ne.setSearchMethod(tree);
- ne.setRadiusSearch(0.5f);
- PointN::Ptr normal_cloud(new PointN);
- ne.compute(*normal_cloud);
-
- //显示法向量
- for (int i = 0; i < normal_cloud->size(); ++i)
- {
- normal_cloud->points[i].x = voxeled_cloud->points[i].x;
- normal_cloud->points[i].y = voxeled_cloud->points[i].y;
- normal_cloud->points[i].z = voxeled_cloud->points[i].z;
-
-
- }
- pcl::visualization::PCLVisualizer viewer;
- viewer.setBackgroundColor(0, 0, 0);
-
- viewer.addPointCloud(voxeled_cloud,"voxel_cloud");
- int level = 1; // 多少条法向量集合显示成一条
- float scale = 0.05; // 法向量长度
- viewer.addPointCloudNormals
(normal_cloud, level, scale, "normals"); - //显示原点云
- viewer.addPointCloud
(cloud, "cloud"); -
- pcl::visualization::PointCloudColorHandlerCustom
color_handler(normal_cloud, 255, 0, 0) ; - //fpfh特征直方图
- pcl::FPFHEstimation
fpfh; - fpfh.setInputCloud(voxeled_cloud);
- //fpfh.setSearchSurface(cloud);
- fpfh.setInputNormals(normal_cloud);
- pcl::search::KdTree
::Ptr tree1(new pcl::search::KdTree) ; - fpfh.setSearchMethod(tree1);
- pcl::PointCloud
::Ptr fpfh_cloud(new(pcl::PointCloud)) ; - fpfh.setRadiusSearch(0.05f);
- //检查法向量
- for (int i = 0; i < normal_cloud->size(); i++)
- {
- if (!pcl::isFinite
((*normal_cloud)[i])) - {
- PCL_WARN("normals[%d] is not finite\n", i);
- }
- }
- fpfh.compute(*fpfh_cloud);
-
- viewer.spin();
- system("pause");
-
-
- }

2.点特征直方图(PFH)描述子
正如点特征表示法所示,表面法线和曲率估计是某个点周围的几何特征基本表示法。虽然计算非常快速容易,但是无法获得太多信息,因为它们只使用很少的几个参数值来近似表示一个点的k邻域的几何特征。
然而大部分场景中包含许多特征点,这些特征点有相同的或者非常相近的特征值,因此采用点特征表示法,其直接结果就减少了全局的特征信息。
那么三维特征描述子中一位成员:点特征直方图(Point Feature Histograms),我们简称为PFH,从PCL实现的角度讨论其实施细节。PFH特征不仅与坐标轴三维数据有关,同时还与表面法线有关。
PFH计算方式通过参数化查询点与邻域点之间的空间差异,并形成一个多维直方图对点的k邻域几何属性进行描述。直方图所在的高维超空间为特征表示提供了一个可度量的信息空间,对点云对应曲面的6维姿态来说它具有不变性,并且在不同的采样密度或邻域的噪音等级下具有鲁棒性。
点特征直方图(PFH)表示法是基于点与其k邻域之间的关系以及它们的估计法线,简言之,它考虑估计法线方向之间所有的相互作用,试图捕获最好的样本表面变化情况,以描述样本的几何特征。因此,合成特征超空间取决于每个点的表面法线估计的质量。
如图所示,表示的是一个查询点(Pq) 的PFH计算的影响区域,Pq 用红色标注并放在圆球的中间位置,半径为r, (Pq)的所有k邻元素(即与点Pq的距离小于半径r的所有点)全部互相连接在一个网络中。最终的PFH描述子通过计算邻域内所有两点之间关系而得到的直方图,因此存在一个O(k) 的计算复杂性。

为了计算两点Pi和Pj及与它们对应的法线Ni和Nj之间的相对偏差,在其中的一个点上定义一个固定的局部坐标系,如图2所示。

使用上图中uvw坐标系,法线 和 之间的偏差可以用一组角度来表示,如下所示:

估计PFH特征
点特征直方图(PFH)在PCL中的实现是pcl_features模块的一部分。默认PFH的实现使用5个区间分类(例如:四个特征值中的每个都使用5个区间来统计),
以下代码段将对输入数据集中的所有点估计其对应的PFH特征。
- computePointPFHSignature (const pcl::PointCloud
&cloud, - const pcl::PointCloud
&normals, - const std::vector<int> &indices,
- int nr_split,
- Eigen::VectorXf &pfh_histogram);
3.SHOT
主要代码
- typedef pcl::PointXYZ PointT;
- typedef pcl::Normal PointNT;
- typedef pcl::SHOT352 FeatureT;
- pcl::SHOTEstimation
shot; - shot.setRadiusSearch(18 * resolution);
- shot.setInputCloud(keys);
- shot.setSearchSurface(cloud);
- shot.setInputNormals(cloud_normal);
- //shot.setInputReferenceFrames(lrf); //也可以自己传入局部坐标系
- pcl::PointCloud
::Ptr features(new pcl::PointCloud) ; - shot.compute(*features);
4.ROPS
由于RoPS是基于网格数据,所以如果输入的是点云数据需要先进行网格化处理。
主要代码
- pcl::PointCloud
::Ptr cloud(new pcl::PointCloud) ; - pcl::io::loadPCDFile(argv[1], *cloud);
-
- // 加载关键点
- pcl::PointCloud
::Ptr key_points(new pcl::PointCloud) ; - pcl::io::loadPCDFile(argv[2], *key_points);
- // 计算法向量
- pcl::NormalEstimation
n; - pcl::PointCloud
::Ptr normals(new pcl::PointCloud) ; - pcl::search::KdTree
::Ptr tree(new pcl::search::KdTree) ; - tree->setInputCloud(cloud);
- n.setInputCloud(cloud);
- n.setSearchMethod(tree);
- n.setKSearch(20);
- n.compute(*normals);
- // 连接数据
- pcl::PointCloud
::Ptr cloud_with_normals(new pcl::PointCloud) ; - pcl::concatenateFields(*cloud, *normals, *cloud_with_normals);
- //* cloud_with_normals = cloud + normals
- // ---- rops基于网格,所以要先将pcd点云数据重建网格 ---
- // Create search tree*
- pcl::search::KdTree
::Ptr tree2(new pcl::search::KdTree) ; - tree2->setInputCloud(cloud_with_normals);
-
- pcl::GreedyProjectionTriangulation
gp3;// Initialize objects - pcl::PolygonMesh triangles;
- // Set the maximum distance between connected points (maximum edge length)
- gp3.setSearchRadius(0.025);
- gp3.setMu(2.5); // Set typical values for the parameters
- gp3.setMaximumNearestNeighbors(100);
- gp3.setMaximumSurfaceAngle(M_PI / 4); // 45 degrees
- gp3.setMinimumAngle(M_PI / 18); // 10 degrees
- gp3.setMaximumAngle(2 * M_PI / 3); // 120 degrees
- gp3.setNormalConsistency(false);
- gp3.setInputCloud(cloud_with_normals);
- gp3.setSearchMethod(tree2);
- gp3.reconstruct(triangles); // Get result
-
- // ----- rops 描述-------
- // 由于pcl_1.8.0中rops还没有定义好的结构,所以采用pcl::Histogram存储描述子
- pcl::ROPSEstimation
135>> rops; - rops.setInputCloud(key_points);
- rops.setSearchSurface(cloud);
- rops.setNumberOfPartitionBins(5);
- rops.setNumberOfRotations(3);
- rops.setRadiusSearch(0.01);
- rops.setSupportRadius(0.01);
- rops.setTriangles(triangles.polygons);
- rops.setSearchMethod(pcl::search::KdTree
::Ptr(new pcl::search::KdTree < pcl::PointXYZ>)); - //feature size = number_of_rotations * number_of_axis_to_rotate_around * number_of_projections * number_of_central_moments
- //unsigned int feature_size = number_of_rotations_ * 3 * 3 * 5; //计算出135
- pcl::PointCloud
135>> description; - rops.compute(description); // 结果计算的是描述子。。需传入inputcloud和surface
- std::cout << "size is " << description.points.size()<
::Ptr cloud(new pcl::PointCloud) ; - pcl::io::loadPCDFile(argv[1], *cloud);
-
- // 加载关键点
- pcl::PointCloud
::Ptr key_points(new pcl::PointCloud) ; - pcl::io::loadPCDFile(argv[2], *key_points);
- // 计算法向量
- pcl::NormalEstimation
n; - pcl::PointCloud
::Ptr normals(new pcl::PointCloud) ; - pcl::search::KdTree
::Ptr tree(new pcl::search::KdTree) ; - tree->setInputCloud(cloud);
- n.setInputCloud(cloud);
- n.setSearchMethod(tree);
- n.setKSearch(20);
- n.compute(*normals);
- // 连接数据
- pcl::PointCloud
::Ptr cloud_with_normals(new pcl::PointCloud) ; - pcl::concatenateFields(*cloud, *normals, *cloud_with_normals);
- //* cloud_with_normals = cloud + normals
- // ---- rops基于网格,所以要先将pcd点云数据重建网格 ---
- // Create search tree*
- pcl::search::KdTree
::Ptr tree2(new pcl::search::KdTree) ; - tree2->setInputCloud(cloud_with_normals);
-
- pcl::GreedyProjectionTriangulation
gp3;// Initialize objects - pcl::PolygonMesh triangles;
- // Set the maximum distance between connected points (maximum edge length)
- gp3.setSearchRadius(0.025);
- gp3.setMu(2.5); // Set typical values for the parameters
- gp3.setMaximumNearestNeighbors(100);
- gp3.setMaximumSurfaceAngle(M_PI / 4); // 45 degrees
- gp3.setMinimumAngle(M_PI / 18); // 10 degrees
- gp3.setMaximumAngle(2 * M_PI / 3); // 120 degrees
- gp3.setNormalConsistency(false);
- gp3.setInputCloud(cloud_with_normals);
- gp3.setSearchMethod(tree2);
- gp3.reconstruct(triangles); // Get result
-
- // ----- rops 描述-------
- // 由于pcl_1.8.0中rops还没有定义好的结构,所以采用pcl::Histogram存储描述子
- pcl::ROPSEstimation
135>> rops; - rops.setInputCloud(key_points);
- rops.setSearchSurface(cloud);
- rops.setNumberOfPartitionBins(5);
- rops.setNumberOfRotations(3);
- rops.setRadiusSearch(0.01);
- rops.setSupportRadius(0.01);
- rops.setTriangles(triangles.polygons);
- rops.setSearchMethod(pcl::search::KdTree
::Ptr(new pcl::search::KdTree < pcl::PointXYZ>)); - //feature size = number_of_rotations * number_of_axis_to_rotate_around * number_of_projections * number_of_central_moments
- //unsigned int feature_size = number_of_rotations_ * 3 * 3 * 5; //计算出135
- pcl::PointCloud
135>> description; - rops.compute(description); // 结果计算的是描述子。。需传入inputcloud和surface
- std::cout << "size is " << description.points.size()<