前言
什么是关键点?
关键点定义: 关键点也称为兴趣点,它是2D图像、3D点云或曲面模型上,可以通过定义检测标准来获取的具有稳定性、区别性的点集。
关键点的意义?
加快后续识别、追踪等数据的处理速度
具备该意义原因?
关键点的数量相比于原始点云或图像的数据量小很多,它与局部特征描述子结合在一起,组成关键点描述子,常用来形成原始数据的紧凑表示,而不失代表性与描述性。
NARF 关键点
NARF(Normal Aligned Radial Feature)关键点是为了从深度图像中识别物体而提出的,关键点探测的重要一步是减少特征提取时的搜索空间,把重点放在重要的结构上。
对NARF关键点提取过程有以下要求:提取的过程必须将边缘以及物体表面变化信息考虑在内;关键点的位置必须稳定,可以被重复探测,即使换了不同视角;关键点所在的位置必须具有稳定的支持区域,可以计算描述子并进行唯一的法向量估计。
该方法提取步骤如下:
1、遍历每个深度图像点,通过寻找在邻近区域有深度突变的位置进行边缘检测。
2、遍历每个深度图像点,根据邻近区域的表面变化决定一种测度表面变化的系数,以及变化的主方向。
3、根据第二步找到的主方向计算兴趣值,表征该方向与其它方向的不同,以及该处表面的变化情况,即该点有多稳定
4、对兴趣值进行平滑滤波
5、进行无最大值压缩找到最终的关键点,即为NARF关键点
Harris关键点
Harris 关键点检测算法通过计算图像点的Harris矩阵和矩阵对应的特征值来判断是否为关键点。
如果Harris矩阵特征的两个特征值都很大,则该点是关键点
注意:Harris关键点检测子对图像旋转变换保持较好的检测重复率,但不适合尺度变化的检测。
点云中的3D Harris 关键点 检测使用的是 点云表面法向量的信息。
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 时
读取点云的检测结果
自创简单点云的检测结果