点云库pcl从入门到精通学习记录 第八章 NARF关键点

读书时候的一些碎碎念,仅做个人读书记录用

慎重阅读!!!

NARF关键点

提取关键点的目的是从深度图像中识别物体,所以要求考虑物体的边缘和物体表面的变化信息;

为什么关键点必须稳定可以被重复检测呢?这是因为viewer point是可以变化的
变化的视角可能会带来同一个物体不同的形状,所以必须在每个视点都能检测到这个物体才行、

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

在这里插入图片描述着实有点难以理解这个NARF关键点的位置,感性上觉得有点离谱

代码

修改好的代码,博主实测可以跑通

源码

/* \author Bastian Steder */
//整个注释参考博客
// https://blog.csdn.net/suyunzzz/article/details/99314844
#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>
#include <pcl/console/parse.h>

typedef pcl::PointXYZ PointType;
//参数
float angular_resolution=0.5f;
float support_size=0.2f;
pcl::RangeImage::CoordinateFrame coordinate_frame=pcl::RangeImage::CAMERA_FRAME;
bool setUnseenToMaxRange=false;
//打印帮助
void
printUsage(const char*progName)
{
std::cout<<"\n\nUsage: "<<progName<<" [options] <scene.pcd>\n\n"
<<"Options:\n"
<<"-------------------------------------------\n"
<<"-r <float>   angular resolution in degrees (default "<<angular_resolution<<")\n"
<<"-c <int>     coordinate frame (default "<<(int)coordinate_frame<<")\n"
<<"-m           Treat all unseen points as maximum range readings\n"
<<"-s <float>   support size for the interest points (diameter of the used sphere - "
<<"default "<<support_size<<")\n"
<<"-h           this help\n"
<<"\n\n";
}

void
setViewerPose(pcl::visualization::PCLVisualizer &viewer,const Eigen::Affine3f &viewer_pose)
{
Eigen::Vector3f pos_vector=viewer_pose*Eigen::Vector3f(0,0,0);
Eigen::Vector3f look_at_vector=viewer_pose.rotation()*Eigen::Vector3f(0,0,1)+pos_vector;
Eigen::Vector3f up_vector=viewer_pose.rotation()*Eigen::Vector3f(0,-1,0);
// https://blog.csdn.net/sljslj111/article/details/120565893
//由于camera_在pcl1.8中已经年没有了这个函数,修改源代码,参照上面网址博客修改
// viewer.camera_.pos[0]=pos_vector[0];
// viewer.camera_.pos[1]=pos_vector[1];
// viewer.camera_.pos[2]=pos_vector[2];
// viewer.camera_.focal[0]=look_at_vector[0];
// viewer.camera_.focal[1]=look_at_vector[1];
// viewer.camera_.focal[2]=look_at_vector[2];
// viewer.camera_.view[0]=up_vector[0];
// viewer.camera_.view[1]=up_vector[1];
// viewer.camera_.view[2]=up_vector[2];
// viewer.updateCamera();


viewer.setCameraPosition(pos_vector[0], pos_vector[1], pos_vector[2], look_at_vector[0],   
look_at_vector[1], look_at_vector[2], up_vector[0], up_vector[1], up_vector[2]);

}

