4.点云数据的配准

1.点云配准ICP(Iterative Closest Point)算法

点云配准的原理及ICP(Iterative Closest Point)算法原理参照博客【PCL】—— 点云配准ICP(Iterative Closest Point)算法_icp点云配准-CSDN博客。

(1)点云配准原理:三维扫描仪设备对目标物体一次扫描往往只能获取到局部的点云信息(如物体背面无法一次扫描到),因此需要进行多次扫描,而每次扫描时得到的点云都有独立的坐标系,不可以直接进行拼接。因此要得到目标物体的完整点云就需要对多个局部点云进行配准。为了得到被测物体的完整数据模型,需要确定一个合适的坐标变换 ,将从各个视角得到的点集合并到一个统一的坐标系下形成一个完整的数据点云,然后就可以方便地进行可视化等操作,这就是点云数据的配准。

        点云配准步骤上可以分为粗配准(Coarse Registration)和精配准(Fine Registration)两个阶段。

        粗配准是指在点云相对位姿完全未知的情况下对点云进行配准,找到一个可以让两块点云相对近似的旋转平移变换矩阵,进而将待配准点云数据转换到统一的坐标系内,可以为精配准提供良好的初始值。

​     精配准是指在粗配准的基础上,让点云之间的空间位置差异最小化,得到一个更加精准的旋转平移变换矩阵。该算法的运行速度以及向全局最优化的收敛性却在很大程度上依赖于给定的初始变换估计以及在迭代过程中对应关系的确立。所以需要各种粗配准技术为ICP算法提供较好的位置,在迭代过程中确立正确对应点集能避免迭代陷入局部极值,决定了算法的收敛速度和最终的配准精度。

​     比较常见的一种配准算法是迭代最近点算法(Iterative Closest Point, ICP)。ICP算法可以基于四元数求解,也可以基于奇异值分解(SVD)求解,本文主要介绍基于奇异值分解的ICP算法。

(2)ICP原理

 的

        给定一个参考点集P和一个数据点集Q(在给定的初始估计R,t),算法为Q中的每个点寻找P中对应的最近点,形成匹配点对。然后,将所有匹配点对的欧氏距离之和作为待求解的目标函数,利用奇异值分解求出R和t以使目标函数最小,根据R, t 转换得到新的'Q ′并再次找到对应的点对,如此迭代。

缺点:需要剔除噪声点(距离过大的点对或包含边界点的点对)。基于点对的配准不包括局部形状信息。在每次迭代中搜索最近点非常耗时。计算可能陷入局部最优。

​     一般来说,ICP可以分为以下四个阶段

  1. 对原始点云数据进行采样
  2. 确定初始对应点集
  3. 去除错误对应点集
  4. 坐标变换的求解

数学原理

问题定义

案例实现

官方教程——How to use iterative closest point — Point Cloud Library 0.0 documentation

(3)ICP配准代码如下:

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/registration/icp.h>int
mains() {// 定义输入和输出点云pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in(new pcl::PointCloud<pcl::PointXYZ>);pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_out(new pcl::PointCloud<pcl::PointXYZ>);// 随机填充无序点云cloud_in->width = 5;cloud_in->height = 1;cloud_in->is_dense = false;cloud_in->points.resize(cloud_in->width * cloud_in->height);for (size_t i = 0; i < cloud_in->points.size(); ++i) {cloud_in->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);cloud_in->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);cloud_in->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);}std::cout << "Saved " << cloud_in->points.size() << " data points to input:"<< std::endl;for (size_t i = 0; i < cloud_in->points.size(); ++i)std::cout << "    " <<cloud_in->points[i].x << " " << cloud_in->points[i].y << " " <<cloud_in->points[i].z << std::endl;*cloud_out = *cloud_in;std::cout << "size:" << cloud_out->points.size() << std::endl;// 在点云上执行简单的刚性变换,将cloud_out中的x平移0.7f米,然后再次输出数据值。for (size_t i = 0; i < cloud_in->points.size(); ++i)cloud_out->points[i].x = cloud_in->points[i].x + 0.7f;// 打印这些点std::cout << "Transformed " << cloud_in->points.size() << " data points:"<< std::endl;for (size_t i = 0; i < cloud_out->points.size(); ++i)std::cout << "    " << cloud_out->points[i].x << " " <<cloud_out->points[i].y << " " << cloud_out->points[i].z << std::endl;// 创建IterativeClosestPoint的实例// setInputSource将cloud_in作为输入点云// setInputTarget将平移后的cloud_out作为目标点云pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;icp.setInputSource(cloud_in);icp.setInputTarget(cloud_out);// 创建一个 pcl::PointCloud<pcl::PointXYZ>实例 Final 对象,存储配准变换后的源点云,// 应用 ICP 算法后, IterativeClosestPoint 能够保存结果点云集,如果这两个点云匹配正确的话// (即仅对其中一个应用某种刚体变换,就可以得到两个在同一坐标系下相同的点云),那么 icp. hasConverged()= 1 (true),// 然后会输出最终变换矩阵的匹配分数和变换矩阵等信息。pcl::PointCloud<pcl::PointXYZ> Final;icp.align(Final);std::cout << "has converged:" << icp.hasConverged() << " score: " <<icp.getFitnessScore() << std::endl;const pcl::Registration<pcl::PointXYZ, pcl::PointXYZ, float>::Matrix4& matrix = icp.getFinalTransformation();std::cout << matrix << std::endl;return (0);
}

