PCL点云配准:特征点提取、同名点匹配和ICP算法实现
PCL点云配准:特征点提取、同名点匹配和ICP算法实现
本文将介绍如何使用PCL库进行点云配准,并提供完整的代码示例。整个流程包括以下步骤:
- 加载源点云和目标点云
- 计算点云表面法向量和FPFH特征
- 找到特征点之间的对应关系
- 根据距离和角度过滤对应关系
- 可视化对应关系
- 使用ICP算法进行点云配准
- 可视化配准结果
代码示例
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/registration/icp.h>
#include <pcl/registration/ndt.h>
#include <pcl/features/normal_3d.h>
#include <pcl/features/fpfh.h>
#include <pcl/visualization/pcl_visualizer.h>
int main(int argc, char** argv)
{
// Load point clouds
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_source(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_target(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile<pcl::PointXYZ>('source.pcd', *cloud_source);
pcl::io::loadPCDFile<pcl::PointXYZ>('target.pcd', *cloud_target);
// Compute surface normals
pcl::PointCloud<pcl::Normal>::Ptr normals_source(new pcl::PointCloud<pcl::Normal>);
pcl::PointCloud<pcl::Normal>::Ptr normals_target(new pcl::PointCloud<pcl::Normal>);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setSearchMethod(tree);
ne.setInputCloud(cloud_source);
ne.setKSearch(20);
ne.compute(*normals_source);
ne.setInputCloud(cloud_target);
ne.compute(*normals_target);
// Compute FPFH features
pcl::PointCloud<pcl::FPFHSignature33>::Ptr fpfhs_source(new pcl::PointCloud<pcl::FPFHSignature33>);
pcl::PointCloud<pcl::FPFHSignature33>::Ptr fpfhs_target(new pcl::PointCloud<pcl::FPFHSignature33>);
pcl::FPFHEstimation<pcl::PointXYZ, pcl::Normal, pcl::FPFHSignature33> fpfh;
fpfh.setSearchMethod(tree);
fpfh.setInputCloud(cloud_source);
fpfh.setInputNormals(normals_source);
fpfh.setRadiusSearch(0.5);
fpfh.compute(*fpfhs_source);
fpfh.setInputCloud(cloud_target);
fpfh.setInputNormals(normals_target);
fpfh.compute(*fpfhs_target);
// Find correspondences
pcl::CorrespondencesPtr correspondences(new pcl::Correspondences);
pcl::registration::CorrespondenceEstimation<pcl::FPFHSignature33, pcl::FPFHSignature33> est;
est.setInputSource(fpfhs_source);
est.setInputTarget(fpfhs_target);
est.determineCorrespondences(*correspondences);
// Reject correspondences based on distance and angle
pcl::CorrespondencesPtr correspondences_filtered(new pcl::Correspondences);
for (int i = 0; i < correspondences->size(); i++)
{
float dist = pcl::geometry::distance(cloud_source->at(correspondences->at(i).index_query),
cloud_target->at(correspondences->at(i).index_match));
float angle = pcl::geometry::acos3(cloud_source->at(correspondences->at(i).index_query).getNormalVector3fMap(),
cloud_target->at(correspondences->at(i).index_match).getNormalVector3fMap());
if (dist < 0.5 && angle < pcl::deg2rad(30))
{
correspondences_filtered->push_back(correspondences->at(i));
}
}
// Visualize correspondences
pcl::visualization::PCLVisualizer viewer('Correspondences');
viewer.addPointCloud(cloud_source, 'cloud_source');
viewer.addPointCloud(cloud_target, 'cloud_target');
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 0, 0, 'cloud_source');
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 1, 0, 'cloud_target');
for (int i = 0; i < correspondences_filtered->size(); i++)
{
pcl::PointXYZ& p1 = cloud_source->at(correspondences_filtered->at(i).index_query);
pcl::PointXYZ& p2 = cloud_target->at(correspondences_filtered->at(i).index_match);
std::stringstream ss;
ss << 'correspondence_' << i;
viewer.addLine(p1, p2, 1, 1, 1, ss.str());
}
viewer.spin();
// Perform ICP registration
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_source_registered(new pcl::PointCloud<pcl::PointXYZ>);
pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
icp.setInputSource(cloud_source);
icp.setInputTarget(cloud_target);
icp.setCorrespondenceEstimation(new pcl::registration::CorrespondenceEstimationBackProjection<pcl::PointXYZ, pcl::PointXYZ>);
icp.setCorrespondences(correspondences_filtered);
icp.setMaximumIterations(100);
icp.setTransformationEpsilon(1e-8);
icp.setEuclideanFitnessEpsilon(1e-6);
icp.align(*cloud_source_registered);
// Visualize registration result
pcl::visualization::PCLVisualizer viewer2('Registration Result');
viewer2.addPointCloud(cloud_source_registered, 'cloud_source_registered');
viewer2.addPointCloud(cloud_target, 'cloud_target');
viewer2.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 0, 0, 'cloud_source_registered');
viewer2.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 1, 0, 'cloud_target');
viewer2.spin();
return 0;
}
代码说明
- 加载点云: 代码首先加载源点云和目标点云,并分别存储在
cloud_source和cloud_target指针中。 - 计算表面法向量: 代码使用
pcl::NormalEstimation类计算每个点云的表面法向量,并将结果存储在normals_source和normals_target指针中。 - 计算FPFH特征: 代码使用
pcl::FPFHEstimation类计算每个点云的FPFH特征,并将结果存储在fpfhs_source和fpfhs_target指针中。 - 寻找对应关系: 代码使用
pcl::CorrespondenceEstimation类寻找源点云和目标点云之间的对应关系,并将结果存储在correspondences指针中。 - 过滤对应关系: 代码根据距离和角度过滤掉不合适的对应关系,并将结果存储在
correspondences_filtered指针中。 - 可视化对应关系: 代码使用
pcl::visualization::PCLVisualizer类可视化过滤后的对应关系。 - 执行ICP配准: 代码使用
pcl::IterativeClosestPoint类执行ICP配准,并将结果存储在cloud_source_registered指针中。 - 可视化配准结果: 代码使用
pcl::visualization::PCLVisualizer类可视化配准后的结果。
代码中的关键部分
- BackProjection对应关系估计方法: 代码中使用的ICP算法使用了
CorrespondenceEstimationBackProjection方法,这种方法可以提高ICP算法的收敛速度和精度,尤其适用于特征点较少的情况。 - 过滤对应关系: 代码根据距离和角度过滤对应关系,可以有效去除噪声和错误对应,提高配准精度。
其他说明
- 如果需要更高的精度,可以使用NDT算法代替ICP算法进行配准。
- 代码中的
source.pcd和target.pcd需要替换成实际的点云文件路径。
希望本文能够帮助你理解如何使用PCL库进行点云配准。如有任何问题,请随时留言。
原文地址: https://www.cveoy.top/t/topic/jUoH 著作权归作者所有。请勿转载和采集!