目录
一、概述
1.1原理
1.2实现步骤
1.3应用场景
二、代码实现
2.1关键函数
2.1.1 计算FPFH特征
2.1.2 RANSAC配准
2.1.3 可视化点云
2.2完整代码
三、实现效果
PCL点云算法汇总及实战案例汇总的目录地址链接:
PCL点云算法与项目实战案例汇总(长期更新)
一、概述
RANSAC(Random Sample Consensus)是一种通过迭代来估计数据集内数学模型参数的算法,用于处理包含大量离群点的场景。在点云配准中,RANSAC算法可以在未知的条件下配准两个不同视角的点云,找到最佳的匹配变换矩阵。本文结合FPFH(Fast Point Feature Histograms)特征计算和改进的RANSAC算法,实现点云的粗配准。
1.1原理
FPFH特征描述子是一种局部几何特征,用于提高点云之间的特征匹配精度。它通过计算点与邻域点的法向量关系来描述局部几何特征。FPFH与RANSAC结合,通过特征匹配找到两个点云间的最佳配准矩阵。
RANSAC配准的主要步骤:
- 随机选择三个点进行配准计算。
- 根据这些点生成初步的配准矩阵。
- 使用该矩阵配准整个点云,计算内点数量。
- 迭代多次,选择最佳配准结果。
1.2实现步骤
- 加载点云数据:从文件中加载源点云和目标点云。
- 计算FPFH特征:计算源点云和目标点云的FPFH特征。
- RANSAC配准:使用RANSAC算法配准两组点云,得到最佳配准矩阵。
- 可视化结果:展示配准前后的点云
1.3应用场景
- 多视角的点云配准。
- 无序数据点的粗配准。
- 机器人视觉中环境地图的构建和配准。
二、代码实现
2.1关键函数
2.1.1 计算FPFH特征
用于计算输入点云的FPFH特征,计算法向量并基于邻域特征计算FPFH描述子。
fpfhFeature::Ptr compute_fpfh_feature(pointcloud::Ptr input_cloud)
{pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());// 法向量估计pointnormal::Ptr normals(new pointnormal);pcl::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> n;n.setInputCloud(input_cloud); // 设置输入点云n.setNumberOfThreads(8); // 使用8线程加速计算n.setSearchMethod(tree); // 设置搜索方法为KD树n.setKSearch(10); // 设置K近邻点个数n.compute(*normals); // 计算法向量// FPFH特征估计fpfhFeature::Ptr fpfh(new fpfhFeature);pcl::FPFHEstimationOMP<pcl::PointXYZ, pcl::Normal, pcl::FPFHSignature33> fest;fest.setNumberOfThreads(8); // 使用8线程并行计算fest.setInputCloud(input_cloud); // 设置输入点云fest.setInputNormals(normals); // 设置输入法线fest.setSearchMethod(tree); // 使用KD树搜索fest.setKSearch(10); // 设置K近邻点个数fest.compute(*fpfh); // 计算FPFH特征return fpfh;
}
2.1.2 RANSAC配准
该函数使用改进的RANSAC算法,通过特征匹配对两组点云进行配准,并返回配准后的点云。
pointcloud::Ptr ransac_registration(pointcloud::Ptr source, pointcloud::Ptr target, fpfhFeature::Ptr source_fpfh, fpfhFeature::Ptr target_fpfh)
{pcl::SampleConsensusPrerejective<PointT, PointT, pcl::FPFHSignature33> r_sac;r_sac.setInputSource(source); // 设置源点云r_sac.setInputTarget(target); // 设置目标点云r_sac.setSourceFeatures(source_fpfh); // 设置源点云的FPFH特征r_sac.setTargetFeatures(target_fpfh); // 设置目标点云的FPFH特征r_sac.setCorrespondenceRandomness(5); // 随机特征对应时使用的邻居数量r_sac.setInlierFraction(0.5f); // 设置所需的内点比例r_sac.setNumberOfSamples(3); // 设置采样点的数量r_sac.setSimilarityThreshold(0.1f); // 设置边缘长度相似度阈值r_sac.setMaxCorrespondenceDistance(1.0f);// 设置最大对应点距离r_sac.setMaximumIterations(100); // 设置最大迭代次数pointcloud::Ptr aligned(new pointcloud); // 配准后的点云r_sac.align(*aligned); // 执行配准pcl::transformPointCloud(*source, *aligned, r_sac.getFinalTransformation()); // 对源点云进行变换cout << "变换矩阵:\n" << r_sac.getFinalTransformation() << endl; // 输出变换矩阵return aligned;
}
2.1.3 可视化点云
在两个窗口中显示点云,窗口1显示配准前的点云,窗口2显示配准后的点云
void visualize_pcd(pointcloud::Ptr pcd_src, pointcloud::Ptr pcd_tgt, pointcloud::Ptr pcd_final)
{pcl::visualization::PCLVisualizer viewer("registration Viewer");// 创建两个显示窗口并设置背景颜色int v1, v2;viewer.createViewPort(0, 0.0, 0.5, 1.0, v1);viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);viewer.setBackgroundColor(255, 255, 255, v1);viewer.setBackgroundColor(255, 255, 255, v2);// 设置点云颜色pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> src_h(pcd_src, 0, 255, 0); // 源点云绿色pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> tgt_h(pcd_tgt, 255, 0, 0); // 目标点云红色pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> final_h(pcd_final, 0, 255, 0); // 配准后的点云绿色// 添加点云到显示窗口viewer.addPointCloud(pcd_src, src_h, "source cloud", v1);viewer.addPointCloud(pcd_tgt, tgt_h, "target cloud", v1);viewer.addPointCloud(pcd_tgt, tgt_h, "tgt cloud", v2);viewer.addPointCloud(pcd_final, final_h, "final cloud", v2);while (!viewer.wasStopped()){viewer.spinOnce(100);boost::this_thread::sleep(boost::posix_time::microseconds(100000));}
}
2.2完整代码
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/features/fpfh_omp.h>
#include <boost/thread/thread.hpp>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/registration/sample_consensus_prerejective.h> // RANSAC配准using namespace std;
typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<pcl::PointXYZ> pointcloud;
typedef pcl::PointCloud<pcl::Normal> pointnormal;
typedef pcl::PointCloud<pcl::FPFHSignature33> fpfhFeature;
typedef pcl::PointCloud<pcl::PointXYZ> pointcloud;// 计算FPFH特征
fpfhFeature::Ptr compute_fpfh_feature(pointcloud::Ptr input_cloud)
{pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());// 法向量估计pointnormal::Ptr normals(new pointnormal);pcl::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> n;n.setInputCloud(input_cloud); // 设置输入点云n.setNumberOfThreads(8); // 使用8线程加速计算n.setSearchMethod(tree); // 设置搜索方法为KD树n.setKSearch(10); // 设置K近邻点个数n.compute(*normals); // 计算法向量// FPFH特征估计fpfhFeature::Ptr fpfh(new fpfhFeature);pcl::FPFHEstimationOMP<pcl::PointXYZ, pcl::Normal, pcl::FPFHSignature33> fest;fest.setNumberOfThreads(8); // 使用8线程并行计算fest.setInputCloud(input_cloud); // 设置输入点云fest.setInputNormals(normals); // 设置输入法线fest.setSearchMethod(tree); // 使用KD树搜索fest.setKSearch(10); // 设置K近邻点个数fest.compute(*fpfh); // 计算FPFH特征return fpfh;
}// RANSAC配准
pointcloud::Ptr ransac_registration(pointcloud::Ptr source, pointcloud::Ptr target, fpfhFeature::Ptr source_fpfh, fpfhFeature::Ptr target_fpfh)
{pcl::SampleConsensusPrerejective<PointT, PointT, pcl::FPFHSignature33> r_sac;r_sac.setInputSource(source); // 设置源点云r_sac.setInputTarget(target); // 设置目标点云r_sac.setSourceFeatures(source_fpfh); // 设置源点云的FPFH特征r_sac.setTargetFeatures(target_fpfh); // 设置目标点云的FPFH特征r_sac.setCorrespondenceRandomness(5); // 随机特征对应时使用的邻居数量r_sac.setInlierFraction(0.5f); // 设置所需的内点比例r_sac.setNumberOfSamples(3); // 设置采样点的数量r_sac.setSimilarityThreshold(0.1f); // 设置边缘长度相似度阈值r_sac.setMaxCorrespondenceDistance(1.0f);// 设置最大对应点距离r_sac.setMaximumIterations(100); // 设置最大迭代次数pointcloud::Ptr aligned(new pointcloud); // 配准后的点云r_sac.align(*aligned); // 执行配准pcl::transformPointCloud(*source, *aligned, r_sac.getFinalTransformation()); // 对源点云进行变换cout << "变换矩阵:\n" << r_sac.getFinalTransformation() << endl; // 输出变换矩阵return aligned;
}// 可视化
void visualize_pcd(pointcloud::Ptr pcd_src, pointcloud::Ptr pcd_tgt, pointcloud::Ptr pcd_final)
{pcl::visualization::PCLVisualizer viewer("registration Viewer");int v1, v2;viewer.createViewPort(0, 0.0, 0.5, 1.0, v1);viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);viewer.setBackgroundColor(255, 255, 255, v1);viewer.setBackgroundColor(255, 255, 255, v2);pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> src_h(pcd_src, 0, 255, 0); // 源点云绿色pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> tgt_h(pcd_tgt, 255, 0, 0); // 目标点云红色pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> final_h(pcd_final, 0, 255, 0); // 配准后的点云绿色viewer.addPointCloud(pcd_src, src_h, "source cloud", v1);viewer.addPointCloud(pcd_tgt, tgt_h, "target cloud", v1);viewer.addPointCloud(pcd_tgt, tgt_h, "tgt cloud", v2);viewer.addPointCloud(pcd_final, final_h, "final cloud", v2);while (!viewer.wasStopped()){viewer.spinOnce(100);boost::this_thread::sleep(boost::posix_time::microseconds(100000));}
}int main(int argc, char** argv)
{pcl::PointCloud<PointT>::Ptr source(new pcl::PointCloud<PointT>);pcl::io::loadPCDFile("1.pcd", *source);pcl::PointCloud<PointT>::Ptr target(new pcl::PointCloud<PointT>);pcl::io::loadPCDFile("2.pcd", *target);fpfhFeature::Ptr source_fpfh = compute_fpfh_feature(source);fpfhFeature::Ptr target_fpfh = compute_fpfh_feature(target);pointcloud::Ptr align = ransac_registration(source, target, source_fpfh, target_fpfh);visualize_pcd(source, target, align);return 0;
}