自己的想法:点云配准是为了获取坐标转换的一个4维矩阵(旋转矩阵加平移向量),配准所有点云数据增加了计算量,因此可以先去噪,再分割出目标,再找目标的关键点,对关键点进行配准。

交互式ICP配准
官方教程——https://pcl.readthedocs.io/projects/tutorials/en/latest/interactive_icp.html

该程序将加载点云并对其施加刚性变换。 之后,ICP算法会将变换后的点云与原始对齐。 每次用户按下“空格”,都会进行一次ICP迭代,并刷新查看器。

代码如下:

#include <string>
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/point_types.h>
#include <pcl/registration/icp.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/console/time.h>   // TicToctypedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloudT;bool next_iteration = false;void
print4x4Matrix (const Eigen::Matrix4d & matrix)
{printf ("Rotation matrix :\n");printf ("    | %6.3f %6.3f %6.3f | \n", matrix (0, 0), matrix (0, 1), matrix (0, 2));printf ("R = | %6.3f %6.3f %6.3f | \n", matrix (1, 0), matrix (1, 1), matrix (1, 2));printf ("    | %6.3f %6.3f %6.3f | \n", matrix (2, 0), matrix (2, 1), matrix (2, 2));printf ("Translation vector :\n");printf ("t = < %6.3f, %6.3f, %6.3f >\n\n", matrix (0, 3), matrix (1, 3), matrix (2, 3));
}
/*** 此函数是查看器的回调。 当查看器窗口位于顶部时,只要按任意键,就会调用此函数。 如果碰到“空格”; 将布尔值设置为true。* @param event* @param nothing*/
void
keyboardEventOccurred (const pcl::visualization::KeyboardEvent& event,void* nothing)
{if (event.getKeySym () == "space" && event.keyDown ())next_iteration = true;
}int
main (int argc,char* argv[])
{// The point clouds we will be usingPointCloudT::Ptr cloud_in (new PointCloudT);  // Original point cloudPointCloudT::Ptr cloud_tr (new PointCloudT);  // Transformed point cloudPointCloudT::Ptr cloud_icp (new PointCloudT);  // ICP output point cloud//    我们检查程序的参数,设置初始ICP迭代的次数,然后尝试加载PCD文件。// Checking program argumentsif (argc < 2){printf ("Usage :\n");printf ("\t\t%s file.pcd number_of_ICP_iterations\n", argv[0]);PCL_ERROR ("Provide one pcd file.\n");return (-1);}int iterations = 1;  // Default number of ICP iterationsif (argc > 2){// If the user passed the number of iteration as an argumentiterations = atoi (argv[2]);if (iterations < 1){PCL_ERROR ("Number of initial iterations must be >= 1\n");return (-1);}}pcl::console::TicToc time;time.tic ();if (pcl::io::loadPCDFile (argv[1], *cloud_in) < 0){PCL_ERROR ("Error loading cloud %s.\n", argv[1]);return (-1);}std::cout << "\nLoaded file " << argv[1] << " (" << cloud_in->size () << " points) in " << time.toc () << " ms\n" << std::endl;// 我们使用刚性矩阵变换来变换原始点云。// cloud_in包含原始点云。// cloud_tr和cloud_icp包含平移/旋转的点云。// cloud_tr是我们将用于显示的备份(绿点云)。// Defining a rotation matrix and translation vectorEigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity ();// A rotation matrix (see https://en.wikipedia.org/wiki/Rotation_matrix)double theta = M_PI / 8;  // The angle of rotation in radianstransformation_matrix (0, 0) = std::cos (theta);transformation_matrix (0, 1) = -sin (theta);transformation_matrix (1, 0) = sin (theta);transformation_matrix (1, 1) = std::cos (theta);// A translation on Z axis (0.4 meters)transformation_matrix (2, 3) = 0.4;// Display in terminal the transformation matrixstd::cout << "Applying this rigid transformation to: cloud_in -> cloud_icp" << std::endl;print4x4Matrix (transformation_matrix);// Executing the transformationpcl::transformPointCloud (*cloud_in, *cloud_icp, transformation_matrix);*cloud_tr = *cloud_icp;  // We backup cloud_icp into cloud_tr for later use// 这是ICP对象的创建。 我们设置ICP算法的参数。// setMaximumIterations(iterations)设置要执行的初始迭代次数(默认值为1)。// 然后,我们将点云转换为cloud_icp。 第一次对齐后,我们将在下一次使用该ICP对象时(当用户按下“空格”时)将ICP最大迭代次数设置为1。// The Iterative Closest Point algorithmtime.tic ();pcl::IterativeClosestPoint<PointT, PointT> icp;icp.setMaximumIterations (iterations);icp.setInputSource (cloud_icp);icp.setInputTarget (cloud_in);icp.align (*cloud_icp);icp.setMaximumIterations (1);  // We set this variable to 1 for the next time we will call .align () functionstd::cout << "Applied " << iterations << " ICP iteration(s) in " << time.toc () << " ms" << std::endl;// 检查ICP算法是否收敛; 否则退出程序。 如果返回true,我们将转换矩阵存储在4x4矩阵中,然后打印刚性矩阵转换。if (icp.hasConverged ()){std::cout << "\nICP has converged, score is " << icp.getFitnessScore () << std::endl;std::cout << "\nICP transformation " << iterations << " : cloud_icp -> cloud_in" << std::endl;transformation_matrix = icp.getFinalTransformation ().cast<double>();print4x4Matrix (transformation_matrix);}else{PCL_ERROR ("\nICP has not converged.\n");return (-1);}// Visualizationpcl::visualization::PCLVisualizer viewer ("ICP demo");// Create two vertically separated viewportsint v1 (0);int v2 (1);viewer.createViewPort (0.0, 0.0, 0.5, 1.0, v1);viewer.createViewPort (0.5, 0.0, 1.0, 1.0, v2);// The color we will be usingfloat bckgr_gray_level = 0.0;  // Blackfloat txt_gray_lvl = 1.0 - bckgr_gray_level;// Original point cloud is whitepcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_in_color_h (cloud_in, (int) 255 * txt_gray_lvl, (int) 255 * txt_gray_lvl,(int) 255 * txt_gray_lvl);viewer.addPointCloud (cloud_in, cloud_in_color_h, "cloud_in_v1", v1);viewer.addPointCloud (cloud_in, cloud_in_color_h, "cloud_in_v2", v2);// Transformed point cloud is greenpcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_tr_color_h (cloud_tr, 20, 180, 20);viewer.addPointCloud (cloud_tr, cloud_tr_color_h, "cloud_tr_v1", v1);// ICP aligned point cloud is redpcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_icp_color_h (cloud_icp, 180, 20, 20);viewer.addPointCloud (cloud_icp, cloud_icp_color_h, "cloud_icp_v2", v2);// Adding text descriptions in each viewportviewer.addText ("White: Original point cloud\nGreen: Matrix transformed point cloud", 10, 15, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "icp_info_1", v1);viewer.addText ("White: Original point cloud\nRed: ICP aligned point cloud", 10, 15, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "icp_info_2", v2);std::stringstream ss;ss << iterations;std::string iterations_cnt = "ICP iterations = " + ss.str ();viewer.addText (iterations_cnt, 10, 60, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "iterations_cnt", v2);// Set background colorviewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v1);viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v2);// Set camera position and orientationviewer.setCameraPosition (-3.68332, 2.94092, 5.71266, 0.289847, 0.921947, -0.256907, 0);viewer.setSize (1280, 1024);  // Visualiser window size// Register keyboard callback :viewer.registerKeyboardCallback (&keyboardEventOccurred, (void*) NULL);// Display the visualiserwhile (!viewer.wasStopped ()){viewer.spinOnce ();// The user pressed "space" :if (next_iteration){// The Iterative Closest Point algorithmtime.tic ();// 如果用户按下键盘上的任意键,则会调用keyboardEventOccurred函数。 此功能检查键是否为“空格”。// 如果是,则全局布尔值next_iteration设置为true,从而允许查看器循环输入代码的下一部分:调用ICP对象以进行对齐。// 记住,我们已经配置了该对象输入/输出云,并且之前通过setMaximumIterations将最大迭代次数设置为1。icp.align (*cloud_icp);std::cout << "Applied 1 ICP iteration in " << time.toc () << " ms" << std::endl;// 和以前一样,我们检查ICP是否收敛,如果不收敛,则退出程序。if (icp.hasConverged ()){// printf(“ 033 [11A”); 在终端增加11行以覆盖显示的最后一个矩阵是一个小技巧。// 简而言之,它允许替换文本而不是编写新行; 使输出更具可读性。 我们增加迭代次数以更新可视化器中的文本值。printf ("\033[11A");  // Go up 11 lines in terminal output.printf ("\nICP has converged, score is %+.0e\n", icp.getFitnessScore ());// 这意味着,如果您已经完成了10次迭代,则此函数返回矩阵以将点云从迭代10转换为11。std::cout << "\nICP transformation " << ++iterations << " : cloud_icp -> cloud_in" << std::endl;// 函数getFinalTransformation()返回在迭代过程中完成的刚性矩阵转换(此处为1次迭代)。transformation_matrix *= icp.getFinalTransformation ().cast<double>();  // WARNING /!\ This is not accurate! For "educational" purpose only!print4x4Matrix (transformation_matrix);  // Print the transformation between original pose and current posess.str ("");ss << iterations;std::string iterations_cnt = "ICP iterations = " + ss.str ();viewer.updateText (iterations_cnt, 10, 60, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "iterations_cnt");viewer.updatePointCloud (cloud_icp, cloud_icp_color_h, "cloud_icp_v2");}else{PCL_ERROR ("\nICP has not converged.\n");return (-1);}//这不是我们想要的。 如果我们将最后一个矩阵与新矩阵相乘,那么结果就是从开始到当前迭代的转换矩阵。}next_iteration = false;}return (0);
}