// -----Main-----
int
main(int argc,char **argv)
{
//解析命令行参数
if(pcl::console::find_argument(argc,argv,"-h")>=0)
{
printUsage(argv[0]);
return 0;
}
if(pcl::console::find_argument(argc,argv,"-m")>=0)
{
setUnseenToMaxRange=true;
cout<<"Setting unseen values in range image to maximum range readings.\n";
}
int tmp_coordinate_frame;
if(pcl::console::parse(argc,argv,"-c",tmp_coordinate_frame)>=0)
{
coordinate_frame=pcl::RangeImage::CoordinateFrame(tmp_coordinate_frame);
cout<<"Using coordinate frame "<<(int)coordinate_frame<<".\n";
}
if(pcl::console::parse(argc,argv,"-s",support_size)>=0)
cout<<"Setting support size to "<<support_size<<".\n";
if(pcl::console::parse(argc,argv,"-r",angular_resolution)>=0)
cout<<"Setting angular resolution to "<<angular_resolution<<"deg.\n";
angular_resolution=pcl::deg2rad(angular_resolution);
//读取给定的pcd文件或者自行创建随机点云,该点云集合为point_cloud
pcl::PointCloud<PointType>::Ptr point_cloud_ptr(new pcl::PointCloud<PointType>);
pcl::PointCloud<PointType>&point_cloud=*point_cloud_ptr;
//定义了far_ranges,这是一个包含了点的坐标和相对点的坐标的点云类型
//查看定义可知,vp_x,vp_y,vp_z是点云在viewpoint下的坐标,x,y,z为点云的坐标
pcl::PointCloud<pcl::PointWithViewpoint>far_ranges;
//传感器位姿,scene_sensor_pose
//Eigen::Affine3f,仿射变换矩阵,包含了传感器的平移矩阵和旋转矩阵在内的描述位姿的矩阵
//参考博客
//https://blog.csdn.net/weixin_42503785/article/details/112552689
Eigen::Affine3f scene_sensor_pose(Eigen::Affine3f::Identity());
//读取并判断,传入的参数中是否含有pcd
std::vector<int>pcd_filename_indices=pcl::console::parse_file_extension_argument(argc,argv,"pcd");
//pcd_filename_indices.empty(),如果是空,就返回1,非空,返回0
if(!pcd_filename_indices.empty())
{
    //传入filename
std::string filename=argv[pcd_filename_indices[0]];
//此时filename为frame_00000.pcd
//载入名称为filename的pcd文件到point_cloud中
if(pcl::io::loadPCDFile(filename,point_cloud)==-1)
{
    //如果在载入失败,返回-1,进入这个if,输出打不开这个文件
cerr<<"Was not able to open file \""<<filename<<"\".\n";
printUsage(argv[0]);
return 0;
}
//传感器位姿的计算
//乘号前面是传感器的位姿变换
//乘号后面是传感器的初始位姿
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_);
//_far_ranges如果去掉的话,会报错
/*
Failed to find match for field 'vp_x'.
Failed to find match for field 'vp_y'.
Failed to find match for field 'vp_z'.
*/
// std::string far_ranges_filename = pcl::getFilenameWithoutExtension(filename)+".pcd";

//这行代码很奇怪,如果不加_far_ranges.pcd,那么后面将会报缺少vp_x,vp_y,vp_z的错误
//如果加上"_far_ranges.pcd",那么又会在终端中报出filename + "_far_ranges.pcd"文件不存在的错误
//getFilenameWithoutExtension 获取不带扩展名的文件名称
std::string far_ranges_filename = pcl::getFilenameWithoutExtension(filename)+"_far_ranges.pcd";
//https://blog.csdn.net/EchoChou428/article/details/106806826/
//如果loadPCDFile不加<xxx.xxx>的模板,那么就是默认以二进制方式读取该pcd文件
// if(pcl::io::loadPCDFile("frame_00000.pcd",far_ranges)==-1)
//如果修改成以上形式,那么这个代码会报错,如问题2,代码跑不通了
//但是如果保持下面的形式,那么就会出现读取不到文件的错误,但是代码是可以正确的跑通的
//下面的这个if代码只是显示有没有存在??可是给原本存在的filename修改名称为
//far_ranges_filename了,当然是不存在的
//所以下面这个一行的if代码是没用的。。。。。
// if(pcl::io::loadPCDFile(far_ranges_filename.c_str(),far_ranges)==-1)
// {
// std::cout<<"Far ranges file \""<<far_ranges_filename<<"\"  is not exists.\n";
// }
}
//注意这个else对应的是 if(!pcd_filename_indices.empty())
//如果说获取的pcd文件,pcd_filename_indices为空的话 ,那么便生成新的点云
else
{
    //setUnseenToMaxRange是一个标志变量,后面的程序能够判断前面的点云是自行生成的还是文件给的
    //将“不可见”设置为“最大范围”?
setUnseenToMaxRange=true;
cout<<"\nNo *.pcd file given =>Genarating example point cloud.\n\n";
//用for循环来生成点云
for(float x=-0.5f;x<=0.5f;x+=0.01f)
{
for(float y=-0.5f;y<=0.5f;y+=0.01f)
{
PointType 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;
}
//从点云创建深度图像(距离图像)
//本书第七章节详细介绍了如何从点云创建深度图像
float noise_level=0.0;
float min_range=0.0f;
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);
range_image.integrateFarRanges(far_ranges);
//setUnseenToMaxRange进行判断
if(setUnseenToMaxRange)
range_image.setUnseenToMaxRange();
// 创建3D点云可视化窗口,并显示点云
//viewer 观测者
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");
//设置点的size
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();
//设置观测者viewer的观测角度,从range_image中获得从传感器自身到世界坐标系的变换
setViewerPose(viewer,range_image.getTransformationToWorldSystem());
// 显示距离图像
pcl::visualization::RangeImageVisualizer range_image_widget("Range image");
range_image_widget.showRangeImage(range_image);

//提取NARF关键点
/*
    提取关键点的目的是从深度图像中识别物体,所以要求考虑物体的边缘和物体表面的变化信息;
    为什么关键点必须稳定可以被重复检测呢?这是因为viewer point是可以变化的
    变化的视角可能会带来同一个物体不同的形状,所以必须在每个视点都能检测到这个物体才行、

    1、遍历所有深度图像点,通过寻找在近邻区有深度突变的位置进行边缘检测

    2、遍历所有深度图像点,根据近邻区域的表面变化决定一种测度变化的系数,以及变化的主方向
    3、根据2找到的主方向计算兴趣值,表征该方向与其他方向不同,以及该处表面的变化情况,也就是该处有多么稳定
    4、对该兴趣值进行平滑过滤
    5、进行无最大值压缩找到最终的关键点,也就是NARF关键点

*/

// 1、遍历所有深度图像点,通过寻找在近邻区有深度突变的位置进行边缘检测

//提取NARF关键点的第一步就是要首先获得边框,所以设置了一个range_image_border_extractor
//range_image_border_extractor 深度图像边框提取器
pcl::RangeImageBorderExtractor range_image_border_extractor;    
//设置narf关键点检测器,传入深度图像边关提取器提取出的数据
pcl::NarfKeypoint narf_keypoint_detector(&range_image_border_extractor);
//把深度图像传入narf关键点检测器
narf_keypoint_detector.setRangeImage(&range_image);
//依照定义,support_size == 0.2,浮点型
//表示的是邻域的大小,搜索空间球体的半径
narf_keypoint_detector.getParameters().support_size=support_size;

//narf_keypoint_detector.getParameters ().add_points_on_straight_edges = true;
//narf_keypoint_detector.getParameters ().distance_for_additional_points = 0.5;

//keypoint_indices 关键点索引,indices是index的复数之一
pcl::PointCloud<int> keypoint_indices;
//求解关键点,把关键点索引放入keypoint_indices对象中
narf_keypoint_detector.compute(keypoint_indices);
//输出找到了几个关键点
std::cout<<"Found "<<keypoint_indices.points.size()<<" key points.\n";


//在距离图像显示组件内显示关键点
//for (size_ti=0; i<keypoint_indices.points.size (); ++i)
//range_image_widget.markPoint (keypoint_indices.points[i]%range_image.width,
//keypoint_indices.points[i]/range_image.width);
//在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);
}
}

