前言

什么是关键点?
关键点定义: 关键点也称为兴趣点,它是2D图像、3D点云或曲面模型上,可以通过定义检测标准来获取的具有稳定性区别性的点集。

关键点的意义?
加快后续识别、追踪等数据的处理速度

具备该意义原因?
关键点的数量相比于原始点云或图像的数据量小很多,它与局部特征描述子结合在一起,组成关键点描述子,常用来形成原始数据的紧凑表示,而不失代表性与描述性。

NARF 关键点

NARF(Normal Aligned Radial Feature)关键点是为了从深度图像中识别物体而提出的,关键点探测的重要一步是减少特征提取时的搜索空间,把重点放在重要的结构上。
对NARF关键点提取过程有以下要求:提取的过程必须将边缘以及物体表面变化信息考虑在内;关键点的位置必须稳定,可以被重复探测,即使换了不同视角;关键点所在的位置必须具有稳定的支持区域,可以计算描述子并进行唯一的法向量估计。

该方法提取步骤如下:
1、遍历每个深度图像点,通过寻找在邻近区域有深度突变的位置进行边缘检测。
2、遍历每个深度图像点,根据邻近区域的表面变化决定一种测度表面变化的系数,以及变化的主方向。
3、根据第二步找到的主方向计算兴趣值,表征该方向与其它方向的不同,以及该处表面的变化情况,即该点有多稳定
4、对兴趣值进行平滑滤波
5、进行无最大值压缩找到最终的关键点,即为NARF关键点

PCL中的pcl_keypoints库目前提供了几种常用的关键点检测算法

Code

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#include <iostream>
#include <boost/thread/thread.hpp>
#include <pcl/range_image/range_image.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/range_image_visualizer.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/features/range_image_border_extractor.h>
#include <pcl/keypoints/narf_keypoint.h>//narf 关键点 提取 模块
#include <pcl/console/parse.h>

要包含的头文件 narf关键点提取 主要是 narf_keypoint.h

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /*声明点云*/
    pcl::PointCloud<pcl::PointXYZ>::Ptr point_cloud_ptr(new pcl::PointCloud<pcl::PointXYZ>);//用于提取关键点的点云
    pcl::PointCloud<pcl::PointXYZ>& point_cloud= *point_cloud_ptr;//指向地址的引用

声明一个点云的指针和其引用
用于存放待提取关键点的 点云

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    //深度相机位姿
    Eigen::Affine3f scene_sensor_pose(Eigen::Affine3f::Identity());

    /*读取指定的pcd文件*/
    if(read_pcd)
    {
        //pcd文件路径
        std::string filename= "/home/jone/slam_learn/pcl/narf_keypoint_extraction/frame_00000.pcd";
        if(pcl::io::loadPCDFile(filename,point_cloud)==-1)
        {
            //文件没有打开
            std::cout << "Was not able to open file:" <<filename<<std::endl ;
            return 0;
        }

        // 指定传感器的姿态  PointCloud 类的成员有 sensor_origin_  sensor_orientation_ 指定传感器采集姿态 但是用的很少
        scene_sensor_pose=Eigen::Affine3f(Eigen::Translation3f(point_cloud.sensor_origin_[0],
        point_cloud.sensor_origin_[1],
        point_cloud.sensor_origin_[2]))*
        Eigen::Affine3f(point_cloud.sensor_orientation_);
    }

从pcd文件中读取点云
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /* 自己创造点云 */
    if(!read_pcd)
    {
        setUnseenToMaxRange=true;
        for(float x=-0.5f;x<=0.5f;x+=0.01f)
        {
            for(float y=-0.5f;y<=0.5f;y+=0.01f)
            {
                pcl::PointXYZ point;
                point.x=x;point.y=y;point.z=2.0f-y;
                point_cloud.points.push_back(point);
            }
        }
        point_cloud.width=(int)point_cloud.points.size();point_cloud.height=1;
    }

