# PCL特征描述与提取Features

特征描述与提取Features

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

特征点

理想情况下,在使用同一种度量规则情况下,相同或相似的表面上的点的特征子应该非常相似,不同表面上的点的特征描述子有明显的差异。通过以下几个条件的变化仍能够获取获取相同或相似的局部表面特征,则说明该特征表示方式比较优秀:

  • 刚性变换(rigid transformations):即数据中的3D旋转和3D平移不应影响结果特征向量F的估计(抗变换性)
  • 多种采样密度(varying sampling density):原则上,在一个局部表面或多或少采样密度的应具有相同的特征向量(抗密度性)
  • 噪声(noise):在数据中存在轻微噪声的情况下,由特征点描述的特征向量必须相同或非常相近(抗噪声性)在这里插入图片描述 通常,PCL功能使用近似的方法通过快速的kd-tree查询来计算查询点的最近邻居,我们感兴趣的查询有两种:
  • k搜索:确定查询点的k个(用户给定参数)邻居
  • 半径搜索:确定半径为r的球面内查询点的所有邻居。

如何传递输入参数

由于PCL中几乎所有类都继承自基本pcl::PCLBase类,因此pcl::Feature类以两种不同的方式接受输入数据:

  1. 通过setInputCloud(PointCloudConstPtr&)给出的整个点云数据集, 任何试图在给定输入云中的每个点上进行特征估计。
  2. 通过setInputCloud(PointCloudConstPtr&)和setIndices(IndicesConstPtr&)(可选)给出的点云数据集的子集, 所有特征估计方式都将用来尝试估计输入云中每个在indices索引列表中点的特征。默认情况下,如果未给出索引集,则将考虑云中的所有点。
  • 另外,可以通过额外的调用setSearchSurface(PointCloudConstPtr&)来指定要搜索使用的点邻居集。此调用是可选的,如果未提供搜索范围的情况下,默认将使用输入点云数据集。 由于始终需要setInputCloud(),因此可以使用<setInputCloud(),setIndices(),setSearchSurface()>创建多达四个组合。假设我们有两个点云,P={p1,p2,…pn}和Q={q1,q2,…,qn}。下图显示了所有四种情况:
    • 下面四种情况对应于四种不同的组合状态

在这里插入图片描述


  • 情况1:setIndices() = false, setSearchSurface() = false
    毫无疑问,这是PCL中最常用的情况,用户仅输入单个PointCloud数据集,并期望在云中的所有点处估计某个特征。
    由于我们不希望根据是否给出一组indices(索引)和/或search surface(搜索表面)来维护不同的实现副本,因此每当indices = false时,PCL就会创建一组内部索引(作为std::vector )基本上指向整个数据集(索引= 1…N,其中N是云中的点数)。
    在上图中,这对应于最左边的情况。首先,我们估计p_1的最近邻居,然后估计p_2的最近邻居,依此类推,直到用尽P中的所有点。(这种情况基本上算是自己搞定自己,考虑自己的邻居和特征等)

  • 情况2:setIndices() = true, setSearchSurface() = false
    如前所述,特征估计方法将仅计算在给定indices(索引) 的vecter中具有索引的那些点的特征;
    在上图中,这对应于第二种情况。在此,我们假设p_2的索引不是给定索引vector的一部分,因此不会在p2处估计任何邻居或特征。(只考虑索引容器中的目标对象,还是自己搞自己,只是搞得对象少了点)

  • 情况3:setIndices() = false, setSearchSurface() = true
    与第一种情况一样,将对输入的所有点进行特征估计,但是setSearchSurface()中给定的基础相邻表面将用于获取输入的最近邻居点,而不是输入云本身;
    在上图中,这对应于第三种情况。如果Q={q1,q2}是作为输入云,把P设置为Q的search surface搜索面,则将从P计算q_1和q_2的邻居。 (这个就是自己搞别人,但是自己体内的每个细胞,都得把别人搞一边,输入是自己哦!,searchSurface是别人哦,别人是被查询对象)

  • 情况4:setIndices() = true, setSearchSurface() = true
    这是最罕见的情况,同时给出了indices索引和search surface搜索表面。在这种情况下,将使用setSearchSurface中提供的搜索表面信息来计算<input, indices>中的子集的特征。
    最后,在上图中,这对应于最右边的情况。在此,我们假设q_2的索引不属于为Q指定的索引向量,因此在q2处不会估计出任何邻居或特征。(这个也是自己搞别人,只是自己体内的一部分细胞需要把别人搞一编)