2.

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

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

相关文章

Learn ComputeShader 02 Multiple kernels

前面已经成功创建了第一个compute shader&#xff0c;并且使用它替换掉quad的材质的纹理&#xff0c;现在我们将要在计算着色器中创建多个kernel。 首先调整上次的计算着色器&#xff0c;让它显示为红色。 然后再次创建一个kernel&#xff0c;显示为黄色。 结果应该是这样的…

mysql基础2——字段类型

整数类型 需要考虑存储空间和可靠性的平衡 浮点类型 浮点数类型不精准 将十进制数转换为二进制数存储 浮点数类型&#xff1a;float double real(默认是&#xff0c;double ) 如果需要将real设定为float &#xff0c;那么通过以下语句实现 set sql_mode "real_as…

52 文本预处理【动手学深度学习v2】

将文本作为字符串加载到内存中。 将字符串拆分为词元&#xff08;如单词和字符&#xff09;。 建立一个词表&#xff0c;将拆分的词元映射到数字索引;将文本转换为数字索引序列&#xff0c;方便模型操作。

我在本地部署通义千问Qwen1.5大模型,并实现简单的对话和RAG

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总…

WordPress 告别 MySQL:Docker SQLite WordPress

本篇文章聊聊&#xff0c;如何将这个持续诞生和维护了 21 年的开源软件“脱离数据库”运行&#xff0c;让它能够更加轻量、适合低成本离线运行。 写在前面 2003 年&#xff0c;Michel Valdrighi 基于 b2/cafelog 创建了开源软件 WordPress&#xff0c;并在 GPL 协议下发布。 …

