CloudCompare二次开发之如何通过PCL进行点云配准?

news2025/1/18 3:42:38

文章目录

  • 0.引言
  • 1.CloudCompare界面设计配准(registrate)按钮
  • 2.ICP配准(ICP_Reg)
  • 3.多幅点云逐步配准(Many_Reg)

0.引言

  因笔者课题涉及点云处理,需要通过PCL进行点云数据一系列处理分析,查阅现有网络资料,对常用PCL点云配准进行代码实现,本文记录配准实现过程。

1.CloudCompare界面设计配准(registrate)按钮

  (1)设计.ui文件
  ①设计按钮
  在这里插入图片描述

  ②编译.ui
  在这里插入图片描述

  (2)修改mainwindow.h文件
  在这里插入图片描述

//点云配准
void doActionPCLICP_Reg(); // ICP配准  
void doActionPCLMany_Reg(); // 多幅点云逐步配准

  (3)修改mainwindow.cpp文件
  ①添加头文件
  在这里插入图片描述

#include <pcl/registration/icp.h>   // ICP配准类相关的头文件
#include <pcl/registration/icp_nl.h>  
#include <pcl/registration/transforms.h>  
#include <pcl/registration/ndt.h>   //NDT配准类对应头文件  
#include <pcl/features/normal_3d.h>

  ②添加实现代码
  在这里插入图片描述

//ICP配准
void MainWindow::doActionPCLICP_Reg()  
{  
}  
//多幅点云逐步配准  
void MainWindow::doActionPCLMany_Reg()  
{  
}

  ③添加信号槽函数
  在这里插入图片描述

connect(m_UI->actionICP_Reg, &amp;QAction::triggered, this, &amp;MainWindow::doActionPCLICP_Reg);//ICP配准
connect(m_UI->actionMany_Reg, &amp;QAction::triggered, this, &amp;MainWindow::doActionPCLMany_Reg);//多幅点云逐步配准

  (4)生成
  在这里插入图片描述

2.ICP配准(ICP_Reg)

  (1)实现代码

//ICP配准
void MainWindow::doActionPCLICP_Reg()  
{  
    if (getSelectedEntities().size() != 2)  
    {  
        ccLog::Print(QStringLiteral("需选择两个点云实体"));  
    return;  
    }  
    ccHObject* entity1 = getSelectedEntities()[0];  
    ccHObject* entity2 = getSelectedEntities()[1];  
    ccPointCloud* ccCloud1 = ccHObjectCaster::ToPointCloud(entity1);  
    ccPointCloud* ccCloud2 = ccHObjectCaster::ToPointCloud(entity2);  
    // ---------------------------读取数据到PCL----------------------------------  
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud1(new pcl::PointCloud<pcl::PointXYZ>);  
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud2(new pcl::PointCloud<pcl::PointXYZ>);  
    cloud1->resize(ccCloud1->size());  
    cloud2->resize(ccCloud2->size());  
    for (int i = 0; i < cloud1->size(); ++i)  
    {  
        const CCVector3* point = ccCloud1->getPoint(i);  
        cloud1->points[i].x = point->x;  
        cloud1->points[i].y = point->y;  
        cloud1->points[i].z = point->z;  
    }  
    for (int i = 0; i < cloud2->size(); ++i)  
    {  
        const CCVector3* point = ccCloud2->getPoint(i);  
        cloud2->points[i].x = point->x;  
        cloud2->points[i].y = point->y;  
        cloud2->points[i].z = point->z;  
    }  
    // ----------------------------ICP配准--------------------------------------  
    pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;  
    icp.setInputCloud(cloud1);  
    icp.setInputTarget(cloud2);  
    pcl::PointCloud<pcl::PointXYZ>::Ptr filtered(new pcl::PointCloud<pcl::PointXYZ>);  
    icp.align(*filtered);  
    // ------------------------PCL->CloudCompare--------------------------------  
    if (!filtered->empty())  
    {  
        ccPointCloud* newPointCloud = new ccPointCloud(QString(ccCloud2->getName()+".registered"));  
        for (int i = 0; i < filtered->size(); ++i)  
        {  
            double x = filtered->points[i].x;  
            double y = filtered->points[i].y;  
            double z = filtered->points[i].z;  
            newPointCloud->addPoint(CCVector3(x, y, z));  
        }  
        newPointCloud->setRGBColor(ccColor::Rgba(255, 255, 255, 255));  
        newPointCloud->showColors(true);  
        ccCloud2->setEnabled(false);  
        //创建一个文件夹来放点云  
        ccHObject* CloudGroup = new ccHObject(QString("RegisteredGroup"));  
        CloudGroup->addChild(newPointCloud);  
        m_ccRoot->addElement(CloudGroup);  
        addToDB(newPointCloud);  
        refreshAll();  
        updateUI();  
        QString str = QStringLiteral("变换矩阵:\n");  
        Eigen::Matrix4f m = icp.getFinalTransformation();  
        float* arr = m.data();  
        for (size_t i = 0; i < 16; i++)  
        {  
            str += QString::number(arr[i],'f',6);  
            if ((i+1) % 4 == 0) {  
                str += "\n";  
            }  
            else {  
                str += ",";  
            }  
        }  
        dispToConsole(str, ccMainAppInterface::STD_CONSOLE_MESSAGE);  
    }  
    else  
    {  
        ccCloud2->setEnabled(true);  
        // Display a warning message in the console  
        dispToConsole("Warning: example shouldn't be used as is", ccMainAppInterface::WRN_CONSOLE_MESSAGE);  
    }  
}

  (2)配准结果
  ①采样前
  在这里插入图片描述

  ②配准后
  在这里插入图片描述

