【PCL】教程 example2 3D点云之间的精确配准(FPFH特征对应关系估计变换矩阵)

news2024/11/16 19:34:58

这段代码主要实现了点云之间的配准功能,旨在通过估计点云的特征并找到最佳的对应关系来计算一个变换矩阵,从而可以将源点云(src)变换到目标点云(tgt)的坐标系统中。

代码功能和方法总结如下:

  1. 估计关键点(estimateKeypoints:使用pcl::UniformSampling过滤器从原始点云中提取均匀分布的关键点。关键点以1米的半径均匀采样后保存到磁盘上,以便于调试。

  2. 估计法线(estimateNormals:使用pcl::NormalEstimation为每个关键点估计法线。法线提取基于0.5米的搜索半径后,结果保存到磁盘上,以便调试。

  3. 估计FPFH特征(estimateFPFH:使用pcl::FPFHEstimation为每个关键点计算FPFH特征。特征估计在1米的搜索半径内完成,计算结果保存到磁盘上用于调试。

  4. 寻找对应关系(findCorrespondences:使用pcl::CorrespondenceEstimation计算两个点云中关键点的FPFH特征间的对应关系。

  5. 拒绝差的对应关系rejectBadCorrespondences):使用pcl::CorrespondenceRejectorDistance根据空间距离来拒绝不良的对应关系。设定最大距离为1米。

  6. 计算变换矩阵computeTransformation):使用pcl::TransformationEstimationSVD 基于剩余的良好对应关系来估计源点云到目标点云的刚性变换。

  7. 主程序(main):作为程序的入口,首先解析命令行参数加载.PCD文件,加载源点云和目标点云数据。然后调用computeTransformation计算最佳变换矩阵。最后,将源点云数据根据计算出的变换矩阵变换后保存到硬盘上。

综上所述,本段代码实现了3D点云之间的精确配准,包括了关键点提取、法线估计、特征计算、对应关系寻找、对应关系过滤和最后的变换矩阵估计等一系列步骤。这种点云配准在3D建模、环境映射、物体检测等领域有着重要的应用。

#include <pcl/console/parse.h> // 包含PCL库中处理命令行参数解析的功能
#include <pcl/point_types.h> // 包含定义了PCL支持的点类型的功能
#include <pcl/point_cloud.h> // 包含点云类的定义
#include <pcl/point_representation.h> // 包含点表示(特征)的定义


#include <pcl/io/pcd_io.h> // 包含PCD文件输入输出的功能
#include <pcl/conversions.h> // 包含点云类型转换的功能
#include <pcl/filters/uniform_sampling.h> // 包含均匀采样的滤波器
#include <pcl/features/normal_3d.h> // 包含计算点云中每个点的法线的功能
#include <pcl/features/fpfh.h> // 包含计算FPFH特征的功能
#include <pcl/registration/correspondence_estimation.h> // 包含估算对应关系的功能
#include <pcl/registration/correspondence_rejection_distance.h> // 包含基于距离的对应关系拒绝功能
#include <pcl/registration/transformation_estimation_svd.h> // 包含使用SVD(单因素分解)方法估算变换矩阵的功能


using namespace pcl; // 使用 PCL 命名空间
using namespace pcl::io; // 使用 PCL 的 IO 命名空间
using namespace pcl::console; // 使用 PCL 的控制台命名空间
using namespace pcl::registration; // 使用 PCL 的注册命名空间
PointCloud<PointXYZ>::Ptr src, tgt; // 定义源点云和目标点云的指针


// 以下为函数定义:



// 估算关键点
void
estimateKeypoints (const PointCloud<PointXYZ>::Ptr &src, 
                   const PointCloud<PointXYZ>::Ptr &tgt,
                   PointCloud<PointXYZ> &keypoints_src,
                   PointCloud<PointXYZ> &keypoints_tgt)
{
  // 获取一个均匀的关键点网格
  UniformSampling<PointXYZ> uniform; // 创建均匀采样的实例
  uniform.setRadiusSearch (1);  // 设置搜索半径为1米


  uniform.setInputCloud (src); // 设置输入的源点云
  uniform.filter (keypoints_src); // 进行滤波,并保留结果到keypoints_src


  uniform.setInputCloud (tgt); // 设置输入的目标点云
  uniform.filter (keypoints_tgt); // 进行滤波,并保留结果到keypoints_tgt


  // 以下为调试目的,可将结果保存到PCD文件并在pcl_viewer中查看
  savePCDFileBinary ("keypoints_src.pcd", keypoints_src); // 保存源关键点到文件
  savePCDFileBinary ("keypoints_tgt.pcd", keypoints_tgt); // 保存目标关键点到文件
}



// 估算法线
void
estimateNormals (const PointCloud<PointXYZ>::Ptr &src, 
                 const PointCloud<PointXYZ>::Ptr &tgt,
                 PointCloud<Normal> &normals_src,
                 PointCloud<Normal> &normals_tgt)
{
  NormalEstimation<PointXYZ, Normal> normal_est; // 创建法线估算实例
  normal_est.setInputCloud (src); // 设置输入的源点云
  normal_est.setRadiusSearch (0.5);  // 设置搜索半径为50厘米
  normal_est.compute (normals_src); // 计算结果保留在normals_src中


  normal_est.setInputCloud (tgt); // 设置输入的目标点云
  normal_est.compute (normals_tgt); // 计算结果保留在normals_tgt中


  // 以下为调试目的,可将结果保存到PCD文件并在pcl_viewer中查看
  PointCloud<PointNormal> s, t;
  copyPointCloud (*src, s); // 拷贝点到s
  copyPointCloud (normals_src, s); // 拷贝法线到s
  copyPointCloud (*tgt, t); // 拷贝点到t
  copyPointCloud (normals_tgt, t); // 拷贝法线到t
  savePCDFileBinary ("normals_src.pcd", s); // 保存源点云的法线到文件
  savePCDFileBinary ("normals_tgt.pcd", t); // 保存目标点云的法线到文件
}



void
computeTransformation (
    const PointCloud<PointXYZ>::Ptr &src,
    const PointCloud<PointXYZ>::Ptr &tgt,
    Eigen::Matrix4f &transform
)
{
    // 获取均匀分布的关键点
    PointCloud<PointXYZ>::Ptr keypoints_src(new PointCloud<PointXYZ>),
                              keypoints_tgt(new PointCloud<PointXYZ>);
    estimateKeypoints(src, tgt, *keypoints_src, *keypoints_tgt); // 调用 estimateKeypoints 方法估计关键点
    print_info("Found %zu and %zu keypoints for the source and target datasets.\n", 
               static_cast<std::size_t>(keypoints_src->size()),
               static_cast<std::size_t>(keypoints_tgt->size())); // 打印信息,输出找到的关键点数量


    // 计算所有关键点的法线
    PointCloud<Normal>::Ptr normals_src (new PointCloud<Normal>),
                            normals_tgt(new PointCloud<Normal>);
    estimateNormals(src, tgt, *normals_src, *normals_tgt); // 调用 estimateNormals 方法计算法线
    print_info("Estimated %zu and %zu normals for the source and target datasets.\n",
               static_cast<std::size_t>(normals_src->size()),
               static_cast<std::size_t>(normals_tgt->size())); // 打印信息,输出计算得到的法线数量


    // 计算每个关键点的 FPFH 特征
    PointCloud<FPFHSignature33>::Ptr fpfhs_src(new PointCloud<FPFHSignature33>), 
                                     fpfhs_tgt(new PointCloud<FPFHSignature33>);
    estimateFPFH(src, tgt, normals_src, normals_tgt, keypoints_src, keypoints_tgt, *fpfhs_src, *fpfhs_tgt); // 调用 estimateFPFH 方法计算 FPFH 特征


    // 查找 FPFH 空间中关键点的对应关系
    CorrespondencesPtr all_correspondences(new Correspondences),
                       good_correspondences(new Correspondences);
    findCorrespondences(fpfhs_src, fpfhs_tgt, *all_correspondences); // 调用 findCorrespondences 方法找到所有对应关系


    // 根据它们的 XYZ 距离拒绝错误的对应关系
    rejectBadCorrespondences(all_correspondences, keypoints_src, keypoints_tgt, *good_correspondences); // 调用 rejectBadCorrespondences 方法拒绝错误的对应关系


    for (const auto& corr : (*good_correspondences))
        std::cerr << corr << std::endl; // 对于每一个剩余的好的对应关系,输出到 cerr


    // 获得给定剩余对应关系后,两组关键点之间的最佳变换
    TransformationEstimationSVD<PointXYZ, PointXYZ> trans_est; // 定义变换估计对象
    trans_est.estimateRigidTransformation(*keypoints_src, *keypoints_tgt, *good_correspondences, transform); // 估算刚性变换矩阵
}


int
main (int argc, char** argv)
{
  // 解析命令行参数以查找 .pcd 文件
  std::vector<int> p_file_indices; // 定义一个整数向量用于存储文件的索引
  p_file_indices = parse_file_extension_argument (argc, argv, ".pcd"); // 解析获得所有后缀为 .pcd 的文件索引
  if (p_file_indices.size () != 2) // 如果没有找到两个 .pcd 文件,则报错
  {
    print_error ("Need one input source PCD file and one input target PCD file to continue.\n"); // 打印错误信息
    print_error ("Example: %s source.pcd target.pcd\n", argv[0]); // 提供正确使用的例子
    return (-1); // 返回错误码 -1
  }


  // 加载文件
  print_info ("Loading %s as source and %s as target...\n", argv[p_file_indices[0]], argv[p_file_indices[1]]); // 打印加载信息
  src.reset (new PointCloud<PointXYZ>); // 初始化源点云
  tgt.reset (new PointCloud<PointXYZ>); // 初始化目标点云
  if (loadPCDFile (argv[p_file_indices[0]], *src) == -1 || loadPCDFile (argv[p_file_indices[1]], *tgt) == -1) // 尝试加载文件,如果失败则报错
  {
    print_error ("Error reading the input files!\n"); // 打印错误信息
    return (-1); // 返回错误码 -1
  }


  // 计算最佳变换
  Eigen::Matrix4f transform; // 定义一个 4x4 的变换矩阵
  computeTransformation (src, tgt, transform); // 调用 computeTransformation 函数计算从源点云到目标点云的变换矩阵


  std::cerr << transform << std::endl; // 输出变换矩阵
  // 对数据进行变换并将结果写入磁盘
  PointCloud<PointXYZ> output; // 定义输出点云
  transformPointCloud (*src, output, transform); // 使用计算得到的变换矩阵对源点云进行变换
  savePCDFileBinary ("source_transformed.pcd", output); // 保存变换后的点云到文件
}

此代码是使用点云库(PCL)进行点云注册的示例程序。程序首先加载两个点云文件,然后计算从源点云到目标点云的最佳变换矩阵,并将变换后的源点云保存到新的文件中。这个过程可以应用于多种场景,如 3D 模型重建、环境映射与导航。

 computeTransformation 函数,用于计算从源点云向目标点云变换的最佳矩阵。函数首先估计两组点云的均匀关键点,然后计算关键点的法线,接下来计算关键点的 FPFH 特征。之后,找到两组关键点在 FPFH 特征空间中的对应关系,并拒绝那些距离较远的错误对应关系。最后,根据剩余的正确对应关系,利用 SVD 方法计算得到最佳的变换矩阵。

TransformationEstimationSVD<PointXYZ, PointXYZ> trans_est;
trans_est.estimateRigidTransformation (*keypoints_src, *keypoints_tgt, *good_correspondences, transform);

TransformationEstimationSVD<PointXYZ, PointXYZ>是一个类,用于估计两组点云间的刚体变换。其中,PointXYZ是PCL库中定义的一种点类型,包含了点的XYZ坐标。

刚体变换是指在变换过程中保持物体形状和大小不变,只进行旋转和平移。在点云处理中,刚体变换通常用于将一组点云数据精确地对齐到另一组点云数据中,这个过程就称为点云配准。

TransformationEstimationSVD内部实现了奇异值分解(Singular Value Decomposition, SVD)方法来计算最优化的刚体变换,即寻找一个最佳的旋转矩阵和平移向量,使得在这个变换下,一组点云与另一组点云之间的对应点尽可能地接近。

a4fbc7186b43de3819cdd7c262b5cd0a.png

CorrespondenceRejectorDistance rej;

259cb548e293043a3cb96ad5aed29a9a.png

CorrespondenceEstimation<FPFHSignature33, FPFHSignature33> est;

8e9693f48acd03072ea95cc6bfe91a27.png

FPFHEstimation<PointXYZ, Normal, FPFHSignature33> fpfh_est;

f9a5e8f65922e47eb9781ab43a003d45.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1637866.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32 DMA直接存储器存取

单片机学习&#xff01; 目录 文章目录 前言 一、DMA简介 1.1 DMA是什么 1.2 DMA作用 1.3 DMA通道 1.4 软硬件触发 1.5 芯片资源 二、存储器映像 2.1 存储器 2.2 STM32存储器 三、DMA框图 3.1 内核与存储器 3.2 寄存器 3.3 DMA数据转运 3.4 DMA总线作用 3.5 DMA请求 3.6 DMA结构…

Flutter笔记:Widgets Easier组件库(3)使用按钮组件

Flutter笔记 Widgets Easier组件库&#xff08;3&#xff09;&#xff1a;使用按钮组件 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddre…

数字旅游以科技创新为核心:推动旅游服务的智能化、精准化、个性化,为游客提供更加贴心、专业、高效的旅游服务

目录 一、引言 二、数字旅游以科技创新推动旅游服务智能化 1、智能化技术的应用 2、提升旅游服务的效率和质量 三、数字旅游以科技创新推动旅游服务精准化 1、精准化需求的识别与满足 2、精准化营销与推广 四、数字旅游以科技创新推动旅游服务个性化 1、个性化服务的创…

Cloudflare高级防御规则 看看我的网站如何用防御的

网站已趋于稳定&#xff0c;并且经过nginx调优。我想先分享一下Cloudflare的WAF规则&#xff0c;因为这是最有效的防御之一&#xff0c;可以抵御大量恶意攻击流量&#xff0c;我已经验证了数月。 对于海外独立站电商网站&#xff0c;Cloudflare的CDN服务是首选&#xff0c;它强…

File contains parsing errors: file:///etc/yum.repos.d/nginx.repo报错解决,文件配置出现问题

执行yum指令出现以下错误&#xff1a; 解决方案&#xff1a;yum的配置文件出现问题&#xff0c; 先删除yum.repos.d目录下所有文件 rm -f /etc/yum.repos.d/* 然后重新下载阿里的资源 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.…

VPX双路***至强高性能服务器模块

VPX双路***至强高性能服务器模块 1 产品介绍 1.1 产品概述 是一款基于Intel Xeon Gold系列处理器设计的双至强VPX模块&#xff0c;连接器采用VPX规范的高速连接器&#xff0c;专为高性能全加固服务器设计&#xff0c;具有贴片内存颗粒128GB DDR4内存&#xff0c;并提供了丰富…

翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习三

合集 ChatGPT 通过图形化的方式来理解 Transformer 架构 翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习一翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习二翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深…

图像处理:乘法滤波器(Multiplying Filter)和逆FFT位移

一、乘法滤波器&#xff08;Multiplying Filter&#xff09; 乘法滤波器是一种以像素值为权重的滤波器&#xff0c;它通过将滤波器的权重与图像的像素值相乘&#xff0c;来获得滤波后的像素值。具体地&#xff0c;假设乘法滤波器的权重为h(i,j)&#xff0c;图像的像素值为f(m,…

【氮化镓】GaN器件在航天器高可靠正向转换器中应用

文章是发表在《IEEE Journal of Emerging and Selected Topics in Power Electronics》2022年10月第10卷第5期上的一篇关于GaN(氮化镓)器件在航天器高可靠性正向转换器中应用的研究。文章的作者是匹兹堡大学电气与计算机工程系的Aidan Phillips, Thomas Cook和Brandon M. Gra…

code-server容器webpack的ws无法连接解决方法

TLDR 通过指定client的wsrul去连接ws devServer.client.webSocketURL ‘wss://<Forwarded uri>/ws’ 拓扑 1、code-server: 用于编写代码、启动webpack dev-server 服务&#xff1b;[https://<domain>:8001] 2、webpack: 用于浏览dev-server服务&#xff1b;[ht…

【计算机网络】网络层总结

目录 知识梗概 IP地址 子网划分 IP包头格式 路由 网络层协议 ARP病毒/ARP欺骗 知识梗概 IP地址 IP相关介绍&#xff1a;机器之间需要交流&#xff0c;必须要一个地址才能找到对应的主机&#xff0c;IP地址是主机的一种表示&#xff0c;保证主机之间的正常通信&#xff…

农牧渔农业信息网整站源码优化版

下载地址&#xff1a;农牧渔农业信息网整站源码优化版.zip 适合做农产品、农业物资、农活用人信息平台

记录一次恢复假卡750ti的过程

有一张卡&#xff0c;一直上不了144hz我就很纳闷&#xff0c;下载了一个gpuz查看了一下 了解了一下gf116是550ti或者gts450.我到挺希望他是550ti的。 很坑

【STM32F407+CUBEMX+FreeRTOS+lwIP netconn UDP TCP记录】

STM32F407CUBEMXFreeRTOSlwIP netconn UDP TCP记录 注意UDPUDP1UDP2 TCPTCP clientTCP server图片 注意 1、超时 #include “lwipopts.h” #define LWIP_SO_RCVTIMEO 12、先保证能ping通 3、关于工程创建可参考 【STM32F407CUBEMXFreeRTOSlwIP之UDP记录】 4、…

NLP 笔记:TF-IDF

TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff0c;词频-逆文档频率&#xff09;是一种用于信息检索和文本挖掘的统计方法&#xff0c;用来评估一个词在一组文档中的重要性。TF-IDF的基本思想是&#xff0c;如果某个词在一篇文档中出现频率高&#xff0…

使用Python的Tkinter库创建你的第一个桌面应用程序

文章目录 准备工作创建窗口和按钮代码解释运行你的应用程序结论 在本教程中&#xff0c;我们将介绍如何使用Python的Tkinter库创建一个简单的桌面应用程序。我们将会创建一个包含一个按钮的窗口&#xff0c;点击按钮时会在窗口上显示一条消息。 准备工作 首先&#xff0c;确保…

图片懒加载:提升网页性能的秘诀

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Python-Socket编程实现tcp-udp通信

本文章是记录我准备大创项目时学的socket编程的用法&#xff0c;纯属记录生活&#xff0c;没有教学意义&#xff0c;视频我是看b站up主王铭东学的&#xff0c;讲的很详细&#xff0c;我只粗略学了个大概&#xff0c;我想要通过tcp&#xff0c;udp传输yolo目标检测中的物体坐标信…

QT:按钮类控件

文章目录 PushButton快捷键Radio Buttion PushButton 这个类继承自QAbstractButton&#xff0c;是所有按钮的父类 创建一个带有图标的按钮&#xff1a; 假设这个图片是这个 那么我们就可以创建按钮并进行设置了&#xff1a; #include "widget.h" #include "u…

net lambda 、 匿名函数 以及集合(实现IEnumerable的 如数组 、list等)

匿名函数&#xff1a;》》》 Action a1 delegate(int i) { Console.WriteLine(i); }; Lambda:>>> Aciont a1 (int i) > { Console.WriteLine(i); }; 可以简写 &#xff08;编译器会自动根据委托类型 推断&#xff09; Action a1 &#xff08;i&#xff09;> {…