自己搞别人的用法

使用setSearchSurface的最有用的情况是:当我们有一个非常密集的输入数据集,但我们不想估计其中所有点的特征,而是要估计使用pcl_keypoints中的方法发现的某些关键点,或者在点云的降采样版本上(例如,使用pcl::VoxelGrid 过滤器获得的降采样点云)。在这种情况下,我们通过setInputCloud传递降采样/关键点输入,并将原始数据作为setSearchSurface传递,这样可以大幅提高效率。(说穿了,把自己内部重要的点,给拿出来,再来搞原来的自己)

法向量估算示例

法向量估算实现(1)

查询点的相邻点可以用于估计局部特征表示,该局部特征表示捕获查询点周围的基础采样表面的几何形状。描述表面几何形状的一个重要问题是首先推断其在坐标系中的方向,即估算其法线。表面法线是表面的重要属性,在许多领域(例如计算机图形应用程序)中大量使用,以应用正确的光源来生成阴影和其他视觉效果(有关更多信息,请参见[RusuDissertation])。 以下代码片段将为输入数据集中的所有点估计一组表面法线。

  • 实现代码 n_show_get_1.cpp
  • getN_1

法向量估算实现(2)

以下代码片段将会从一个点云的子集中进行表面法线估算

  • getN_2

法向量估算实现(3)

最后,以下代码片段将为输入数据集中的所有点估算一组表面法线,但将使用另一个数据集(原始点云)估计其最近的邻居。如前所述,一个很好的方式是降采样版本作为输入input cloud。

  • getN_3
#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>

int getN_1(){
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    
    if(pcl::io::loadPCDFile<pcl::PointXYZ>("bunny.pcd",*cloud)==-1) //* load the file
    {
        PCL_ERROR("Couldn`t read file bunny.pcd\n");
        return(-1);
    }

    // 创建法向量估算类,传递输入数据集
    pcl::NormalEstimation<pcl::PointXYZ,pcl::Normal> ne;
    ne.setInputCloud(cloud);

    // 创建一个空的kdtree,将值传递给法向量估算对象
    // 这个tree对象将会在ne内部根据输入的数据集进行填充(这里设置没有其他的search surface)
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
    ne.setSearchMethod(tree);

    //定义输出数据集
    pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>);

    //使用-个半径为3cm的球体中的所有邻居点
    ne.setRadiusSearch(0.03);

    //计算特征
    ne.compute(*cloud_normals);
    //cloud_normals->points.size () 输出特征的点个数应当定于输入的点个数 cloud->points.size ()

    //创建PCLVisualizer
    boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D viewer"));

    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> single_color(cloud,0,255,0);
    viewer->addPointCloud<pcl::PointXYZ>(cloud,single_color,"sample cloud");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,2,"sample cloud");

    //创建PCLVisualizer
    pcl::visualization::PointCloudColorHandlerCustom<pcl::Normal> rgb_color(cloud_normals,0,255,0);
    viewer->addPointCloudNormals<pcl::PointXYZ,pcl::Normal>(cloud,cloud_normals,10,0.01,"features cloud");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,2,"features cloud");

    //coordinate
    viewer->addCoordinateSystem(0.5);

    while(!viewer->wasStopped()){
        //每次循环调用内部的重绘函数
        viewer->spinOnce();
    }
    return 0;


    //以下内容用于可视化法向量
    //int v1(0),v2(0);
    //viewer->createViewPort(0.5,0.0,1.0,0.5,v2);

}