3.多幅点云逐步配准(Many_Reg)

  (1)实现代码

typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloud;  
typedef pcl::PointNormal PointNormalT;  
typedef pcl::PointCloud<PointNormalT> PointCloudWithNormals;  
  
/* 以< x, y, z, curvature >形式定义一个新的点 */  
class MyPointRepresentation : public pcl::PointRepresentation <PointNormalT>  
{  
    using pcl::PointRepresentation<PointNormalT>::nr_dimensions_;  
public:  
    MyPointRepresentation()  
    {  
        nr_dimensions_ = 4;   //定义尺寸值  
    }  
    /* 覆盖copyToFloatArray方法来定义我们的特征矢量 */  
    virtual void copyToFloatArray(const PointNormalT &amp;p, float * out) const  
    {  
        /* < x, y, z, curvature > */  
        out[0] = p.x;  
        out[1] = p.y;  
        out[2] = p.z;  
        out[3] = p.curvature;  
    }  
};  
  
/**匹配一对点云数据集并且返回结果  
*cloud_src:源点云  
*cloud_tgt:目标点云  
*output:输出的配准结果的源点云  
*final_transform:源点云和目标点云之间的转换矩阵  
*/  
void pairAlign(const PointCloud::Ptr cloud_src, const PointCloud::Ptr cloud_tgt, PointCloud::Ptr output,Eigen::Matrix4f &amp;final_transform, bool downsample = false)  
{  
    /* 下采样 */  
    PointCloud::Ptr src(new PointCloud);  
    PointCloud::Ptr tgt(new PointCloud);  
    pcl::VoxelGrid<PointT> grid;  
    if (downsample)  
    {  
        grid.setLeafSize(0.05, 0.05, 0.05);  
        grid.setInputCloud(cloud_src);  
        grid.filter(*src);  
        grid.setInputCloud(cloud_tgt);  
        grid.filter(*tgt);  
    }  
    else  
    {  
        src = cloud_src;  
        tgt = cloud_tgt;  
    }  
    /* 计算曲面法线和曲率 */  
    PointCloudWithNormals::Ptr points_with_normals_src(new PointCloudWithNormals);  
    PointCloudWithNormals::Ptr points_with_normals_tgt(new PointCloudWithNormals);  
    pcl::NormalEstimation<PointT, PointNormalT> norm_est;  
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());  
    norm_est.setSearchMethod(tree);  
    norm_est.setKSearch(30);  
    norm_est.setInputCloud(src);  
    norm_est.compute(*points_with_normals_src);  
    pcl::copyPointCloud(*src, *points_with_normals_src);  
    norm_est.setInputCloud(tgt);  
    norm_est.compute(*points_with_normals_tgt);  
    pcl::copyPointCloud(*tgt, *points_with_normals_tgt);  
    MyPointRepresentation point_representation;  
    //调整'curvature'尺寸权重以便使它和x, y, z平衡  
    float alpha[4] = { 1.0, 1.0, 1.0, 1.0 };  
    point_representation.setRescaleValues(alpha);  
    /* 配准 */  
    pcl::IterativeClosestPointNonLinear<PointNormalT, PointNormalT> reg;  
    reg.setTransformationEpsilon(1e-6);  
    reg.setMaxCorrespondenceDistance(0.1); //将两个对应关系之间的(src<->tgt)最大距离设置为10厘米,需要根据数据集大小来调整  
    reg.setPointRepresentation(boost::make_shared<const MyPointRepresentation>(point_representation));  //设置点表示  
    reg.setInputCloud(points_with_normals_src);  
    reg.setInputTarget(points_with_normals_tgt);  
    /* 在一个循环中运行相同的最优化并且使结果可视化 */  
    Eigen::Matrix4f Ti = Eigen::Matrix4f::Identity(), prev, targetToSource;  
    PointCloudWithNormals::Ptr reg_result = points_with_normals_src;  
    reg.setMaximumIterations(2);  
    for (int i = 0; i < 30; ++i)  
    {  
        //PCL_INFO("Iteration Nr. %d.\n", i);  
        points_with_normals_src = reg_result;         //为了可视化的目的保存点云  
        reg.setInputCloud(points_with_normals_src);  
        reg.align(*reg_result);  
        Ti = reg.getFinalTransformation() * Ti;      //在每一个迭代之间累积转换  
    /* 如果这次转换和之前转换之间的差异小于阈值,则通过减小最大对应距离来改善程序 */  
    if (fabs((reg.getLastIncrementalTransformation() - prev).sum()) < reg.getTransformationEpsilon())  
    {  
        reg.setMaxCorrespondenceDistance(reg.getMaxCorrespondenceDistance() - 0.001);  
        prev = reg.getLastIncrementalTransformation();  
    }  
    }  
    targetToSource = Ti.inverse();  //得到目标点云到源点云的变换  
    /* 把目标点云转换回源框架 */  
    pcl::transformPointCloud(*cloud_tgt, *output, targetToSource);  
    /* 添加源点云到转换目标 */  
    *output += *cloud_src;  
    final_transform = targetToSource;  
}  
  
