精华内容
下载资源
问答
  • PCL中3D点云特征描述与提取(一)

    千次阅读 2021-10-08 16:45:18
    PCL中3D点云特征描述与提取1 特征描述与提取的概念及相关算法1.1 3D形状内容描述子1.2 旋转图像(Spin Images)1.3 PCL中特征描述与提取模块及类2 点云特征描述与提取入门级实例解析2.1 PCL中描述三维特征相关基础...


      3D点云特征描述与提取是点云信息处理中最基础也是最关键的一部分,点云的识别、分割、重采样、配准、曲面重建等处理的大部分算法,都严重依赖特征描述与提取的结果。从尺度上来分,一般分为局部特征描述和全局特征描述,例如局部的法线等几何形状特征的描述,全局的拓扑特征描述,都属于3D点云特征描述与提取范畴。在PCL中,目前已有很多基本的特征描述子与提取算法,相信在PCL的快速发展下,将来会集成和添加更多特征描述子和提取算法。

      本章首先对涉及的部分点云特征描述与提取的概念进行简介,由于特征描述子和提取算法的多样性,和实例相关的概念在后面结合实例也进行了详细介绍;其次对PCL的特征描述与提取相关模块及类进行简单介绍;最后通过应用实例来展示如何对PCL中特征描述与提取相关模块进行灵活运用,例如法线估计、各种点特征描述子的提取方法等。

    1 特征描述与提取的概念及相关算法

    1.1 3D形状内容描述子

      利用描述子建立曲面间的对应点在3D物体识别领域有广泛应用。采用一个向量描述曲面上指定点及其邻域的形状特征,通过匹配向量的值来建立不同曲面间点的对应关系,此向量即为指定点的描述子。3D形状内容描述子构造简单,辨别力强,且对噪声不敏感。其构造方法为:在以指定点p为中心的球形支撑域内,沿径向、方向角和俯仰角3个坐标方向划分成网格,统计落入网格内的点数,构造向量V。V的每个元素与支撑域内的一个网格对应,元素的值为对应网格中的点数,向量V即为点p的描述子。3D shape context网格划分如下图所示。详细内容请参考[Andrea Frome, Daniel Huber, Ravi Kolluri and Thomas Bulow, Jitendra Malik: Recognizing Objects in Range Data Using Regional Point Descriptors, In proceedings of the 8th European Conference on Computer Vision (ECCV), Prague, May 11-14, 2004]
    在这里插入图片描述

    3D shape context 网格划分

    1.2 旋转图像(Spin Images)

      旋转图像最早是Johnson提出的特征描述子,主要用于3D场景中的曲面匹配和模型识别。如下面第一幅图所示,在模型表面上,存在顶点 p p p 和其法向量 n n n 定义的二维基,以及切平面 P P P,假设模型上任意顶点 x x x ,现定义 α \alpha α x x x 在平面 P P P 上投影点与 p p p点的距离,规定其值取大于零的实数, β \beta β x x x 与其在平面 P P P 上投影点之间的距离,按照向上或向下规定其有正负之分,点 p p p 的旋转图像则为,将除 p p p 点外其他模型上的顶点在 P P P 上的投影( α i \alpha _{i} αi β i \beta _{i} βi),其中 i i i 表示顶点的一维索引,将( α i \alpha _{i} αi β i \beta _{i} βi)统计得到二维直方图即为点 p p p 的旋转图像,图像的坐标由 α \alpha α β \beta β 而定,强度为( α \alpha α β \beta β)落在同一统计区间的点的统计个数。下面第二幅图所示模型表面三个点的旋转图像可帮助大家理解。
    在这里插入图片描述

    旋转图像生成示意图

    在这里插入图片描述

    模型表面三个点的旋转图像

    1.3 PCL中特征描述与提取模块及类

      PCL中pcl_features库提供了特征描述与提取相关的基本数据结构与算法,目前PCL内部的特征提取算法包含基础和最新的点云或曲面模型相关的描述子实现,包括法线估计、多种基于近邻的局部描述算子、基于视角的全局描述算子等等,其依于commonsearchkdtreeoctreerange_image模块。类和函数的接口说明受篇幅所限,请感兴趣的读者自行查阅相关资料,或者查看官网

    2 点云特征描述与提取入门级实例解析

    2.1 PCL中描述三维特征相关基础

      本小节介绍点云库(PCL)中的三维特征描述子工作原理,以及在pcl::feature模块中类的通用调用习惯。

    2.1.1 理论基础

      在原始表示形式下,点的定义是用笛卡尔坐标系坐标 x x x y y y z z z 相对于一个给定的原点来简单表示的三维映射系统的概念,假定坐标系的原点不随着时间而改变,这里有两个点 p 1 p_{1} p1 p 2 p_{2} p2 ,分别在时间 t 1 t_{1} t1 t 2 t_{2} t2 捕获,有着相同的坐标。对这两个点做比较其实是属于不适定问题(ill-posed problem),因为虽然相对于一些距离测度(如:欧几里得度量)它们是相等的,但是它们取样于完全不同的表面,因此当把它们和邻近的其他环境中的点放在一起时,它们表达着完全不同的信息,这是因为在 t 1 t_{1} t1 t 2 t_{2} t2 之间局部环境有可能发生改变。一些获取设备也许能够提供取样点的额外数据,例如强度或表面反射率等,甚至颜色,然而那并不能完全解决问题,单从两个点之间来对比仍然是不适定问题。由于各种不同需求需要进行对比以便能够区分曲面空间的分布情况,应用软件要求更好的特征度量方式,因此作为一个单一实体的三维点概念和笛卡尔坐标系被淘汰了,出现了一个新的概念取而代之:局部描述子(local descriptor)。文献中对这一概念的描述有许多不同的命名,如:形状描述子(shape descriptor)或几何特征(geometric features),本文中剩余部分都统称之为点特征表示(point feature representations)。通过包括周围的邻域,特征描述子能够表征采样表面的几何性质,它有助于解决不适定的对比问题。如下图所示,理想情况下,相同或相似表面上的点的特征值将非常形似(相对特定度量准则),而不同表面上的点的特征描述子将有明显差异。下面几个条件,通过能否获得相同的局部表面特征值,可以判定点特征表示方式的优劣。

       ∙ \bullet 刚体变换(rigid transformations)—— 即三维旋转和三维平移变化不会影响特征向量F估计,即特征向量具有平移旋转不变性。
       ∙ \bullet 改变采样密度(varying sampling density)—— 原则上,一个局部表面小块的采样密度无论是大还是小,都应该有相同的特征向量值,即特征向量具有抗密度干扰性。
       ∙ \bullet 噪音(noise)—— 数据中有轻微噪音的情况下,点特征表示在它的特征向量中必须保持相同或者投其相似的值,即特征向量对点云噪声具有鲁棒性。

    在这里插入图片描述

    点特征描述子示意图

      通常,PCL中特征向量利用快速kd tree查询,使用近似法来计算查询点的最近邻元素,有两种常用的查询类型。
      (1)决定一个查询点的k邻域元素(k为用户已给参数)(也称为k-搜索)
      (2)在半径r的围内,确定一个查询点的所有邻元素(也称为半径-搜索)

    2.1.2 输入点云调用习惯

      因为所有点云库中的类都继承来自基类pcl::PCLBasepcl::Feature类接受以下两种不同方式的输入数据。
      (1)一个完整的点云数据集,由setInputCloud (PointCloudConstPtr &) 给出——此函数必须设置,这样后续特征算子才能正常计算,任何可以进行特征描述子估计的类,为给定的输入点云中的每个点估计一个特征向量。
      (2)点云数据集的一个子集,由setInputCloud (PointCloudConstPtr &)setIndices (IndicesConstPtr &) 给出——后面的setIndices 函数为可选设置。如果传入IndicesConstPtr 参数,则任何可以进行特征估计的类将为给定输入点云中的索引对应的点估计一个特征,默认情况下,如果没有给出一组索引,点云中的所有点参与计算。

      此外,通过一个附加调用程序,可以明确指定搜索时使用的点邻域集合setSearchSurface (PointCloudConstPtr &),这个调用是可选的,当搜索点邻域集合未给出时,则输入点云数据为默认的搜索空间。因为总是需要 setInputCloud (),所以我们可以使用<setInputCloud(), setIndices(), setSearchSurface()>来创建四个组合。假如我们有两个点云, P = { p 1 , p 2 , . . . , p n } P = \left \{ p_{1}, p_{2},..., p_{n}\right \} P={p1,p2,...,pn} Q = { q 1 , q 2 , . . . , q n } Q = \left \{ q_{1}, q_{2},..., q_{n}\right \} Q={q1,q2,...,qn},如下图所示,则表示了所以四种情况:从左到右边依次为,未指定索引和搜索点云集合、只指定了索引、只指定了搜索点云集合、指定了索引和搜索点云集合。

    点云输入组合

    点云输入组合

       ∙ \bullet setIndices() = false, setSearchSurface() = false —— 毫无疑问这是点云库中最常用的情况,用户只需要输入一个单一的点云数据集,并且为点云中的所有点估计一特征向量。不论一组索引和(或)搜索点云是否给定,都不希望保存不同的实现副本,无论何时,即使indices = false,PCL都会创建一组内部索引(为 std::vector<int>),这个索引集是指向整个数据集的(indices=1..NN是点云中点的数目)。上述与图中最左边的情况对应,首先,我们估计了 p 1 p_{1} p1的最近邻元素,然后是 p 2 p_{2} p2的最近邻元素,以此类推,直到我们估计完 P P P中的所有点。

       ∙ \bullet setIndices() = true, setSearchSurface() = false —— 如前面所提到的,特征估计方法只计算已给索引的点的特征。对应上图的第二种情况,这里,我们假设 p 2 p_{2} p2的索引不在已给的索引向量中,因此在 p 2 p_{2} p2点处,没有估计邻元素或者特征向量。

       ∙ \bullet setIndices() = false, setSearchSurface() = true —— 如第一种情况,对所有已给点进行特征向量估计,但是,在setSearchSurface()中给出的采样面点云将用来为输入点获取最近邻元素,而不是输入点云本身,上述对应图中第三种情况。如果 Q = { q 1 , q 2 } Q = \left \{ q_{1}, q_{2}\right \} Q={q1,q2}作为输入,是不同于 P P P的另一个给出的点云, P P P Q Q Q的搜索表面,那么将从 P P P中计算两个点 q 1 q_{1} q1 q 2 q_{2} q2的近邻。

       ∙ \bullet setIndices() = true, setSearchSurface() = true —— 这种组合可能是最少见的情况,索引和搜索点云都给定。这种情况下,将使用setSearchSurface()中给出的搜索点云,只对<input, indices>中的子集进行特征向量估计。上述对应图中最后(最右端)一种情况,这里,我们假设 q 2 q_{2} q2的索引没有在 Q Q Q的已给索引向量中,因此在 q 2 q_{2} q2点处,没有估计其邻元素或者特征。

      在使用 setSearchSurface()时,最有用的案例是:当有一个非常密集的输入点云数据集时,我们不想对它里面的所有点都进行特征估计,而是希望在找到的一些关键点处(使用pcl_keypoints中的方法进行估计),或者在点云的下采样版本中(如:使用pcl::VoxelGrid<T>过滤而获得的)进行特征估计。这种情况下,我们通过setInputCloud()来把下采样后的点云/ 关键点传递给特征估计算法,而把原始数据通过setSearchSurface()设置为搜索集合,从而提高程序的运行效率。

    2.2 估计一个点云的表面法线

      表面法线是几何体表面的重要属性,在很多领域都有大量应用,例如:在进行光照渲染时产生符合可视习惯的效果时需要表面法线信息才能正常进行,对于一个已知的几何体表面,根据垂直于点表面的矢量,因此推断表面某一点的法线方向通常比较简单。然而,由于我们获取的点云数据集在真实物体的表面表现为一组定点样本,这样就会有两种解决方法。
      (1)使用曲面重建技术,从获取的点云数据集中得到采样点对应的曲面,然后从曲面模型中计算表面法线。
      (2)直接从点云数据集中近似推断表面法线。

      本小节将针对后一种情况进行讲解,已知一个点云数据集,在其中的每个点处直接近似计算表面法线。

    2.2.1 理论基础

      尽管有许多不同的法线估计方法,本教程中着重讲解的是其中最简单的一个,表述如下:确定表面一点法线的问题近似于估计表面的一个相切面法线的问题,因此转换过来以后就变成一个最小二乘法平面拟合估计问题。
      注意:更多信息,包含最小二乘法问题的数学方程式,可以查看相关文章

      因此估计表面法线的解决方案就变成了分析一个协方差矩阵的特征矢量和特征值(或者PCA——主成分分析),这个协方差矩阵从查询点的近邻元素中创建。更具体地说,对于每一个点 P i P_{i} Pi,对应的协方差矩阵 C C C如下:
    C = 1 K ∑ i = 1 K ( P i − P ˉ ) ( P i − P ˉ ) T , C ⋅ V ⃗ j = λ j ⋅ V ⃗ j , j ϵ { 0 , 1 , 2 } C = \frac{1}{K}\sum_{i=1}^{K}\left ( P_{i} - \bar{P}\right )\left ( P_{i} - \bar{P} \right )^{T},C\cdot \vec{V}_{j}=\lambda _{j}\cdot \vec{V}_{j}, j\epsilon \left \{ 0,1,2 \right \} C=K1i=1K(PiPˉ)(PiPˉ)T,CV j=λjV j,jϵ{0,1,2}
    此处, K K K 是点 P i P_{i} Pi 邻近点的数目, P ˉ \bar{P} Pˉ 表示最近邻元素的三维质心, λ j \lambda _{j} λj 是协方差矩阵的第 j j j 个特征值, V ⃗ j \vec{V}_{j} V j 是第 j j j 个特征向量。

      在PCL内估计一点集对应的协方差矩阵,可以使用以下函数调用实现:

    //定义每个表面小块的3x3协方差矩阵的存储对象
    Eigen::Matrix3f covariance_matrix;
    //定义一个表面小块的质心坐标16字节对齐存储对象
    Eigen::Vector4f xyz_centroid;
    //估计质心坐标
    compute3DCentroid(cloud, xyz_centroid);
    //计算3x3协方差矩阵
    computeCovarianceMatrix(cloud, xyz_centroid, covariance_matrix);
    

      通常,没有数学方法能解决法线的正负向问题,如上所示,通过主成分分析法(PCA)来计算它的方向也具有二义性,无法对整个点云数据集的法线方向进行一致性定向。下图(1)中显示出对一个更大数据集的两部分产生的影响,此数据集来自于厨房环境的一部分,很明显估计的法线方向并非完全一致,图(2)部分展现了其对应扩展的高斯图像(EGI),也称为法线球体(normal sphere),它描述了点云中所有法线的方向。由于数据集是2.5维,其只从一个单一的视角获得,因此法线应该仅呈现出一半球体的扩展高斯图像(EGI)。然而,由于定向的不一致性,它们遍布整个球体,如下图所示。

    在这里插入图片描述

              (1)估计厨房环境的表面法线                (2)法线球体

      如果实际知道视点 V p V_{p} Vp,那么这个问题的解决是非常简单的。对所有法线 V ⃗ j \vec{V}_{j} V j 定向只需要使它们一致朝向视点方向,满足下面的方程式:
    V ⃗ j ⋅ ( V p − P i ) > 0 \vec{V}_{j}\cdot \left ( V_{p}-P_{i} \right )>0 V j(VpPi)>0
      下图展示了上图中的数据集的所有法线被一致定向到视点后的结果:

    在这里插入图片描述

    将所有法线一致定向到后视点的结果

      在PCL中对一个已知点的法线进行手动重新定向,可以使用如下代码:

    flipNormalTowardsViewpoint (const PointT &point, float vp_x, float vp_y, float vp_z, Eigen::Vector4f &normal)
    

      注意:如果数据集是从多个捕获视点中配准后集成的,那么上述法线的一致性定向方法就不适用了。需要使用更复杂的算法,可以查看文章

    2.2.2 选择合适的尺度

      如之前介绍的,在估计一个点的表面法线时,我们需要从周围支持这个点的邻近点着手(也称作k邻域)。最近邻估计问题的具体内容又提出了另一个问题“合适的尺度”:已知一个取样点云数据集,k的正确取值是多少(k通过pcl::Feature::setKSearch给出)或者确定一个点r为半径的圆内的最近邻元素集时使用的半径r该取什么值(r通过pcl::Feature::setRadiusSearch给出)。这个问题非常重要,并且在一个点特征算子的自动估计时(例如用户没有给定阈值)是一个限制因素。为了更好地说明这个问题,以下图示表现了选择更小尺度(如:r值或k取相对小)与选择更大尺度(如:r值或k值比较大)时的两种不同效果,下面两幅图分别为近视图和远视图,两图中左边部分展示选择了一个合理的比例因子,估计的表面法线近似垂直于两个平面,即使在互相垂直的边缘部分,可明显看到边缘。如果这个尺度取得太大(右边部分),这样邻近点集将更大范围地覆盖邻近表面的点,估计的点特征表现就会扭曲失真,在两个平面边缘处出现旋转表面法线,以及模糊不清的边界,这样就隐藏了一些细节信息。

    在这里插入图片描述

    表面法线估计近视图

    在这里插入图片描述

    表面法线估计远视图

      无法深入探究更多讨论,现在可粗略假设,以应用程序所需的细节需求为参考,选择确定点的邻域所用的尺度。简言之,如果杯子手柄和圆柱体部分之间边缘的曲率是重要的,那么需要足够小的尺度来捕获这些细节信息,而在其他不需要细节信息的应用中可选择大的尺度。

    2.2.3 法线估计实例详解

      首先创建一个工作空间normal_estimation,然后再在工作空间创建一个文件夹src用于存放源代码:

    mkdir -p normal_estimation/src
    

      接着,在normal_estimation/src路径下,创建一个文件并命名为normal_estimation.cpp,拷贝如下代码:

    #include <pcl/io/io.h>
    #include <pcl/io/pcd_io.h>
    #include <pcl/features/integral_image_normal.h>
    #include <pcl/visualization/cloud_viewer.h>
    #include <pcl/point_types.h>
    #include <pcl/features/normal_3d.h>
    
    int main ()
    {
        /* 加载点云 */
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
        pcl::io::loadPCDFile ("../pcd/table_scene_lms400.pcd", *cloud);
    
        pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;   // 创建法线估计对象
        ne.setInputCloud (cloud);                               // 把原始点云数据传递给法线估计对象
    
        //基于给出的输入数据集,kdtree将被建立
        pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());   // 创建一个空的kdtree对象
        ne.setSearchMethod (tree);      // 把kdtree对象传递给法线估计对象
    
        pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);         // 创建输出数据集对象
        
        ne.setRadiusSearch (0.03);      // 使用半径在查询点周围3厘米范围内的所有近邻元素
        
        ne.compute (*cloud_normals);    // 计算法线,并将结果存储到cloud_normals
        
        /* 法线可视化 */
        pcl::visualization::PCLVisualizer viewer("PCL Viewer");
        viewer.setBackgroundColor (0.0, 0.0, 0.0);
        // viewer.addPointCloud(cloud);
        viewer.addPointCloudNormals<pcl::PointXYZ,pcl::Normal>(cloud, cloud_normals);
    
        while (!viewer.wasStopped ())
        {
            viewer.spinOnce ();
        }
    
        return 0;
    }
    

      法线估计类NormalEstimation的实际计算调用内部程序执行以下操作。

      (1)对点 P P P 中的每个点,得到 p p p 点的最近邻元素
      (2)计算 p p p 点的表面法线 n n n
      (3)检查 n n n 的方向是否一致指向视点,如果不是则翻转。

      视点坐标默认为(0,0,0),可以使用以下代码进行更换:

    setViewPoint (float vpx, float vpy, float vpz)
    

      计算单个点的法线,使用:

    computePointNormal(const pcl::PointCloud<PointInT> &cloud, const std::vector<int> &indices, Eigen::Vector4f &plane_parameters, float &curvature)
    

      此处,cloud是包含点的输入点云,indices是点的k-最近邻元素集索引,plane_parameterscurvature是法线估计的输出,plane_parameters前三个坐标中, 以(nx,ny,nz)来表示法线。输出表面曲率curvature通过协方差矩阵的特征值之间的运算估计得到。

    【编译和运行程序】
      在工作空间根目录normal_estimation下,编写CMakeLists.txt文件如下:

    cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
    project(normal_estimation)
    
    find_package(PCL 1.2 REQUIRED)
    
    include_directories(${PCL_INCLUDE_DIRS})
    link_directories(${PCL_LIBRARY_DIRS})
    add_definitions(${PCL_DEFINITIONS})	
    
    add_executable (${PROJECT_NAME}_node src/normal_estimation.cpp)
    target_link_libraries (${PROJECT_NAME}_node ${PCL_LIBRARIES})
    

      在工作空间根目录normal_estimation下创建一个build文件夹,用于存放编译过程中产生的文件,然后执行编译:

    mkdir build
    cd build
    cmake ..
    make
    

      此时,会在build文件夹下生成一个可执行文件normal_estimation_node,运行该可执行文件:

    ./normal_estimation_node
    

      运行上述命令后,可以在3D可视化窗口中看到如下效果:

    在这里插入图片描述

    法线估计前原始点云

    在这里插入图片描述

    法线估计结果可视化

      上述代码片段估计了输入数据集中所有点的一组曲面法线,可以对上述代码稍微修改,从而只为输入数据集中的一部分点估计一组曲面法线。修改之后的代码如下所示:

    #include <pcl/io/io.h>
    #include <pcl/io/pcd_io.h>
    #include <pcl/features/integral_image_normal.h>
    #include <pcl/visualization/cloud_viewer.h>
    #include <pcl/point_types.h>
    #include <pcl/features/normal_3d.h>
    #include <boost/make_shared.hpp>
    
    int main ()
    {
        /* 加载点云 */
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_sub (new pcl::PointCloud<pcl::PointXYZ>);
        pcl::io::loadPCDFile ("../pcd/table_scene_lms400.pcd", *cloud);
    
        std::vector<int> indices (std::floor (cloud->size () / 2)); // 创建原始输入点云的一个子集索引,取前50%
        for (std::size_t i = 0; i < indices.size (); ++i)
        {
            indices[i] = i;
            cloud_sub->push_back(cloud->at(i));
        }
    
        pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;   // 创建法线估计对象
        ne.setInputCloud (cloud);                               // 把原始点云数据传递给法线估计对象
    
        pcl::IndicesPtr indicesptr (new std::vector<int> (indices));
        ne.setIndices (indicesptr);     // 把子集的索引传递给法线估计对象
    
        //基于给出的输入数据集,kdtree将被建立
        pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());   // 创建一个空的kdtree对象
        ne.setSearchMethod (tree);      // 把kdtree对象传递给法线估计对象
    
        pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);         // 创建输出数据集对象
        
        ne.setRadiusSearch (0.03);      // 使用半径在查询点周围3厘米范围内的所有近邻元素
        
        ne.compute (*cloud_normals);    // 计算法线,并将结果存储到cloud_normals
    
        /* 法线可视化 */
        pcl::visualization::PCLVisualizer viewer("PCL Viewer");
        viewer.setBackgroundColor (0.0, 0.0, 0.0);
        // viewer.addPointCloud(cloud);
        viewer.addPointCloudNormals<pcl::PointXYZ,pcl::Normal>(cloud_sub, cloud_normals);
    
        while (!viewer.wasStopped ())
        {
            viewer.spinOnce ();
        }
    
        return 0;
    }
    

      上述代码中创建原始输入点云的一个子集索引,取前50%,然后利用 setIndices 函数把子集的索引传递给法线估计对象,这样求解出来的就是这一组子集所对应的曲面法线,结果如下所示:

    在这里插入图片描述

    法线估计结果可视化(求解子集所对应的曲面法线)

      最后,下面的代码片段将为输入数据集中的所有点估计一组曲面法线,但将使用另一个数据集估计它们的最近邻:

    #include <pcl/io/io.h>
    #include <pcl/io/pcd_io.h>
    #include <pcl/features/integral_image_normal.h>
    #include <pcl/visualization/cloud_viewer.h>
    #include <pcl/point_types.h>
    #include <pcl/features/normal_3d.h>
    #include <pcl/filters/voxel_grid.h>
    #include <boost/make_shared.hpp>
    
    int main ()
    {
        /* 加载点云 */
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_downsampled (new pcl::PointCloud<pcl::PointXYZ>);
        pcl::io::loadPCDFile ("../pcd/table_scene_lms400.pcd", *cloud);
    
        pcl::VoxelGrid<pcl::PointXYZ> sor;      // 创建滤波对象
        sor.setInputCloud (cloud);              // 给滤波对象设置需要过滤的点云
        sor.setLeafSize (0.01f, 0.01f, 0.01f);  // 设置滤波时创建的体素大小为1cm立方体
        sor.filter (*cloud_downsampled);        // 执行滤波处理,存储输出到cloud_downsampled
    
        pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;   // 创建法线估计对象
        ne.setInputCloud (cloud_downsampled);                   // 把下采样后的点云数据传递给法线估计对象
    
        // Pass the original data (before downsampling) as the search surface
        ne.setSearchSurface (cloud);            // 传递原始数据(下采样之前的点云数据)作为搜索点云
    
        //基于给出的输入数据集,kdtree将被建立
        pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());   // 创建一个空的kdtree对象
        ne.setSearchMethod (tree);      // 把kdtree对象传递给法线估计对象
    
        pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);         // 创建输出数据集对象
        
        ne.setRadiusSearch (0.03);      // 使用半径在查询点周围3厘米范围内的所有近邻元素
        
        ne.compute (*cloud_normals);    // 计算法线,并将结果存储到cloud_normals
        
        /* 法线可视化 */
        pcl::visualization::PCLVisualizer viewer("PCL Viewer");
        viewer.setBackgroundColor (0.0, 0.0, 0.0);
        // viewer.addPointCloud(cloud);
        viewer.addPointCloudNormals<pcl::PointXYZ,pcl::Normal>(cloud_downsampled, cloud_normals);
    
        while (!viewer.wasStopped ())
        {
            viewer.spinOnce ();
        }
    
        return 0;
    }
    

      在上述代码中,利用 setInputCloud 函数把下采样后的点云数据传递给法线估计对象,然后利用 setSearchSurface 函数传递原始数据(下采样之前的点云数据)作为搜索点云,最终可视化结果如下所示:
    在这里插入图片描述

    法线估计结果可视化(指定搜索时使用的点邻域集合)

    2.2.4 使用OpenMP加速法线估计

      对于对运算速度有要求的用户,PCL点云库提供了一个表面法线的附加实现程序,它使用多核/多线程开发规范,利用OpenMP来提高计算速度。它的类命名为pcl::NormalEstimationOMP,并且它的应用程序接口(API)100%兼容单线程pcl::NormalEstimation,这使它适合作为一个可选提速方法。在8核系统中,可以轻松提速6-8倍。

    2.3 使用积分图进行法线估计

      本小节我们将学习如何使用积分图(integral images)计算一个有序点云的法线,注意该方法只适用于有序点云。

      首先创建一个工作空间normal_estimation_using_integral_images,然后再在工作空间创建一个文件夹src用于存放源代码:

    mkdir -p normal_estimation_using_integral_images/src
    

      接着,在normal_estimation_using_integral_images/src路径下,创建一个文件并命名为normal_estimation_using_integral_images.cpp,拷贝如下代码:

    #include <pcl/io/io.h>
    #include <pcl/io/pcd_io.h>
    #include <pcl/features/integral_image_normal.h>
    #include <pcl/visualization/cloud_viewer.h>
    
    int main ()
    {
        /* 加载点云 */
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
        pcl::io::loadPCDFile ("../pcd/table_scene_mug_stereo_textured.pcd", *cloud);
    
        /* 估计法线 */
        pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
        pcl::IntegralImageNormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
        ne.setNormalEstimationMethod (ne.AVERAGE_3D_GRADIENT);
        ne.setMaxDepthChangeFactor(0.02f);
        ne.setNormalSmoothingSize(10.0f);
        ne.setInputCloud(cloud);
        ne.compute(*normals);
    
        /* 法线可视化 */
        pcl::visualization::PCLVisualizer viewer("PCL Viewer");
        viewer.setBackgroundColor (0.0, 0.0, 0.0);
        viewer.addPointCloudNormals<pcl::PointXYZ,pcl::Normal>(cloud, normals);
        while (!viewer.wasStopped ())
        {
            viewer.spinOnce ();
        }
        return 0;
    }
    

    【解释说明】
      上述代码的第一部分,我们从文件中加载了一个点云存储在点云对象,以备后续作为法线估计对象的输入:

    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
    pcl::io::loadPCDFile ("../pcd/table_scene_mug_stereo_textured.pcd", *cloud);
    

      在第二部分中,定义了存储估计法线的点类型指针,并为创建了一个积分图法线估计的对象ne设置对象计算时需要的参数,例如估计方法、点云等:

    pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
    pcl::IntegralImageNormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
    ne.setNormalEstimationMethod (ne.AVERAGE_3D_GRADIENT);	// 设置估计方法
    ne.setMaxDepthChangeFactor(0.02f);						// 最大深度变化系数
    ne.setNormalSmoothingSize(10.0f);						// 优化法线方向时考虑邻域大小
    ne.setInputCloud(cloud);								// 输入点云,必须为有序点云
    ne.compute(*normals);									// 执行法线估计,存储结果到 normals
    

      以下是可使用的法线估计方法:

    enum NormalEstimationMethod
    {
    	COVARIANCE_MATRIX,
    	AVERAGE_3D_GRADIENT,
    	AVERAGE_DEPTH_CHANGE
    }
    

      COVARIANCE_MATRIX模式从具体某个点的局部邻域的协方差矩阵创建9个积分图,来计算这个点的法线。AVERAGE_3D_GRADIENT模式创建了6个积分图来计算水平和垂直方向平滑后的三维梯度,并使用两个梯度间的向量积计算法线。AVERAGE_DEPTH_CHANGE模式只创建了一个单一的积分图,并从平均深度变化计算法线。

    【编译和运行程序】
      在工作空间根目录normal_estimation_using_integral_images下,编写CMakeLists.txt文件如下:

    cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
    
    project(normal_estimation_using_integral_images)
    
    find_package(PCL 1.2 REQUIRED)
    include_directories(${PCL_INCLUDE_DIRS})
    link_directories(${PCL_LIBRARY_DIRS})
    
    add_definitions(${PCL_DEFINITIONS})	
    add_executable (${PROJECT_NAME}_node src/normal_estimation_using_integral_images.cpp)
    target_link_libraries (${PROJECT_NAME}_node ${PCL_LIBRARIES})
    

      在工作空间根目录normal_estimation_using_integral_images下创建一个build文件夹,用于存放编译过程中产生的文件,然后执行编译:

    mkdir build
    cd build
    cmake ..
    make
    

      此时,会在build文件夹下生成一个可执行文件normal_estimation_using_integral_images_node,运行该可执行文件:

    ./normal_estimation_using_integral_images_node
    

      运行上述命令后,可以在3D可视化窗口中看到如下效果:

    在这里插入图片描述

    积分图法线估计示例结果

      从上图可以看出,法线方向基本一致朝向视点,视图视点朝向场景中的桌面,桌面上的杯子处出现平行于桌面的法线,而桌面上的点集的法线都垂直于桌面并指向点云本身获取时的视点,利用此方法进行法线估计只适用于有序点云,对于无序点云就只能采用其他方法了。

    展开全文
  • 3D点云特征提取

    2021-08-16 16:50:42
  • 点云特征点的提取,主要目的是为了配准,然而过滤掉大量非特征点数据,起到了减少数据量的作用。 因为点云本身是稀疏的,所以点云的主要是特征是面特征,其次是线特征,很难利用点特征。 平面的特征是曲率小,因此...

    点云帧数据

    激光雷达点云数据会以一定频率从激光雷达传输到接收器,比如10Hz,就是每间隔0.1秒发送一帧点云数据。

    激光SLAM接收到每帧点云数据后,需要提取特征点,然后进行点云配准得到帧间位姿变换,累计变换得到里程计。

    点云特征数据

    点云特征点的提取,主要目的是为了配准,然而过滤掉大量非特征点数据,起到了减少数据量的作用。

    因为点云本身是稀疏的,所以点云的主要是特征是面特征,其次是线特征,很难利用点特征。

    平面的特征是曲率小,因此点云中曲率小的点可以标记为平面点,曲率可以用来提取点云平面特征。

    空间面曲率的计算可以用点周围小片区域来估计。有一种简单计算曲率(空间面曲率)的方法,就是用线曲率近似面曲率。

    点云曲率的挑选需要用到点云索引。虽然激光雷达产生的点云帧是二维的,但是pcl中存储的点云是一维的(不方便操作)。根据激光雷达点云帧的产生机制,我们可以对一维pcl点云重建索引,将点信息存储在二维数组中。

    点云数据的输出

    最终需要的点云数据是平面特征点云,我们可以把平面特征点云发布出去供后面配准程序使用,同时也可以把原始点云帧发布出去方便在rviz中查看。

    S-LOAM代码结构

     

    S-LOAM的ros节点图

     

    header.h

    代码注释较详细,如下所示。

    #ifndef SLOAM_HEADER_H
    #define SLOAM_HEADER_H
    
    #include <vector>
    #include <queue>
    #include <thread>
    #include <mutex>
    #include <pcl/common/eigen.h>
    #include <pcl/common/transforms.h>
    #include <tf/LinearMath/Quaternion.h>
    #include <tf/transform_datatypes.h>
    #include <pcl/filters/voxel_grid.h>
    #include "ros/ros.h"
    #include "sensor_msgs/PointCloud2.h"
    #include "nav_msgs/Odometry.h"
    #include "nav_msgs/Path.h"
    #include "pcl_conversions/pcl_conversions.h"
    #include "pcl/kdtree/kdtree_flann.h"
    #include "ceres/ceres.h"
    #include "ceres/rotation.h"
    #include <gtsam/geometry/Rot3.h>
    #include <gtsam/geometry/Pose3.h>
    #include <gtsam/slam/PriorFactor.h>
    #include <gtsam/slam/BetweenFactor.h>
    #include <gtsam/navigation/GPSFactor.h>
    #include <gtsam/navigation/ImuFactor.h>
    #include <gtsam/navigation/CombinedImuFactor.h>
    #include <gtsam/nonlinear/NonlinearFactorGraph.h>
    #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
    #include <gtsam/nonlinear/Marginals.h>
    #include <gtsam/nonlinear/Values.h>
    #include <gtsam/inference/Symbol.h>
    #include <gtsam/nonlinear/ISAM2.h>
    #include <pcl/registration/icp.h>
    
    //int N_SCAN_ROW=16;    // 激光雷达线数,16线
    int N_SCAN_ROW=64;      // 激光雷达线数,64线
    
    /**
     * 点信息结构
     * value,indexInRow
     */
    typedef struct {
        float value;        // 属性值,比如曲率
        int indexInRow;     // 行内点索引
    } PointInfo;
    
    /**
     * 点结构
     * x,y,z,intensity,ring,time
     * 按照pcl规定,自定义点云结构,主要用于点云数据的接收与解析
     * 自定义点云结构的缺点是,在有些pcl函数中无法识别,比如kdtree中
    */
    struct VelodynePointXYZIRT {
        PCL_ADD_POINT4D       // 位置
        PCL_ADD_INTENSITY;    // 激光点反射强度,也可以存点的索引
        //uint16_t ring;      // 各点所在扫描线号,可选
        //float time;         // 各点时间戳,相对于帧第一个点时间差,可选
        EIGEN_MAKE_ALIGNED_OPERATOR_NEW
    } EIGEN_ALIGN16;          // 内存16字节对齐,EIGEN SSE优化要求
    // 注册为PCL点云格式
    POINT_CLOUD_REGISTER_POINT_STRUCT (VelodynePointXYZIRT,
        (float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)
        //(uint16_t, ring, ring)(float, time, time)
    )
    
    /**
     * 6D位姿点结构定义(x,y,z,roll,pitch,yaw)
     * x,y,z,intensity,roll,pitch,yaw,time
    */
    struct PointXYZIRPYT
    {
        PCL_ADD_POINT4D
        PCL_ADD_INTENSITY;
        float roll;
        float pitch;
        float yaw;
        double time;
        EIGEN_MAKE_ALIGNED_OPERATOR_NEW
    } EIGEN_ALIGN16;
    
    POINT_CLOUD_REGISTER_POINT_STRUCT (PointXYZIRPYT,
        (float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)
        (float, roll, roll)(float, pitch, pitch)(float, yaw, yaw)
        (double, time, time)
    )
    
    #endif //SLOAM_HEADER_H

    frameFeature.cpp

    代码注释较详细,如下所示。

    /**
     * Created by haocaichao on 2021/8/29
     *
     * 点云特征提取
     * (1)接收原始点云
     * (2)重建点云索引
     * (3)计算点云曲率
     * (4)提取平面特征点,点云抽稀
     * (5)发布点云
     */
    
    #include "header.h"
    
    typedef VelodynePointXYZIRT PointType;   //点类型名称重定义,用于接收点云中各点
    typedef pcl::PointXYZI PointTypeOut;     //pcl点类型名称重定义,简化
    
    ros::Subscriber sub_lidar_frame_cloud;   //定义ros订阅者,接收激光雷达
    ros::Publisher pub_plane_frame_cloud;    //定义ros发布者,发布平面特征点云
    ros::Publisher pub_org_frame_cloud;      //定义ros发布者,发布原始点云
    //定义pcl点云对象,存储原始点云
    pcl::PointCloud<PointType>::Ptr framePtr(new pcl::PointCloud<PointType>());
    //定义pcl点云对象,存储平面特征点云
    pcl::PointCloud<PointTypeOut>::Ptr framePlanePtr(new pcl::PointCloud<PointTypeOut>());
    //定义容器,存储每线点云
    std::vector<pcl::PointCloud<PointType>> v_scan_row = std::vector<pcl::PointCloud<PointType>>(N_SCAN_ROW);
    //定义容器,存储每线点云中各点信息
    std::vector<std::vector<PointInfo>> v_scan_row_info = std::vector<std::vector<PointInfo>>(N_SCAN_ROW);
    float planeMin=0.5;      //定义平面曲率最小门槛值
    int planeSpan=2;         //定义点间隔,用于抽稀
    int rowIndexStart=0;     //定义点云线内点起点索引
    int rowIndexEnd=0;       //定义点云线内点终点索引
    pcl::VoxelGrid<PointTypeOut> downSizeFilterPlane;  //定义点云下采样对象,用于点云抽稀
    
    //接收原始点云,处理,发布
    void cloudHandler(const sensor_msgs::PointCloud2ConstPtr &cldMsg) {
        framePtr->clear();                    //存储点云之前需要先清空
        framePlanePtr->clear();
        v_scan_row = std::vector<pcl::PointCloud<PointType>>(N_SCAN_ROW);
        v_scan_row_info = std::vector<std::vector<PointInfo>>(N_SCAN_ROW);
        //将ros点云消息类型转换为pcl点云对象
        pcl::fromROSMsg(*cldMsg, *framePtr);
        //遍历点云各点,重建点云索引
        for (size_t i = 0; i < framePtr->points.size(); ++i) {
            PointType point;
            point.x = framePtr->points[i].x;
            point.y = framePtr->points[i].y;
            point.z = framePtr->points[i].z;
            point.intensity = framePtr->points[i].intensity;
            int scanID = -1;
            int flag = 2;         //1-使用原始点云线号ring信息  2-根据垂向角度计算点云线号
            if (flag == 1) {
    //            scanID = framePtr->points[i].ring;
            } else {
                //计算垂向角度
                float angle = atan(point.z / sqrt(point.x * point.x + point.y * point.y)) * 180 / M_PI;
                if (N_SCAN_ROW == 16) {     //16线点云线号计算
                    if (angle >= -15 || angle <= 15) {
                        scanID = int((angle + 15) / 2 + 0.5);
                    }
                }
                if (N_SCAN_ROW == 64) {     //64线点云线号计算
                    if (angle >= -24.33 || angle <= 2) {
                        if (angle >= -8.83){
                            scanID = int((2 - angle) * 3.0 + 0.5);
                        }else{
                            scanID = N_SCAN_ROW / 2 + int((-8.83 - angle) * 2.0 + 0.5);
                        }
                    }
                }
            }
            if (scanID > -1 && scanID < N_SCAN_ROW) {        //每条扫描线是一个点云对象
                PointInfo p_info;
                p_info.value = 0;
                p_info.indexInRow = v_scan_row[scanID].size();
                point.intensity=p_info.indexInRow+scanID/100.0;
                v_scan_row[scanID].push_back(point);         //用数组存储各线点云数据
                v_scan_row_info[scanID].push_back(p_info);   //用另一个数组同步存储各点信息
            }
        }
    
        //计算点云曲率
        for (int i = 0+rowIndexStart; i < N_SCAN_ROW-rowIndexEnd; i++) {
            for (int j = 5; j < int(v_scan_row[i].size()) - 5; j++) {
                float diffX =
                        v_scan_row[i].points[j - 5].x + v_scan_row[i].points[j - 4].x + v_scan_row[i].points[j - 3].x +
                        v_scan_row[i].points[j - 2].x + v_scan_row[i].points[j - 1].x
                        - 10 * v_scan_row[i].points[j].x
                        + v_scan_row[i].points[j + 1].x + v_scan_row[i].points[j + 2].x + v_scan_row[i].points[j + 3].x +
                        v_scan_row[i].points[j + 4].x + v_scan_row[i].points[j + 5].x;
                float diffY =
                        v_scan_row[i].points[j - 5].y + v_scan_row[i].points[j - 4].y + v_scan_row[i].points[j - 3].y +
                        v_scan_row[i].points[j - 2].y + v_scan_row[i].points[j - 1].y
                        - 10 * v_scan_row[i].points[j].y
                        + v_scan_row[i].points[j + 1].y + v_scan_row[i].points[j + 2].y + v_scan_row[i].points[j + 3].y +
                        v_scan_row[i].points[j + 4].y + v_scan_row[i].points[j + 5].y;
                float diffZ =
                        v_scan_row[i].points[j - 5].z + v_scan_row[i].points[j - 4].z + v_scan_row[i].points[j - 3].z +
                        v_scan_row[i].points[j - 2].z + v_scan_row[i].points[j - 1].z
                        - 10 * v_scan_row[i].points[j].z
                        + v_scan_row[i].points[j + 1].z + v_scan_row[i].points[j + 2].z + v_scan_row[i].points[j + 3].z +
                        v_scan_row[i].points[j + 4].z + v_scan_row[i].points[j + 5].z;
                //存储各线各点曲率值
                v_scan_row_info[i][j].value = (diffX * diffX + diffY * diffY + diffZ * diffZ);
            }
        }
    
        //遍历各线,再遍历各点,根据曲率门槛值筛选出平面特征点云
        for (int i = 0+rowIndexStart; i < N_SCAN_ROW-rowIndexEnd; i++) {
            size_t jstart = 0;
            for (size_t j = 0; j < v_scan_row_info[i].size(); j++) {
                if (j >= jstart && v_scan_row_info[i][j].value < planeMin) {
                    PointTypeOut pt;
                    pt.x = v_scan_row[i][v_scan_row_info[i][j].indexInRow].x;
                    pt.y = v_scan_row[i][v_scan_row_info[i][j].indexInRow].y;
                    pt.z = v_scan_row[i][v_scan_row_info[i][j].indexInRow].z;
                    pt.intensity = v_scan_row[i][v_scan_row_info[i][j].indexInRow].intensity;
                    framePlanePtr->push_back(pt);
                    jstart = j + planeSpan;      //按指定间隔提取点云,相当于抽稀
                }
            }
        }
        //点云下采样,抽稀
        pcl::PointCloud<PointTypeOut>::Ptr cloud_temp(new pcl::PointCloud<PointTypeOut>());
        downSizeFilterPlane.setInputCloud(framePlanePtr);
        downSizeFilterPlane.filter(*cloud_temp);
        //发布平面特征点云
        sensor_msgs::PointCloud2 planeCloudMsg;
        pcl::toROSMsg(*framePlanePtr, planeCloudMsg);      //将pcl点云对象转换为ros点云消息类型
        planeCloudMsg.header.stamp = cldMsg->header.stamp;
        planeCloudMsg.header.frame_id = "map";
        pub_plane_frame_cloud.publish(planeCloudMsg);
        //发布原始点云
        sensor_msgs::PointCloud2 orgCloudMsg;
        orgCloudMsg=*cldMsg;
        orgCloudMsg.header.frame_id="map";
        pub_org_frame_cloud.publish(orgCloudMsg);
    }
    
    int main(int argc, char **argv) {
        //针对不同线数激光雷达数据,设置不同参数
        if(N_SCAN_ROW==16){
            planeMin=0.05;
            planeSpan=3;
        }
        if(N_SCAN_ROW==64){
            planeMin=0.005;
            planeSpan=25;
            rowIndexStart=5;
            rowIndexEnd=5;
        }
        downSizeFilterPlane.setLeafSize(0.2,0.2,0.2);   //设置抽稀参数
    
        ros::init(argc, argv, "FrameFeature");
        ros::NodeHandle nh;
        //订阅原始激光雷达数据
        sub_lidar_frame_cloud = nh.subscribe<sensor_msgs::PointCloud2>("/velodyne_points", 10, cloudHandler);
        //发布平面特征点云
        pub_plane_frame_cloud = nh.advertise<sensor_msgs::PointCloud2>("/plane_frame_cloud1", 100);
        //发布原始点云
        pub_org_frame_cloud = nh.advertise<sensor_msgs::PointCloud2>("/org_frame_cloud1", 100);
        ROS_INFO("\033[1;32m----> FrameFeature Started.\033[0m");
        ros::spin();
        return 0;
    }

    代码地址为(GitHub - haocaichao/S-LOAM: S-LOAM(Simple LOAM) 是一种简单易学的激光SLAM算法,整个程序只有几百行代码,十分方便学习与试验分析。)。

    展开全文
  • 三维点云特征提取

    2021-08-13 20:43:50
    标题 三维点云特征提取 点云的曲率和法向量是点云的固有几何特征,它们可以描述点及其邻域的几 何信息。利用曲率和法向量特征建立点云间的对应点集,是点云配准中的常用思 路。 ...

    标题 三维点云特征提取

    点云的曲率和法向量是点云的固有几何特征,它们可以描述点及其邻域的几
    何信息。利用曲率和法向量特征建立点云间的对应点集,是点云配准中的常用思
    路。

    展开全文
  • 基于区域聚类分割的点云特征线提取. 光学学报, 2018, 38(11): 1110001-。Wang Xiaohui, Wu Lushen, Chen Huawei, Hu Yun, Shi Yaying. . Feature Line Extraction from a Point Cloud Based on Region Cluster...
  • 点云特征描述简介

    千次阅读 2019-05-07 08:58:52
    2.点云特征描述分类 2.1基于全局的特征描述 2.2基于局部的特征描述:基于点特征、基于直方图、基于变换 3.局部描述子(local descriptor=shape descriptors=geometric features=point feature representations)...
  • PCL中3D点云特征描述与提取(三)

    千次阅读 2021-10-12 15:36:31
    PCL中3D点云特征描述与提取(三)1 如何从一个深度图像中提取NARF特征 1 如何从一个深度图像中提取NARF特征   本小节将演示如何从一个深度图像中提取NARF特征点位置的NARF特征描述子。可执行程序能够加载一个点云...
  • 针对该问题提出一种有效的目标识别方法,滤除地平面点云特征,并采用分割聚类和支持向量积融合方法识别运动目标。实验表明,该方法在满足实时性前提下,相对于传统人体骨骼、二维梯度直方图算法具有更高的准确率,更...
  • 基于PCL库的点云特征识别

    千次阅读 2019-08-12 14:37:47
    根据特征所在的区域不同可将点云特征分为两类:1、点云边缘特征;2、点云内部尖锐特征。 1、点云边缘特征 点云边缘特征是重要的配准特征。现有的识别算法有增殖思想膨胀出边缘[4]、重心偏移阈值法[5]、多判别...
  • 法向量是点云的一个重要特征,我们可以应用点云法向量到平面分割、模板匹配目标检测等领域中。下面介绍如何在PCL中计算法向量。 3.数据 本例中使用的点云数据(test.pcd)请见百度网盘分享。 链接:...
  • PCL代码经典赏析七:PCL 点云特征描述与提取 但是我需要的方法是其中的两个 PCL 法线估计实例 ------ 估计某一点的表面法线 PCL 使用积分图像进行法线估计 但是无法成功编译 法线估计实例又参照了这篇文章 但是在...
  • list, dim=1) masked_features = features * mask pillar_feature = self.pfn_layers[0](masked_features) return pillar_feature 将非空Pillar内的每个点的维度扩展到9维,然后就是学习点云特征。具体说来就是用一...
  • 在本论文中,作者提出了FCGF(Fully-Convolutional Geometric Features),它通过全卷积网络一次计算得到点云特征;作者还提出了新的度量学习损失,可以显著提升性能。FCGF是紧凑的,同时捕获了广泛的空间上下文,并可...
  • 已知点云P中有n个点,那么它的点特征直方图(PFH)的理论计算复杂度是O(nk^2), 其中k是点云P中每个点p计算特征向量时考虑的邻域数量。对于实时应用或接近实时应用中,密集点云的点特征直方图(PFH)的计算,是一个...
  • 本例中使用的点云数据(test.pcd)请见百度网盘分享。 链接:https://pan.baidu.com/s/1FKP2CnyhOrMgv1AS_7lr8Q 提取码:l7k7 4.代码 #include <pcl/features/normal_3d.h> #include <pcl/io/pcd_io.h> ...
  • PCL(46)点云特征

    2021-03-21 10:08:38
    /**************************LIBRARIES**************************/ // Include the ROS library #include <ros/ros.h> // Include PointCloud2 message #include <sensor_msgs/PointCloud2.h>...
  • PCL(48)点云特征2

    2021-03-21 10:10:00
    #include <pcl/features/normal_3d.h> #include <pcl/features/pfh.h> #include<pcl/visualization/histogram_visualizer.h> void PFH(const pcl::PointCloud<pcl::PointXYZ>...
  • 点云关键点提取(1)

    2019-04-10 14:44:15
    pcl中实现对点云的关键点提取,具体包括iss和sift关键点。
  • velodyne16点云特征分类

    2021-11-12 10:14:13
    将velodyne16点云特征分为平面和角点,参考lio-sam //************************************************************************************************* // 点云特征分类 // source ~/catkin_ws/devel/setup....
  • 三维点云数据特征检测

    千次阅读 2019-06-29 07:14:25
    综述 扫描数据中存在大量冗余数据(不同角度多次拍摄),为后续曲面重建、模型编辑等带来很大不便,因此... 基于点云模型的特征检测 基于多边形网格模型的特征检测 文献74 使用局部多项式拟合来估计曲率张量信息...
  • PCL中3D点云特征描述与提取(二)

    千次阅读 2021-10-12 00:21:14
    PCL中3D点云特征描述与提取(二)1 点特征直方图描述子1.1 理论基础 1 点特征直方图描述子   正如点特征表示法所示,表面法线和曲率估计是某个点周围的几何特征基本表示法。虽然计算非常快速容易,但是无法获得太...
  • 2. 两帧点云重合的部分较少=》扫到了不同的部分,比如innvosuion的雷达 Harris特征 物理意义上理解harris特征: 我们关注每个点在x,y方向intensity变化的速率。 如果在两个方向变化都快,这就是角点。 ...
  • # 用来基于iss方法提取得到点云特征点 import numpy as np from scipy.spatial import KDTree import open3d as o3d # 功能:读取txt文件 # 输入: # path: txt文件路径 # delimiter: txt文件的数据分隔符 # ...
  • 通过对点云进行ESF全局特征提取,训练神经网络分类器,对点云进行分类。ESF特征提取见上述链接。 基于tensorflow2 python代码如下: import tensorflow as tf import numpy as np dataset =tf.data.Dataset a3 = np...
  • MATLAB读取显示txt格式点云(带数据)
  • 三维点云数据的预处理与圆特征提取方法的研究,特征包括边界,角点,圆等等。
  • 3D,点云关键点和特征描述

    千次阅读 2021-11-19 10:39:14
    3D点云特征描述与提取是点云信息处理中最基础也是最关键的一部分。如果要对一个三维点云进行描述,光有点云的位置是不够的,常常需要计算一些额外的参数,比如法线方向、曲率、纹理特征、颜色、领域中心距、协方差...
  • 三维信息的表征形式 常见三维表征 点云 参数化曲线曲面 隐式曲线曲面 多边形面元 点云的基本特征和描述
  • [工作记录] 点云线特征提取

    千次阅读 2018-12-24 10:47:00
    目前的点云线特征提取方法可以分为: 1.基于面片patch的线特征提取,主要可以提取交线,边缘线。这类方法首先都是要提取面,然后对每个面对象提取。又可以分为:  基于图像的提取,首先都是要提取面,然后转换为...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,764
精华内容 6,305
关键字:

点云特征