int getN_2(){
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    if(pcl::io::loadPCDFile<pcl::PointXYZ>("bunny.pcd",*cloud)==-1) //* load the file
    {
        PCL_ERROR("Couldn`t read file bunny.pcd\n");
        return -1;
    }
    // 准备一个indices索引集合,为了简单起见,我们直接使用点云的前10%的点
    std::vector<int> indices(std::floor(cloud->points.size()/10));
    for (std::size_t i=0;i<indices.size();++i) indices[i]=i;

    //创建法向量估算类,设置输入点云
    pcl::NormalEstimation<pcl::PointXYZ,pcl::Normal> ne;
    ne.setInputCloud(cloud);

    //设置indices索引
    std::shared_ptr<std::vector<int>> indicesptr(new std::vector<int>(indices));
    ne.setIndices(indicesptr);

    // 创建一个空的kdtree,将值传递给法向量估算对象
    // 这个tree对象将会在ne内部根据输入的数据集进行填充(这里设置没有其他的search surface)
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
    ne.setSearchMethod(tree);
    //自己对于KDtree的部分理解,如果只有自己,那么把自己变成KDtree,然后自己查询自己
    //如果设置了SearchSurface,那么把别人变成KDtree,然后自己在别人那里找邻近点

    //定义输出数据集
    pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>);

    //使用一个半径为3cm的球体中的所有邻近点
    ne.setRadiusSearch(0.03);

    //计算法向量特征
    ne.compute(*cloud_normals);
    // cloud_normals->points.size () 输出特征的点个数应当定于输入的点个数 cloud->points.size ()

    //创建PCLVisualizer
    boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D viewer"));

    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> single_color(cloud,0,255,0);
    viewer->addPointCloud<pcl::PointXYZ>(cloud,single_color,"sample cloud");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,2,"sample cloud");

    //创建PCLVisualizer
    pcl::visualization::PointCloudColorHandlerCustom<pcl::Normal> rgb_color(cloud_normals,0,255,0);
    viewer->addPointCloudNormals<pcl::PointXYZ,pcl::Normal>(cloud,cloud_normals,50,.01,"features cloud");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,2,"features cloud");

    //coordinate
    viewer->addCoordinateSystem(0.5);

    while(!viewer->wasStopped()){
        //每次循环调用内部的重绘函数
        viewer->spinOnce();
    }
    return 0;

}

void getN_3()
{
  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 ...
/
  // 创建法向量估算类,将降采样后的数据作为输入点云
  pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
  ne.setInputCloud (cloud_downsampled);

  // 传入降采样之前的原始数据作为search surface
  ne.setSearchSurface (cloud);

  // 创建一个空的kdtree,将值传递给法向量估算对象
  // 这个tree对象将会在ne内部根据输入的数据集进行填充(这里设置没有其他的search surface)
  pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());
  ne.setSearchMethod (tree);

  // 定义输出数据集
  pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);

  // 使用一个半径为3cm的球体中的所有邻居点
  ne.setRadiusSearch (0.03);

  // 计算特征
  ne.compute (*cloud_normals);

  // cloud_normals->points.size()输出特征的点个数应当定于输入的点个数cloud->points.size()


int main(){
    //getN_1();
    getN_2();
    getN_3()
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用PCL库进行NARF关键点提取的示例代码: ```c++ #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/features/narf_descriptor.h> int main(int argc, char** argv) { if(argc != 2) { std::cerr << "Usage: " << argv[0] << " cloud.pcd" << std::endl; return -1; } pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); pcl::io::loadPCDFile(argv[1], *cloud); pcl::PointCloud<pcl::Narf36>::Ptr narfs(new pcl::PointCloud<pcl::Narf36>); pcl::RangeImage range_image; pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME; float angular_resolution = 0.5f; float support_size = 0.2f; Eigen::Affine3f scene_sensor_pose(Eigen::Affine3f::Identity()); range_image.createFromPointCloud(*cloud, angular_resolution, pcl::deg2rad(360.0f), pcl::deg2rad(180.0f), scene_sensor_pose, coordinate_frame, support_size, support_size/2.0f); pcl::PointCloud<pcl::PointXYZ>::Ptr keypoints(new pcl::PointCloud<pcl::PointXYZ>); pcl::NarfKeypoint narf_keypoint_detector; narf_keypoint_detector.setRangeImage(&range_image); narf_keypoint_detector.getParameters().support_size = support_size; narf_keypoint_detector.compute(*keypoints); pcl::NarfDescriptor narf_descriptor(&range_image, &narf_keypoint_detector); narf_descriptor.getParameters().support_size = support_size; narf_descriptor.getParameters().rotation_invariant = true; narf_descriptor.compute(*narfs); boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("NARF Keypoints")); viewer->setBackgroundColor(0, 0, 0); viewer->addPointCloud<pcl::PointXYZ>(cloud, "cloud"); viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud"); viewer->addPointCloud<pcl::PointXYZ>(keypoints, "keypoints"); viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 5, "keypoints"); viewer->spin(); return 0; } ``` 该代码加载点云数据后,使用 `pcl::RangeImage` 对其进行预处理,然后使用 `pcl::NarfKeypoint` 进行关键点提取,最终使用 `pcl::NarfDescriptor` 计算NARF描述符。提取出的关键点和原始点云数据一起显示在可视化窗口中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值