//多幅点云逐步配准  
void MainWindow::doActionPCLMany_Reg()  
{  
    if (getSelectedEntities().size() < 2)  
    {  
        ccLog::Print(QStringLiteral("需至少选择两个点云实体"));  
    return;  
    }  
    //加个进度条  
    ccProgressDialog pDlg(true, 0);  
    CCLib::GenericProgressCallback* progressCb = &amp;pDlg;  
    if (progressCb) {  
        if (progressCb->textCanBeEdited()) {  
            progressCb->setMethodTitle("compute");  
            progressCb->setInfo(qPrintable(QString("waiting...")));  
        }  
        progressCb->update(0);  
        progressCb->start();  
    }  
    PointCloud::Ptr result(new PointCloud), source, target;  
    Eigen::Matrix4f GlobalTransform = Eigen::Matrix4f::Identity(), pairTransform;  
    for (size_t i = 1; i < getSelectedEntities().size(); ++i)  
    {  
        ccHObject* entity1 = getSelectedEntities()[i-1];  
        ccHObject* entity2 = getSelectedEntities()[i];  
        ccPointCloud* ccCloud1 = ccHObjectCaster::ToPointCloud(entity1);  
        ccPointCloud* ccCloud2 = ccHObjectCaster::ToPointCloud(entity2);  
        // ---------------------------读取数据到PCL----------------------------------  
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud1(new pcl::PointCloud<pcl::PointXYZ>);  
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud2(new pcl::PointCloud<pcl::PointXYZ>);  
        cloud1->resize(ccCloud1->size());  
        cloud2->resize(ccCloud2->size());  
        for (int i = 0; i < cloud1->size(); ++i)  
        {  
            const CCVector3* point = ccCloud1->getPoint(i);  
            cloud1->points[i].x = point->x;  
            cloud1->points[i].y = point->y;  
            cloud1->points[i].z = point->z;  
        }  
        for (int i = 0; i < cloud2->size(); ++i)  
        {  
            const CCVector3* point = ccCloud2->getPoint(i);  
            cloud2->points[i].x = point->x;  
            cloud2->points[i].y = point->y;  
            cloud2->points[i].z = point->z;  
        }  
        source = cloud1;  
        target = cloud2;  
    /* 添加可视化数据 */  
    PointCloud::Ptr temp(new PointCloud);  
    pairAlign(source, target, temp, pairTransform, true);  
    pcl::transformPointCloud(*temp, *result, GlobalTransform); //把当前的两两配对转换到全局变换  
    GlobalTransform = pairTransform * GlobalTransform;          //更新全局变换  
    ccCloud1->setEnabled(false);  
    ccCloud2->setEnabled(false);  
    progressCb->update((float)100.0*i / getSelectedEntities().size());  
    }  
    if (progressCb) {  
        progressCb->stop();  
    }  
    // ------------------------PCL->CloudCompare--------------------------------  
    if (!result->empty())  
    {  
        ccPointCloud* newPointCloud = new ccPointCloud(QString("FinalRegistered"));  
        for (int i = 0; i < result->size(); ++i)  
        {  
            double x = result->points[i].x;  
            double y = result->points[i].y;  
            double z = result->points[i].z;  
            newPointCloud->addPoint(CCVector3(x, y, z));  
        }  
        newPointCloud->setRGBColor(ccColor::Rgba(255, 255, 255, 255));  
        newPointCloud->showColors(true);  
        //创建一个文件夹来放点云  
        ccHObject* CloudGroup = new ccHObject(QString("RegisteredGroup"));  
        CloudGroup->addChild(newPointCloud);  
        m_ccRoot->addElement(CloudGroup);  
        addToDB(newPointCloud);  
        refreshAll();  
        updateUI();  
        QString str = QStringLiteral("最终的全局变换矩阵:\n");  
        Eigen::Matrix4f m = GlobalTransform;  
        float* arr = m.data();  
        for (size_t i = 0; i < 16; i++)  
        {  
            str += QString::number(arr[i], 'f', 6);  
            if ((i + 1) % 4 == 0) {  
                str += "\n";  
            }  
            else {  
                str += ",";  
            }  
        }  
        dispToConsole(str, ccMainAppInterface::STD_CONSOLE_MESSAGE);  
    }  
    else  
    {  
        //ccCloud2->setEnabled(true);  
        // Display a warning message in the console  
        dispToConsole("Warning: example shouldn't be used as is", ccMainAppInterface::WRN_CONSOLE_MESSAGE);  
    }  
}

  (2)配准结果
  ①配准前
  在这里插入图片描述

  ②配准后
  在这里插入图片描述