自己创造点云

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /****根据点云创造深度图像****/

    /*设置深度图像生成的参数*/
    //设置看不见的点 为 最大距离
    setUnseenToMaxRange=true;
    //设置深度图像的坐标系
    int tmp_coordinate_frame = 0;
    coordinate_frame=pcl::RangeImage::CoordinateFrame(tmp_coordinate_frame);
    //设置角度分辨率  弧度 相邻像素点 所对应的每束 光束之间相差 0.5 度
    float angular_resolution=0.5f;
    angular_resolution=pcl::deg2rad(angular_resolution); 
    //传感器噪声
    float noise_level=0.0;
    //最小距离
    float min_range=0.0f;
    //在剪裁图像时若果 此值>0,将在图像周围留下当前视点不可见的边界
    int border_size=1;

    boost::shared_ptr<pcl::RangeImage>range_image_ptr(new pcl::RangeImage);
    pcl::RangeImage & range_image = *range_image_ptr;
    //根据点云生成深度图像
    range_image.createFromPointCloud(point_cloud,angular_resolution,pcl::deg2rad(360.0f),pcl::deg2rad(180.0f),
                                    scene_sensor_pose,coordinate_frame,noise_level,min_range,border_size);

    if(setUnseenToMaxRange)
    {
        range_image.setUnseenToMaxRange();
    }

根据点云创造深度图像
上一篇已经详细说了这块就不再细说
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    // 创建3D点云可视化窗口,并显示点云
    pcl::visualization::PCLVisualizer viewer("3D Viewer");
    viewer.setBackgroundColor(1,1,1);
    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange>range_image_color_handler(range_image_ptr,0,0,0);
    viewer.addPointCloud(range_image_ptr,range_image_color_handler,"range image");
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,1,"range image");
    viewer.addCoordinateSystem (1.0f);
    PointCloudColorHandlerCustom<PointType>point_cloud_color_handler (point_cloud_ptr, 150, 150, 150);
    viewer.addPointCloud (point_cloud_ptr, point_cloud_color_handler, "original point cloud");
    viewer.initCameraParameters();

    // 显示距离图像
    pcl::visualization::RangeImageVisualizer range_image_widget("Range image");
    range_image_widget.showRangeImage(range_image);

显示图像点云和原始点云 显示距离图像

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /*提取NARF关键点*/

    // NARF的参数   设置所支持的范围(搜索空间球体的半径,它指定计算感兴趣值的测度时所使用的邻域范围)
    float support_size=0.2f;

    pcl::RangeImageBorderExtractor range_image_border_extractor;
    pcl::NarfKeypoint narf_keypoint_detector(&range_image_border_extractor);
    narf_keypoint_detector.setRangeImage(&range_image);//设置输入的深度图像。
    // getParameters()  获取 Parameters 结构体对象引用,其中存储了与 Narf 关键点提取算法的很多参数

    narf_keypoint_detector.getParameters().support_size=support_size;//support_size  检测关键点的支持区域。
    narf_keypoint_detector.getParameters ().add_points_on_straight_edges = true; //如果该值设置为 true ,则在空间直的边缘处添加关键点,表示空间曲面变化大。
    narf_keypoint_detector.getParameters ().distance_for_additional_points = 0.5;//所有与最大兴趣值点之间距离在 distance_for_additional_points 之内的点,只要感兴趣值大于 min_interest_value ,该点则成为关键点候选点。

    //存放 深度图像点云的 该点的索引值
    pcl::PointCloud<int> keypoint_indices;
    //计算关键点  返回 深度图像点云的 该点的索引值
    narf_keypoint_detector.compute(keypoint_indices);
    //打印有多少个 关键点
    std::cout<<"Found "<<keypoint_indices.points.size()<<" key points.\n";

本片重点部分
提取NARF关键点
设置所支持的范围(搜索空间球体的半径,它指定计算感兴趣值的测度时所使用的邻域范围)
声明narf关键点提取实例
设置输入的深度图像
设置narf的相关参数
此处仅设置了三个 。具体含义写在注释里了 还有很多个如下:

float support_size 检测关键点的支持区域
int max_no_of_interest_points 返回关键点数目的上限
float min_distance_between_interest_points 关键点之间的最小距离,影响支持区域
float optimal_distance_to_high_surface_change 关键点与表面特征变化大的位置之间的距离
float min interest value 候选关键点感兴趣值下限
float min_s.urface_change_score 候选关键点表面特征变化大小下限
int optimal_range_image_patch_size 计算每个关键点时考虑的距离图像大小(像素〉
float distance_for_additional_points 所有与最大兴趣值点之间距离在 distance_for_additional_points 之内的点,只要感兴趣值大于 min_interest_value ,该点则成为关键点候选点
bool add_points_on_straight_edges 如果该值设置为 true ,则在空间直的边缘处添加关键点,表示空间曲面变化大
bool do_non_max.imum_suppression 如果该值设置为 false,只要大于 min_interest_ value 兴趣值的点都会被加入到关键点队列
bool no_of_polynomial_approximations_per_point 如果该值设置为 true ,则对于每个关键点的位置,需要采用双变量多项式插值获取,这样获取的位置更精确
int max no of threads 采用 OpenMP 机制时,设置的建立线程数的上限
bool use recursive scale reduction 如果设置为真,距离图像在小范围内有多个关键点,则调整小分辨率,加快计算速度
bool calculate_sparse_interest_image 使用启发式机制,自适应调整那些距离图像区域不需要计算关键点,从而加快计算速度

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    //在3D窗口中显示关键点
    pcl::PointCloud<pcl::PointXYZ>::Ptr keypoints_ptr(new pcl::PointCloud<pcl::PointXYZ>);//存放关键点指针
    pcl::PointCloud<pcl::PointXYZ>& keypoints = *keypoints_ptr;//存放关键点指针 的引用
    keypoints.points.resize(keypoint_indices.points.size());//设置点云数量
    for(size_t i=0;i<keypoint_indices.points.size();++i)//循环 将深度图像的对应关键点 提取的  关键点云 里面
    keypoints.points[i].getVector3fMap()=range_image.points[keypoint_indices.points[i]].getVector3fMap();

    //显示关键点
    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>keypoints_color_handler(keypoints_ptr,0,255,0);
    viewer.addPointCloud<pcl::PointXYZ>(keypoints_ptr,keypoints_color_handler,"keypoints");
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,7,"keypoints");

将计算的关键点 提取到 声明的关键点的 点云 中,然后显示
计算关键点返回的是 深度图像点云中 的 该点的索引值

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    // 主循环
    while(!viewer.wasStopped())
    {
        range_image_widget.spinOnce();// process GUI events
        viewer.spinOnce();
        pcl_sleep(0.01); 
    }

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

CMakeList.txt

# 声明要求的 cmake 最低版本
cmake_minimum_required(VERSION 2.8 )
# 声明工程名称
project(narf_keypoint_extraction)
# 添加c++ 11 标准支持
set(CMAKE_CXX_FLAGS "-std=c++11")
# 寻找PCL的库
find_package(PCL REQUIRED COMPONENT common io visualization filters features keypoints)
link_directories(${PCL_LIBRARY_DIRS})
# 添加头文件
include_directories(${PCL_INCLUDE_DIRS})
add_definitions( ${PCL_DEFINITIONS} )
#添加一个可执行程序
add_executable(narf_keypoint_extraction narf_keypoint_extraction.cpp)
#链接PCL 库
target_link_libraries(narf_keypoint_extraction ${PCL_LIBRARIES})

注意: find_package() 中加入 keypoints

Result

add_points_on_straight_edges 为true 即 在空间直的边缘处添加关键点,表示空间曲面变化大。 时
读取点云的检测结果

自创简单点云的检测结果

add_points_on_straight_edges 为false 时
读取点云的检测结果

自创简单点云的检测结果