Cmake文件代码

cmake_minimum_required(VERSION 3.0)
project(narf_keypoint_extraction)
find_package(PCL 1.3 REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
set(CMAKE_BUILD_TYPE Debug)
add_executable(narf_keypoint_extraction narf_keypoint_extraction.cpp)
target_link_libraries(narf_keypoint_extraction ${PCL_LIBRARIES})

vscode中调试所用的json文件如下:

c_cpp_properties.json

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/include/**"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu11",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

launch文件

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch", // 配置名称,将会在启动配置的下拉菜单中显示
            "type": "cppdbg", // 配置类型,这里只能为cppdbg
            "request": "launch", // 请求配置类型,可以为launch(启动)或attach(附加)
            "program": "${workspaceFolder}/build/narf_keypoint_extraction", // 将要进行调试的程序的路径
            "args": ["frame_00000.pcd"], // 程序调试时传递给程序的命令行参数,一般设为空即可
            "stopAtEntry": false, // 设为true时程序将暂停在程序入口处,我一般设置为true
            "cwd": "${workspaceFolder}", // 调试程序时的工作目录
            "environment": [],
            "externalConsole": true, // 调试时是否显示控制台窗口,一般设置为true显示控制台,
                                      // 但是最新版cpptools有BUG,具体请看文末的注意
           // "internalConsoleOptions": "neverOpen", // 如果不设为neverOpen,调试时会跳到“调试控制台”选项卡,你应该不需要对gdb手动输命令吧?
            "MIMode": "gdb", // 指定连接的调试器,可以为gdb或lldb。但目前lldb在windows下没有预编译好的版本。
            " MIMode": "gdb", // 调试器路径,Windows下后缀不能省略,Linux下则去掉
            "setupCommands": [ // 用处未知,模板如此
                {
                    "description": "为gdb启动整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": false
                }
            ],
            "preLaunchTask": "Build", // 调试会话开始前执行的任务,一般为编译程序。与tasks.json的label相对应

            "miDebuggerPath":"/usr/bin/gdb"

        }

    ]
}

task文件

{   
    "version": "2.0.0",
    "options": {
        "cwd": "${workspaceFolder}/build"
    },
    "tasks": [
        {
            "type": "shell",
            "label": "cmake",
            "command": "cmake",
            "args": [
                ".."
            ]
        },
        {
            "label": "make",
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "command": "make",
            "args": [

            ]
        },
        {
            "label": "Build",
			"dependsOrder": "sequence", // 按列出的顺序执行任务依赖项
            "dependsOn":[
                "cmake",
                "make"
            ]
        }
    ]

}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值