参考资料:
[1] 来吧!我在未来等你!. CloudCompare二次开发之如何配置PCL点云库?; 2023-05-15 [accessed 2023-05-16].
[2] Roar冷颜. PCL中点云配准(非常详细,建议收藏); 2021-10-20 [accessed 2023-05-16].
[3] MSTIFIY. 点云配准(PCL+ICP); 2022-07-25 [accessed 2023-05-16].
[4] 滑了丝的螺丝钉. PCL点云配准官方教程; 2020-06-08 [accessed 2023-05-16].
[5] 悠缘之空. PCL函数库摘要——点云配准; 2021-11-07 [accessed 2023-05-16].
[6] 令狐少侠、. 3D点云系列——pcl:点云平滑及法线估计; 2022-04-05 [accessed 2023-05-16].

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

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

相关文章

ChatgGPT生成Excel统计公式

需求背景 编写excel公式&#xff0c;提取下图中符号之前的内容并填充到“修改后的内容”这一列 流程思路 借助ChatGPT完成Excel公式的大致流程如下&#xff1a; 确定要解决的问题&#xff1a;明确你需要在Excel中实现的具体任务或计算需求。例如&#xff0c;求和、平均值、…

优思学院|用ChatGPT人工智能制作FMEA可以吗?

问题和缺陷是昂贵的&#xff0c;它们是质量成本的主要构成部分。同时&#xff0c;顾客可以对制造商和服务提供商抱有很高的期望&#xff0c;希望他们提供高质量和高可靠性。 通常&#xff0c;很多企业只会在产品和服务的开发后期&#xff0c;通过广泛的测试和檢查来发现问题。…

