前言
在PCL中,有多种方法和函数可以用来提取点云特征,本文介绍关键点提取。
提取点云关键点,本文介绍的方法包括:SIFT、Harris、NARF、ISS和SUSAN。
Harris 提取点云关键点,效果如下图所示:
白色点是原始的点云(兔子),绿色点是Harris提取的点云关键点。
1、SIFT 提取点云关键点
基本原理:
- SIFT算法的设计目的是在点云中,不同尺度和旋转角度下稳定地检测特征点。
- 主要步骤包括:
- 构建尺度空间,通过高斯模糊和降采样生成不同尺度的图像。
- 在不同尺度图像中计算高斯差分(Difference of Gaussian, DoG),检测极值点。
- 对每个极值点进行精确定位,通过滤除低对比度点和边缘响应点,保留稳定的关键点。
- 为每个关键点分配主方向,使其具有旋转不变性。
- 基于关键点周围的梯度方向和幅值,生成特征描述子。
使用场景:
- SIFT关键点适用于需要在尺度和旋转变化下保持不变的应用,例如物体识别和点云匹配。
示例代码:
#include <pcl/point_types.h>
#include <pcl/keypoints/sift_keypoint.h>
#include <pcl/io/pcd_io.h>
// 提取SIFT关键点的函数
pcl::PointCloud<pcl::PointWithScale>::Ptr extractSIFTKeypoints(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) {
// 创建SIFT关键点检测对象
pcl::SIFTKeypoint<pcl::PointXYZ, pcl::PointWithScale> sift;
// 创建KdTree用于加速搜索
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
sift.setSearchMethod(tree);
// 设置尺度参数
// 第一个参数是初始尺度,
// 第二个参数是尺度的数量,
// 第三个参数是尺度之间的倍数关系
sift.setScales(0.01f, 3, 2);
// 设置最小对比度(用于过滤掉对比度太低的关键点)
sift.setMinimumContrast(0.0f);
// 设置输入点云
sift.setInputCloud(cloud);
// 创建一个点云对象用于存储关键点
pcl::PointCloud<pcl::PointWithScale>::Ptr keypoints(new pcl::PointCloud<pcl::PointWithScale>);
// 计算SIFT关键点
sift.compute(*keypoints);
// 返回关键点
return keypoints;
}
2、Harris 提取点云关键点
基本原理:
- Harris角点检测器通过计算图像中每个点云的自相关矩阵来找到角点(特征点)。
- 主要步骤包括:
- 计算每个像素点的梯度信息。
- 根据梯度信息构建自相关矩阵。
- 计算响应函数R,响应函数R的高值表示可能的角点。
- 对响应函数进行非极大值抑制,得到角点位置。
使用场景:
- Harris关键点适用于需要检测点云中局部几何特征显著点的应用,如点云配准和运动检测。
示例代码:
#include <pcl/point_types.h>
#include <pcl/keypoints/harris_3d.h>
#include <pcl/io/pcd_io.h>
// 提取Harris关键点的函数
pcl::PointCloud<pcl::PointXYZI>::Ptr extractHarrisKeypoints(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) {
// 创建Harris关键点检测对象
pcl::HarrisKeypoint3D<pcl::PointXYZ, pcl::PointXYZI> harris;
// 设置非极大值抑制(用于去除非局部最大值点)
harris.setNonMaxSupression(true);
// 设置搜索半径
harris.setRadius(0.01);
// 设置输入点云
harris.setInputCloud(cloud);
// 创建一个点云对象用于存储关键点
pcl::PointCloud<pcl::PointXYZI>::Ptr keypoints(new pcl::PointCloud<pcl::PointXYZI>);
// 计算Harris关键点
harris.compute(*keypoints);
// 返回关键点
return keypoints;
}
3、NARF 提取点云关键点
基本原理:
- NARF关键点检测器结合点云的边界特征和法线方向来检测关键点。
- 主要步骤包括:
- 将点云转换为深度图像(Range Image)。
- 提取深度图像的边界信息。
- 基于边界信息和法线方向,检测具有显著特征的关键点。
使用场景:
- NARF关键点适用于环境感知和机器人导航等需要高鲁棒性的场景。
示例代码:
#include <pcl/point_types.h>
#include <pcl/range_image/range_image.h>
#include <pcl/keypoints/narf_keypoint.h>
#include <pcl/io/pcd_io.h>
// 提取NARF关键点的函数
pcl::PointCloud<int>::Ptr extractNARFKeypoints(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) {
// 设置角度分辨率(以弧度为单位)
float angular_resolution = pcl::deg2rad(1.0f);
// 设置坐标系框架
pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;
// 设置噪声水平
float noise_level = 0.0;
// 设置最小范围
float min_range = 0.0f;
// 设置边界大小
int border_size = 1;
// 创建范围图像对象
pcl::RangeImage range_image;
// 从点云创建范围图像
range_image.createFromPointCloud(*cloud, angular_resolution, pcl::deg2rad(360.0f), pcl::deg2rad(180.0f), Eigen::Affine3f::Identity(), coordinate_frame, noise_level, min_range, border_size);
// 创建边界提取器对象
pcl::RangeImageBorderExtractor range_image_border_extractor;
// 创建NARF关键点检测对象
pcl::NarfKeypoint narf_keypoint_detector;
// 设置范围图像边界提取器
narf_keypoint_detector.setRangeImageBorderExtractor(&range_image_border_extractor);
// 设置范围图像
narf_keypoint_detector.setRangeImage(&range_image);
// 设置支持大小参数
narf_keypoint_detector.getParameters().support_size = 0.2f;
// 创建一个点云对象用于存储关键点索引
pcl::PointCloud<int>::Ptr keypoints_indices(new pcl::PointCloud<int>);
// 计算NARF关键点
narf_keypoint_detector.compute(*keypoints_indices);
// 返回关键点索引
return keypoints_indices;
}
4、ISS 提取点云关键点
基本原理:
- ISS关键点检测器通过分析点的局部几何特征来确定关键点。
- 主要步骤包括:
- 计算每个点的局部协方差矩阵。
- 通过协方差矩阵的特征值和特征向量,分析局部几何特征。
- 根据特征值的显著性和稳定性,选择关键点。
使用场景:
- ISS关键点适用于点云数据噪声较少,且需要提取稳定几何特征的场景。
示例代码:
#include <pcl/point_types.h>
#include <pcl/keypoints/iss_3d.h>
#include <pcl/io/pcd_io.h>
// 提取ISS关键点的函数
pcl::PointCloud<pcl::PointXYZ>::Ptr extractISSKeypoints(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) {
// 创建ISS关键点检测对象
pcl::ISSKeypoint3D<pcl::PointXYZ, pcl::PointXYZ> iss_detector;
// 设置显著半径(影响关键点的选择)
iss_detector.setSalientRadius(0.05);
// 设置非极大值抑制半径(用于抑制局部非最大值)
iss_detector.setNonMaxRadius(0.05);
// 设置两个阈值,用于控制特征值之间的比率
iss_detector.setThreshold21(0.975); // 阈值21
iss_detector.setThreshold32(0.975); // 阈值32
// 设置最小邻居数(用于过滤孤立点)
iss_detector.setMinNeighbors(5);
// 设置输入点云
iss_detector.setInputCloud(cloud);
// 创建一个点云对象用于存储关键点
pcl::PointCloud<pcl::PointXYZ>::Ptr keypoints(new pcl::PointCloud<pcl::PointXYZ>);
// 计算ISS关键点
iss_detector.compute(*keypoints);
// 返回关键点
return keypoints;
}
5、SUSAN 提取点云关键点
基本原理:
- SUSAN关键点检测器通过分析云点周围的强度或距离一致性来检测特征点。
- 主要步骤包括:
- 为每个点定义一个圆形邻域。
- 计算邻域内与中心点强度或距离相似的点的数量。
- 基于相似点的数量,判断该点是否为特征点。
使用场景:
- SUSAN关键点适用于需要提取局部特征显著点的应用,如点云处理中的特征检测。
示例代码:
#include <pcl/point_types.h>
#include <pcl/keypoints/susan.h>
#include <pcl/io/pcd_io.h>
// 提取SUSAN关键点的函数
pcl::PointCloud<pcl::PointXYZI>::Ptr extractSUSANKeypoints(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) {
// 创建SUSAN关键点检测对象
pcl::SUSANKeypoint<pcl::PointXYZ, pcl::PointXYZI> susan;
// 设置搜索半径
susan.setRadius(0.01);
// 设置输入点云
susan.setInputCloud(cloud);
// 创建一个点云对象用于存储关键点
pcl::PointCloud<pcl::PointXYZI>::Ptr keypoints(new pcl::PointCloud<pcl::PointXYZI>);
// 计算SUSAN关键点
susan.compute(*keypoints);
// 返回关键点
return keypoints;
}
6、方法总结
提取点云关键点,本文介绍的方法包括:
- SIFT (Scale-Invariant Feature Transform)
- Harris Keypoints
- NARF (Normal Aligned Radial Feature)
- ISS (Intrinsic Shape Signatures)
- SUSAN (Smallest Univalue Segment Assimilating Nucleus)
对比一下5种点云关键点算法,如下表格所示:
方法 | 简介 | 特点 | 时间复杂度 | 优点 | 缺点 |
SIFT | 基于尺度不变特征变换,检测不同尺度和旋转不变的关键点 | 尺度不变性、旋转不变性 | 高 | 在尺度和旋转变化下稳定,对噪声有较强的鲁棒性 | 计算复杂度高,占用内存大 |
Harris | 基于自相关矩阵计算角点,检测局部几何特征显著点 | 非极大值抑制 | 中 | 检测速度快,能有效检测角点 | 对噪声敏感,缺乏尺度不变性 |
NARF | 结合点云的边界特征和法线方向,通过深度图像提取关键点 | 法线和边界信息结合,鲁棒性高 | 中 | 对边界和法线特征敏感,适用于环境感知和机器人导航 | 需要生成深度图像,计算复杂度较高 |
ISS | 通过分析点的局部几何特征,选择特征值显著且稳定的关键点 | 几何特征分析,特征值比率 | 中 | 能检测出稳定的几何特征点,对噪声不敏感 | 计算复杂度较高,在密集点云上效率较低 |
SUSAN | 通过分析邻域内与中心点强度或距离相似的点,检测局部特征显著点 | 基于强度或距离一致性 | 低 | 计算简单,适用于实时处理 | 对特征点的准确性依赖于参数设置,对不同数据集需要调整参数 |
7、实践应用——Harris 提取点云关键点
对输入的PLY文件进行Harris特征提取,并分别可视化原图和特征提取后的效果。
代码思路:
- 包含头文件:包含PCL的点类型、IO操作、可视化和Harris关键点提取的头文件。
- 提取Harris关键点的函数:定义
extractHarrisKeypoints
函数,用于从输入点云中提取Harris关键点。 - 主函数:
- 直接在代码中指定PLY文件的路径。
- 创建点云对象并读取PLY文件。
- 调用
extractHarrisKeypoints
函数提取Harris关键点。 - 创建PCL可视化对象
viewer
,设置背景颜色。 - 添加原始点云和关键点到可视化对象中,并设置关键点的渲染属性。
- 启动可视化循环,显示原始点云和关键点。
示例代码:
#include <pcl/point_types.h>
#include <pcl/io/ply_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/keypoints/harris_3d.h>
#include <thread> // 添加此头文件
#include <chrono> // 添加此头文件
// 提取Harris关键点的函数
pcl::PointCloud<pcl::PointXYZI>::Ptr extractHarrisKeypoints(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) {
// 创建Harris关键点检测对象
pcl::HarrisKeypoint3D<pcl::PointXYZ, pcl::PointXYZI> harris;
harris.setNonMaxSupression(true); // 设置非极大值抑制
harris.setRadius(0.01); // 设置搜索半径
harris.setInputCloud(cloud); // 设置输入点云
// 创建一个点云对象用于存储关键点
pcl::PointCloud<pcl::PointXYZI>::Ptr keypoints(new pcl::PointCloud<pcl::PointXYZI>);
// 计算Harris关键点
harris.compute(*keypoints);
return keypoints;
}
int main(int argc, char** argv) {
// 指定PLY文件路径
std::string file_path = "../../bunny/data/bun000.ply"; // 请将此路径替换为你的PLY文件路径
// 创建点云对象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>());
// 读取PLY文件
if (pcl::io::loadPLYFile<pcl::PointXYZ>(file_path, *cloud) == -1) {
std::cerr << "Couldn't read file " << file_path << std::endl;
return -1;
}
// 提取Harris关键点
pcl::PointCloud<pcl::PointXYZI>::Ptr keypoints = extractHarrisKeypoints(cloud);
// 创建PCL可视化对象
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
// 设置背景颜色为黑色
viewer->setBackgroundColor(0, 0, 0);
// 添加原始点云
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> cloud_color_handler(cloud, 255, 255, 255);
viewer->addPointCloud(cloud, cloud_color_handler, "original cloud");
// 添加Harris关键点
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZI> keypoints_color_handler(keypoints, 0, 255, 0);
viewer->addPointCloud<pcl::PointXYZI>(keypoints, keypoints_color_handler, "keypoints");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 5, "keypoints");
// 启动可视化
// viewer->addCoordinateSystem(1.0);
// viewer->initCameraParameters();
while (!viewer->wasStopped()) {
viewer->spinOnce(100);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return 0;
}
可视化效果:
白色点是原始的点云(兔子),绿色点是Harris提取的点云关键点。
分享完成~