(一)Java EE企业级应用开发实战之Servlet教程——JDK安装

首先打开清华大学开源软件镜像站&#xff0c;清华大学开源镜像网站地址为&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/ 打开该地址后的界面显示如下图所示 找到8版本对应的SDK安装包&#xff0c;我现在用的开发机器是Windows&#xff0c;所以我找的是Windows对应的版本…

[柏鹭杯 2021]试试大数据分解?

题目&#xff1a;&#xff08;NSSCTF | 在线CTF平台&#xff09; 题目就是如此&#xff0c;我没看到有5个不同的文本&#xff0c;其中最后一个文本以pem后缀&#xff0c;所以我们先来了解一下什么是pem格式。 PEM 格式 PEM格式通常用于数字证书认证机构&#xff08;Certifica…

EelasticSearch的介绍和基于docker安装

1.概述 Elasticsearch 是一个基于 Apache Lucene 构建的开源分布式搜索引擎和分析引擎。它专为云计算环境设计&#xff0c;提供了一个分布式的、高可用的实时分析和搜索平台。Elasticsearch 可以处理大量数据&#xff0c;并且具备横向扩展能力&#xff0c;能够通过增加更多的硬…

AES和RSA加解密算法学习笔记(实战版)

1. 写在前面 今天整理一篇有关密码学的学习笔记,原因是最近做的一个任务是在网络传输的时候,需要对传输的包进行加密和解密工作,以保证传输过程的安全性。所以,这个过程用到了AES和RSA两个算法。 场景:假设我要给我的老师传送毕设代码和论文, 我已经把代码和论文打成了一…