提高 Maya 渲染质量和速度的4个小技巧

Autodesk Maya&#xff0c;通常简称为Maya&#xff0c;是一种3D计算机图形应用程序&#xff0c;可在Windows、macOS和Linux上运行&#xff0c;最初由Alias开发&#xff0c;目前由Autodesk拥有和开发。它用于为交互式3D应用程序、动画电影、电视剧和视觉效果创建资产。 您可以通…

以 29K 成功入职字节跳动,这份《 软件测试面试笔记》让我受益匪浅

朋友入职已经两周了&#xff0c;整体工作环境还是非常满意的&#xff01;所以这次特意抽空给我写出了这份面试题&#xff0c;而我把它分享给小伙伴们&#xff0c;面试&入职的经验&#xff01; 大概是在3月中的时候他告诉我投递了简历&#xff0c;4月的时候经过了3轮面试收…

【C++】4. 类和对象终章

专栏导读 &#x1f341;作者简介&#xff1a;余悸&#xff0c;在读本科生一枚&#xff0c;致力于 C方向学习。 &#x1f341;收录于 C专栏&#xff0c;本专栏主要内容为 C初阶、 C 进阶、STL 详解等&#xff0c;持续更新中&#xff01; &#x1f341;相关专栏推荐&#xff1a; …

做F牌独立站要做好功课,拒绝被割韭菜!

做过爆品独立站的朋友们都知道&#xff0c;遇到爆品不容易&#xff0c;很多都具有滞后性&#xff0c;都是当你发现了之后&#xff0c;这个帖子/视频/产品已经被人跑烂了&#xff0c;你再去跑&#xff0c;这样只会浪费大量的广告费。既然爆品独立站的广告费烧不过大卖&#xff0…

知识图谱学习笔记——(五)知识图谱推理

一、知识学习 声明&#xff1a;知识学习中本文主体按照浙江大学陈华钧教授的《知识图谱》公开课讲义进行介绍&#xff0c;并个别地方加入了自己的注释和思考&#xff0c;希望大家尊重陈华钧教授的知识产权&#xff0c;在使用时加上出处。感谢陈华钧教授。 &#xff08;一&…

Java配置方式使用Spring MVC:实战练习

文章目录 续写任务1、创建登录页面、登录成功与登录失败页面1、创建登录页面2、创建登录成功页面3、创建登录失败页面 任务2、首页添加登录链接&#xff0c;单击可跳转到登录页面1、 修改首页&#xff0c;添加超链接2、修改Spring MVC配置类&#xff0c;定义视图控制器3、创建登…

Spark - 创建 _SUCCESS 文件与获取最新可用文件

目录 一.引言 二.增加 _SUCCESS 标识 1.SparkContext 生成 2.FileSystem 生成 3.Hadoop 生成 三.获取最新文件 1.获取 SparkContext 2.按照时间排序 3.遍历生成 Input 四.总结 一.引言 有任务需要每小时生成多个 split 文件分片&#xff0c;为了保证线上任务读取最新…

Linux实操篇---常用的基本命令5(进程管理类和crontab系统定时任务)

一、进程管理类 进程是正在执行的一个程序或命令&#xff0c;每一个进程都是一个运行的实体&#xff0c;都有自己的地址空间&#xff0c;并占用一定的系统资源。 守护进程和系统服务就是一一对应的关系。 有系统级别的进程和用户级别的进程。 进程管理&#xff1a;所有的进…

