如何从范围图像中提取 NARF 特征
本教程演示如何从深度图像中在 NARF 关键点位置提取 NARF 描述符。该可执行文件使我们能够从磁盘加载点云(或创建它,如果没有提供),在其上提取兴趣点,然后在这些位置计算描述符。然后,它在图像和 3D 查看器中可视化这些位置。
Found 10 key points.
Extracted 11 descriptors for 10 keypoints.
No *.pcd file given => Generating example point cloud.
Found 6 key points.
Extracted 6 descriptors for 6 keypoints.
这段代码写的是一个使用 PCL(Point Cloud Library,点云库)库的 C++ 程序,在3D 图像识别领域进行操作。其中包括:
参数设置:如角度解析度、参考框架等。
帮助功能:如果运行程序时添加-h作为参数,就会打印出程序使用方法,并返回0。
视窗姿态设置:
setViewerPose
函数用于设置3D视窗的观察角度和位置。主函数:
命令行参数解析:对输入的命令行参数进行解析,如角度解析度、功能开关等。
读取或创建点云数据:先检查输入的点云文件,如果不存在则会创建一个样例点云数据。
利用点云创建 RangeImage(过程包括设定传感器方位及计算各点的深度信息)。
在3D视窗中展示这个 RangeImage。
提取 NARF 关键点。
在 RangeImage 中标记出这些关键点,并在3D视窗中显示这些关键点。
对这些关键点执行 NARF 描述符提取操作。
主循环:持续运行,直到视窗关闭。
这个程序主要处理3D图像数据,特别是利用NARF算法来寻找并标记出图像中的关键点。完整包括了加载样例或现有数据,图形化显示,以及结果的提取。
/* \author Bastian Steder */ // 作者: Bastian Steder
// 引入所需要的头文件
#include <iostream> // 提供输入输出流的功能
// PCL库相关的头文件
#include <pcl/range_image/range_image.h> // 提供range image相关的功能
#include <pcl/io/pcd_io.h> // 提供pcd文件的读写功能
#include <pcl/visualization/range_image_visualizer.h> // 提供range image的可视化功能
#include <pcl/visualization/pcl_visualizer.h> // 提供pcl的可视化功能
#include <pcl/features/range_image_border_extractor.h> // 提供range image边界抽取的功能
#include <pcl/keypoints/narf_keypoint.h> // 提供narf关键点检测的功能
#include <pcl/features/narf_descriptor.h> // 提供narf描述符的功能
#include <pcl/console/parse.h> // 提供命令行参数解析的功能
#include <pcl/common/file_io.h> // 提供文件输入输出的功能:getFilenameWithoutExtension
typedef pcl::PointXYZ PointType; // 定义PointType为pcl::PointXYZ类型
// 参数设定
float angular_resolution = 0.5f; // 角度分辨率
float support_size = 0.2f; // 描述符的支持尺度
pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME; // 坐标系设定
bool setUnseenToMaxRange = false; // 是否将看不见的点设为最大范围
bool rotation_invariant = true; // 是否旋转不变
// 定义printUsage函数,不返回任何值,接收一个字符常量指针,输出帮助信息
void
printUsage (const char* progName)
{
//将出现在命令行中的程序的名字和若干参数/options的信息打印出来
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 to max range\n"
<< "-s <float> support size for the interest points (diameter of the used sphere - "
"default "<<support_size<<")\n"
<< "-o <0/1> switch rotational invariant version of the feature on/off"
<< " (default "<< (int)rotation_invariant<<")\n"
<< "-h this help\n"
<< "\n\n";
}
// 定义setViewerPose函数,不返回任何值,接收一个可视化对象和一个存储相机位姿的对象,
// 用于设置可视化对象的相机位姿
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);
// 调用viewer的setCameraPosition函数设置相机的位姿
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]);
}
int
main (int argc, char** argv)
{
// 解析命令行参数
// 检查是否有请求帮助信息的"-h"参数,如果有则打印使用方法并退出程序
if (pcl::console::find_argument (argc, argv, "-h") >= 0)
{
printUsage (argv[0]);
return 0;
}
// 检查是否有"-m"参数,用于将未看到的点设置为最大距离
if (pcl::console::find_argument (argc, argv, "-m") >= 0)
{
setUnseenToMaxRange = true;
std::cout << "Setting unseen values in range image to maximum range readings.\n";
}
// 解析是否启用旋转不变特性的"-o"参数
if (pcl::console::parse (argc, argv, "-o", rotation_invariant) >= 0)
std::cout << "Switching rotation invariant feature version "<< (rotation_invariant ? "on" : "off")<<".\n";
// 解析坐标框架的"-c"参数
int tmp_coordinate_frame;
if (pcl::console::parse (argc, argv, "-c", tmp_coordinate_frame) >= 0)
{
coordinate_frame = pcl::RangeImage::CoordinateFrame (tmp_coordinate_frame);
std::cout << "Using coordinate frame "<< (int)coordinate_frame<<".\n";
}
// 解析兴趣点支持区域大小的"-s"参数
if (pcl::console::parse (argc, argv, "-s", support_size) >= 0)
std::cout << "Setting support size to "<<support_size<<".\n";
// 解析角分辨率的"-r"参数,并从度转换为弧度
if (pcl::console::parse (argc, argv, "-r", angular_resolution) >= 0)
{
std::cout << "Setting angular resolution to "<<angular_resolution<<"deg.\n";
angular_resolution = pcl::deg2rad (angular_resolution);
}
// -----读取pcd文件或者如果没有提供就创建示例点云-----
// ------------------------------------------------------------------
pcl::PointCloud<PointType>::Ptr point_cloud_ptr (new pcl::PointCloud<PointType>); // 创建一个新的点云指针
pcl::PointCloud<PointType>& point_cloud = *point_cloud_ptr; // 创建一个点云的引用,简化之后的代码操作
pcl::PointCloud<pcl::PointWithViewpoint> far_ranges; // 创建一个视点信息的点云,用于处理远距离的点
Eigen::Affine3f scene_sensor_pose (Eigen::Affine3f::Identity ()); // 定义传感器的姿态,初始为单位矩阵
std::vector<int> pcd_filename_indices = pcl::console::parse_file_extension_argument (argc, argv, "pcd"); // 解析命令行参数中的pcd文件名
if (!pcd_filename_indices.empty ()) // 如果命令行中提供了pcd文件
{
std::string filename = argv[pcd_filename_indices[0]]; // 获取文件名
if (pcl::io::loadPCDFile (filename, point_cloud) == -1) // 尝试加载这个pcd文件,如果失败则返回错误信息
{
std::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_);
// 尝试加载对应的远距离信息的pcd文件,如果失败则输出不存在的信息
std::string far_ranges_filename = pcl::getFilenameWithoutExtension (filename)+"_far_ranges.pcd";
if (pcl::io::loadPCDFile (far_ranges_filename.c_str (), far_ranges) == -1)
std::cout << "Far ranges file \""<<far_ranges_filename<<"\" does not exists.\n"; // 文件不存在的提示
}
else // 没有提供pcd文件
{
setUnseenToMaxRange = true; // 将无法看到的点设置为最大范围值
std::cout << "\nNo *.pcd file given => Generating example point cloud.\n\n"; // 提示正在生成示例点云
// 生成一个简单的栅格形状的点云,用于之后的处理
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.push_back (point); // 将点加入到点云中
}
}
point_cloud.width = point_cloud.size (); // 设置点云的宽度为点的数量
point_cloud.height = 1; // 高度设置为1,表示点云是非有序的
}
// 从点云创建RangeImage
// -----------------------------------------------
float noise_level = 0.0; // 噪声级别设置为0
float min_range = 0.0f; // 最小范围设置为0
int border_size = 1; // 边框大小设置为1
pcl::RangeImage::Ptr range_image_ptr (new pcl::RangeImage); // 创建一个新的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); // 使用点云数据填充RangeImage
range_image.integrateFarRanges (far_ranges); // 集成远端范围信息
if (setUnseenToMaxRange) // 如果设置了将未看到的值设为最大范围
range_image.setUnseenToMaxRange (); // 则对RangeImage应用该设置
// 打开3D视图并添加点云
// --------------------------------------------
pcl::visualization::PCLVisualizer viewer ("3D Viewer"); // 创建3D视图
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.initCameraParameters (); // 初始化相机参数
setViewerPose (viewer, range_image.getTransformationToWorldSystem ()); // 设置视图的姿态
// 显示range image
// --------------------------
pcl::visualization::RangeImageVisualizer range_image_widget ("Range image"); // 创建range image的视图
range_image_widget.showRangeImage (range_image); // 显示range image
// 提取NARF关键点
// --------------------------------
pcl::RangeImageBorderExtractor range_image_border_extractor; // 创建RangeImage边界提取器
pcl::NarfKeypoint narf_keypoint_detector; // 创建NARF关键点检测器
narf_keypoint_detector.setRangeImageBorderExtractor (&range_image_border_extractor); // 设置范围图像边界提取器
narf_keypoint_detector.setRangeImage (&range_image); // 设置范围图像
narf_keypoint_detector.getParameters ().support_size = support_size; // 设置支持区域大小
pcl::PointCloud<int> keypoint_indices; // 创建保存关键点索引的点云
narf_keypoint_detector.compute (keypoint_indices); // 计算关键点
std::cout << "Found "<<keypoint_indices.size ()<<" key points.\n"; // 输出找到的关键点数量
// 在3D视图中展示关键点
// -------------------------------------
pcl::PointCloud<pcl::PointXYZ>::Ptr keypoints_ptr (new pcl::PointCloud<pcl::PointXYZ>); // 创建一个新的XYZ点云指针,用于存放关键点
pcl::PointCloud<pcl::PointXYZ>& keypoints = *keypoints_ptr; // 通过引用操作点云
keypoints.resize (keypoint_indices.size ()); // 调整关键点点云的大小
for (std::size_t i=0; i<keypoint_indices.size (); ++i) // 遍历所有关键点索引
keypoints[i].getVector3fMap () = range_image[keypoint_indices[i]].getVector3fMap (); // 转换关键点索引到3D坐标
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"); // 设置关键点的渲染属性
// 提取NARF描述子
// ------------------------------------------------------
std::vector<int> keypoint_indices2; // 创建保存关键点索引的向量
keypoint_indices2.resize (keypoint_indices.size ()); // 调整大小
for (unsigned int i=0; i<keypoint_indices.size (); ++i) // 转换关键点索引
keypoint_indices2[i]=keypoint_indices[i];
pcl::NarfDescriptor narf_descriptor (&range_image, &keypoint_indices2); // 创建NARF描述子提取器
narf_descriptor.getParameters ().support_size = support_size; // 设置描述子的支持区域大小
narf_descriptor.getParameters ().rotation_invariant = rotation_invariant; // 设置是否使用旋转不变性
pcl::PointCloud<pcl::Narf36> narf_descriptors; // 创建NARF描述子的点云
narf_descriptor.compute (narf_descriptors); // 计算描述子
std::cout << "Extracted "<<narf_descriptors.size ()<<" descriptors for "
<<keypoint_indices.size ()<< " keypoints.\n"; // 输出描述子数量
// 主循环,直到视图关闭
while (!viewer.wasStopped ())
{
range_image_widget.spinOnce (); // 处理GUI事件
viewer.spinOnce ();
//pcl_sleep(0.01);
Sleep(10); // 暂停一段时间,防止CPU使用率过高
}
}
(pcl::console::parse(argc, argv, "-o", rotation_invariant) >= 0)
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);
setViewerPose(viewer, range_image.getTransformationToWorldSystem());
keypoints[i].getVector3fMap() =
range_image[keypoint_indices[i]].getVector3fMap();
NARF关键点检测过程