VSCode 配置 C/C++ 环境

1 安装 VSCode 直接去官网(https://code.visualstudio.com/)下载并安装即可。 2 配置C/C编译环境 方案一 如果是在Windows&#xff0c;需要安装 MingW&#xff0c;可以去官网(https://sourceforge.net/projects/mingw-w64/)下载安装包。 注意安装路径不要出现中文。 打开 w…

Ubuntu-18.04本地化部署Rustdesk服务器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、配置防火墙二、安装三大件1.下载三大件2.安装三大件 三、安装客户端1.下载客户端1.Windows2.Linux 四、配置客户端连接服务器五、总结 前言 如果你是想数据…

JEECG表格选中状态怎么去掉

官网代码&#xff08;在取消选中状态的时候不生效&#xff09; rowSelection() {return {onChange: (selectedRowKeys, selectedRows) > {console.log(selectedRowKeys: ${selectedRowKeys}, selectedRows: , selectedRows);},getCheckboxProps: record > ({props: {disa…

数据结构系列-堆排序

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 昨天我们实现的堆的搭建&#xff0c;我们今天实现以下堆的排序&#xff0c; 堆的排序的最大的优点就是提高的效率&#xff0c;减小了时间复杂度&#xff0c;在这个里面我们有一个…

面试十八、容器适配器

容器适配器是一种特殊类型的容器&#xff0c;它们提供了一种不同于常规容器的接口和行为。容器适配器通常是建立在其他容器之上&#xff0c;通过改变接口或添加限制来满足特定的需求或解决特定的问题。 在 C 中&#xff0c;标准库提供了三种常见的容器适配器&#xff1a; 栈&am…

Web3钱包开发获取测试币-Polygon Mumbai(一)

Web3钱包开发获取测试币-Polygon Mumbai(一) 由于主网区块链上的智能合约需要真正的代币&#xff0c;而部署和使用需要花费真金白银&#xff0c;因此测试网络为 Web3 开发人员提供了一个测试环境&#xff0c;用于部署和测试他们的智能合约&#xff0c;以识别和修复在将智能合约…

边缘渲染是什么意思?属于云渲染吗?

边缘渲染&#xff0c;即利用靠近数据源或用户的边缘计算资源进行渲染&#xff0c;以减少数据传输延迟并提升处理速度。这种方式虽然基于云技术&#xff0c;但更注重数据处理的地理位置优化&#xff0c;与传统的云渲染有所不同。 一、边缘渲染属于什么意思? 边缘渲染是一种基于…

周鸿祎和雷军、马化腾相逢一笑泯恩仇

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 马云竟然没有到场&#xff0c;真是遗憾! 前两天工信部和互联网协会联合举办的中国互联网三十周年座谈会上。周鸿祎、雷军、马化腾相逢一笑泯恩仇。 第一条视频&#xff1a; 周鸿祎和马化腾握手言欢&#xff0c…

一道有意思的面试题

面试过程中没有想到合适的数据结构&#xff0c;想了半天&#xff0c;构思没有实践落地。 也是听错了&#xff0c;以为三道题都要做&#xff0c;做出来了两道&#xff0c;这道死活没落地成功。 结果&#xff0c;三道题里面任选一道就行。。。。 好吧 题目&#xff1a;给定一个…

对接浦发银行支付(八)-- 对账接口

一、背景 本文不是要讲述支付服务的对账模块具体怎么做&#xff0c;仅是介绍如何对接浦发银行的对账接口。 也就是说&#xff0c;本文限读取到对账文件的内容&#xff0c;不会进一步去讲述如何与支付平台进行对账。 如果要获取商户的对账单&#xff0c;需要遵循以下步骤&…

【从浅学到熟知Linux】进程间通信之匿名管道方式(进程间通信方式汇总、匿名管道的创建、匿名管道实现进程池详解)

&#x1f3e0;关于专栏&#xff1a;Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程等内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 进程间通信介绍如何实现进程间通信进程间通信分类 管道通信方式什么是管道匿名管道pipe匿名管道读写规则管…