如何使用自定义知识库构建自定义ChatGPT机器人

目录 隐藏 使用自定义数据源为您的 ChatGPT 机器人提供数据 1. 通过Prompt提示工程提供数据 2. 使用 LlamaIndex&#xff08;GPT 索引&#xff09;扩展 ChatGPT 如何添加自定义数据源 先决条件 怎么运行的 最后的总结 使用自定义数据源为您的 ChatGPT 机器人提供数据…

rt-thread启动流程

资料下载 RT-Thread Simulator 例程 操作流程 将上面的仿真例程下载并解压&#xff0c;通过MDK打开&#xff0c;编译&#xff0c;调试&#xff0c;并打开串口点击运行&#xff0c;就可以看到如下输出了&#xff1a; 添加自己的 thread&#xff1a;在main()函数中添加即可&am…

[Java基础]基本概念(上)(标识符,关键字,基本数据类型)

hello 大家好&#xff0c;计算机语言各有不同&#xff0c;但本质上都是操作内存和计算。这章的内容是介绍Java中的基本概念展开&#xff0c;包括&#xff1a;标识符&#xff0c;关键字&#xff0c;Java基本数据类型&#xff0c;运算符&#xff0c;表达式和语句&#xff0c;分支…

前端架构师-week6-require源码解析

require 源码解析——彻底搞懂 npm 模块加载原理 require 的使用场景 加载模块类型 加载内置模块&#xff1a;require(fs)加载 node_modules 模块&#xff1a;require(ejs)加载本地模块&#xff1a;require(./utils)支持文件类型 加载 .js 文件加载 .mjs 文件加载 .json 文件…

AI女友同时和1000人谈恋爱,狂赚500万

AI女友&#xff0c;预计暴赚4亿 要说当下什么最火&#xff0c;AI首当其冲无可置疑。00后网络红人红卡琳玛乔丽&#xff08;Caryn Marjorie&#xff09;最近与Forever Voices公司合作&#xff0c;通过视频训练等方式打造出个人形象、声音和性格的AI虚拟女友&#xff0c;就像在和…

Redis高可用--持久化

在Web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准实在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。 但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#xff0c;除了保证提提供正常服务&a…

大疆无人机 MobileSDK(遥控器/手机端)开发 v4版<3>

导读 第三篇文章准备单独拿出来写,因为在大疆为人机的所有功能中,航线规划的功能最为复杂,也相当的繁琐,这里需要说仔细一点,可能会将代码进行多步分解。 航线规划 1)航线打点 点击 按钮进行打点,在地图中手动选择点位选择完成后点击**[完成]**按钮,即可完成航线打点…

新展预告 | YT U LOVE——许峰个展即将亮相!

深圳东方美术馆荣幸地宣布&#xff0c;将于5月20日呈现艺术家许峰在鹏城的首次个展“YT U LOVE”&#xff0c;展出艺术家从2020年至2023年创作的油画、纸本及雕塑40余件作品。此次展览以“YT U LOVE”为题&#xff0c;恰逢兔年&#xff0c;yutu在中国意指玉兔&#xff0c;前后两…

美创科技首家互联网医院数据安全建设案例实践

互联网医院作为医疗服务模式创新发展的新产物&#xff0c;在各项配套政策支持下快速发展。然而&#xff0c;蓬勃之势下&#xff0c;无数双“暗夜之手”也在蠢蠢欲动&#xff0c;试图从中渔利&#xff0c;关乎患者隐私、种类繁多的医疗数据迎来愈加严峻的安全挑战。 某市中心医院…

劳有所学|文献可视化分析工具CiteSpace、vosviewer使用指南

【基于Citespace和vosviewer文献计量学相关论文 】 专题一&#xff1a;文献计量学方法与应用 1 文献计量学方法基本介绍 2 与其他综述方法区别联系 3 各学科领域应用趋势近况 4 主流分析软件优缺点对比 5 经典高分10SCI思路复盘 6 软件安装与Java环境配置 专题二&#…