点云配准方法原理(NDT、ICP)

news2024/11/15 13:27:55

配准是点云处理中的一个基础问题,众多学者此问题进行了广泛而深入的研究,也出现了一系列优秀成熟的算法,在三维建模、自动驾驶等领域发挥着重要的作用。

本文主要介绍粗配准NDT (Normal Distribution Transform) 与 精配准ICP (Iterative Closest Point)两种经典算法。

NDT (Normal Distribution Transform)点云配准

在去年曾经对NDT原理进行了简单总结,大家可以参考链接:点云配准NDT (P2D)算法详解。

ICP (Iterative Closest Point)点云配准

参考:
基于SVD求解 3D-3D 点对匹配
该如何学习三维点云配准的相关知识?
ICP是经典的精配准算法,其以“点”为配准基元,不断迭代求得最优的位姿,最终使得目标函数最小。

点云集合

假设存在两个点云集合,
目标点云 (target cloud ): P = { p 1 , p 2 , p 3 , . . . , p n } P=\{p_1, p_2, p_3,..., p_n\} P={p1,p2,p3,...,pn}
在这里插入图片描述
源点云 (source cloud): Q = { q 1 , q 2 , q 3 , . . . , q n } Q=\{q_1, q_2, q_3,..., q_n\} Q={q1,q2,q3,...,qn}
在这里插入图片描述
两者叠加显示:
在这里插入图片描述
可以看到上图中的两帧点云之间存在一个偏差,这个偏差需要一个位姿变换 ( R , t ) (R, t) (R,t)进行转换。

构造目标函数

ICP算法则希望得到一个最优的位姿变换 ( R ∗ , t ∗ ) (R^*, t^*) (R,t)使得下式目标函数最小:
f ( R ∗ , t ∗ ) = M I N ( 1 N p ∑ i = 1 N p ∣ p i t − ( R ∗ q i s + t ∗ ) ∣ 2 ) f(R^*, t^*)=MIN(\frac{1}{N_p}{\textstyle \sum_{i=1}^{N_p}\left | p_{i}^{t}-(R^*q_{i}^{s}+t^*) \right |^2 } ) f(R,t)=MIN(Np1i=1Nppit(Rqis+t)2)
其中 N p N_p Np为配对点云个数, p i t p_{i}^{t} pit q i s q_{i}^{s} qis是目标点云与源点云中的一对配对点。

寻找对应点对

起始步骤中,我们只有一系列无序的三维点,并没有对应的 p i t p_{i}^{t} pit q i s q_{i}^{s} qis点对。ICP中定义了“最近邻点”的定义。

  • 使用一个初始位姿 ( R 0 , t 0 ) (R^0, t^0) (R0,t0)对源点云 Q Q Q进行变换,得到一个变换后的点云 Q ′ Q' Q
  • 对变换后的点云 Q ′ Q' Q中的点 q i ′ q_{i}^{'} qi在目标点云中查找最近邻点 p j p_{j} pj,如果两点之间的距离小于预先设置的阈值,则认为点 q i ′ q_{i}^{'} qi与点 p j p_{j} pj为对应的点对。

优化位姿变换 ( R , t ) (R, t) (R,t)

找到所有合理的最近邻点对之后,则每一个点对都可以构建一个函数(每个点对类似于一个观测),而待求变量 ( R , t ) (R, t) (R,t)只有6个自由度。所以我们有了 N p N_p Np个观测,6个待求变量。使用最小二乘进行优化求解。

这样我们就更新了初始位姿 ( R 0 , t 0 ) (R^0, t^0) (R0,t0)为新的 ( R 1 , t 1 ) (R^1, t^1) (R1,t1)

迭代优化

得到新的位姿变换 ( R 1 , t 1 ) (R^1, t^1) (R1,t1)之后,我们再次回到寻找对应点对步骤中,重新转换源点云 Q Q Q Q 2 Q^2 Q2,查找 Q 2 Q^2 Q2 P P P新的点对。
接着,进行新的 “优化位姿变换 ( R , t ) (R, t) (R,t)” 步骤,重复得到新的优化位姿 ( R 2 , t 2 ) (R^2, t^2) (R2,t2)

直到达到迭代停止条件,如:1)达到最大迭代次数,或2)位姿变化量小于阈值,或3)最近邻点对不再变化等则终止迭代。

调用PCL库

参考链接:Interactive Iterative Closest Point

  1#include <iostream>
  2#include <string>
  3
  4#include <pcl/io/ply_io.h>
  5#include <pcl/point_types.h>
  6#include <pcl/registration/icp.h>
  7#include <pcl/visualization/pcl_visualizer.h>
  8#include <pcl/console/time.h>   // TicToc
  9
 10typedef pcl::PointXYZ PointT;
 11typedef pcl::PointCloud<PointT> PointCloudT;
 12
 13bool next_iteration = false;
 14
 15void
 16print4x4Matrix (const Eigen::Matrix4d & matrix)
 17{
 18  printf ("Rotation matrix :\n");
 19  printf ("    | %6.3f %6.3f %6.3f | \n", matrix (0, 0), matrix (0, 1), matrix (0, 2));
 20  printf ("R = | %6.3f %6.3f %6.3f | \n", matrix (1, 0), matrix (1, 1), matrix (1, 2));
 21  printf ("    | %6.3f %6.3f %6.3f | \n", matrix (2, 0), matrix (2, 1), matrix (2, 2));
 22  printf ("Translation vector :\n");
 23  printf ("t = < %6.3f, %6.3f, %6.3f >\n\n", matrix (0, 3), matrix (1, 3), matrix (2, 3));
 24}
 25
 26void
 27keyboardEventOccurred (const pcl::visualization::KeyboardEvent& event,
 28                       void*)
 29{
 30  if (event.getKeySym () == "space" && event.keyDown ())
 31    next_iteration = true;
 32}
 33
 34int
 35main (int argc,
 36      char* argv[])
 37{
 38  // The point clouds we will be using
 39  PointCloudT::Ptr cloud_in (new PointCloudT);  // Original point cloud
 40  PointCloudT::Ptr cloud_tr (new PointCloudT);  // Transformed point cloud
 41  PointCloudT::Ptr cloud_icp (new PointCloudT);  // ICP output point cloud
 42
 43  // Checking program arguments
 44  if (argc < 2)
 45  {
 46    printf ("Usage :\n");
 47    printf ("\t\t%s file.ply number_of_ICP_iterations\n", argv[0]);
 48    PCL_ERROR ("Provide one ply file.\n");
 49    return (-1);
 50  }
 51
 52  int iterations = 1;  // Default number of ICP iterations
 53  if (argc > 2)
 54  {
 55    // If the user passed the number of iteration as an argument
 56    iterations = atoi (argv[2]);
 57    if (iterations < 1)
 58    {
 59      PCL_ERROR ("Number of initial iterations must be >= 1\n");
 60      return (-1);
 61    }
 62  }
 63
 64  pcl::console::TicToc time;
 65  time.tic ();
 66  if (pcl::io::loadPLYFile (argv[1], *cloud_in) < 0)
 67  {
 68    PCL_ERROR ("Error loading cloud %s.\n", argv[1]);
 69    return (-1);
 70  }
 71  std::cout << "\nLoaded file " << argv[1] << " (" << cloud_in->size () << " points) in " << time.toc () << " ms\n" << std::endl;
 72
 73  // Defining a rotation matrix and translation vector
 74  Eigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity ();
 75
 76  // A rotation matrix (see https://en.wikipedia.org/wiki/Rotation_matrix)
 77  double theta = M_PI / 8;  // The angle of rotation in radians
 78  transformation_matrix (0, 0) = std::cos (theta);
 79  transformation_matrix (0, 1) = -sin (theta);
 80  transformation_matrix (1, 0) = sin (theta);
 81  transformation_matrix (1, 1) = std::cos (theta);
 82
 83  // A translation on Z axis (0.4 meters)
 84  transformation_matrix (2, 3) = 0.4;
 85
 86  // Display in terminal the transformation matrix
 87  std::cout << "Applying this rigid transformation to: cloud_in -> cloud_icp" << std::endl;
 88  print4x4Matrix (transformation_matrix);
 89
 90  // Executing the transformation
 91  pcl::transformPointCloud (*cloud_in, *cloud_icp, transformation_matrix);
 92  *cloud_tr = *cloud_icp;  // We backup cloud_icp into cloud_tr for later use
 93
 94  // The Iterative Closest Point algorithm
 95  time.tic ();
 96  pcl::IterativeClosestPoint<PointT, PointT> icp;
 97  icp.setMaximumIterations (iterations);
 98  icp.setInputSource (cloud_icp);
 99  icp.setInputTarget (cloud_in);
100  icp.align (*cloud_icp);
101  icp.setMaximumIterations (1);  // We set this variable to 1 for the next time we will call .align () function
102  std::cout << "Applied " << iterations << " ICP iteration(s) in " << time.toc () << " ms" << std::endl;
103
104  if (icp.hasConverged ())
105  {
106    std::cout << "\nICP has converged, score is " << icp.getFitnessScore () << std::endl;
107    std::cout << "\nICP transformation " << iterations << " : cloud_icp -> cloud_in" << std::endl;
108    transformation_matrix = icp.getFinalTransformation ().cast<double>();
109    print4x4Matrix (transformation_matrix);
110  }
111  else
112  {
113    PCL_ERROR ("\nICP has not converged.\n");
114    return (-1);
115  }
116
117  // Visualization
118  pcl::visualization::PCLVisualizer viewer ("ICP demo");
119  // Create two vertically separated viewports
120  int v1 (0);
121  int v2 (1);
122  viewer.createViewPort (0.0, 0.0, 0.5, 1.0, v1);
123  viewer.createViewPort (0.5, 0.0, 1.0, 1.0, v2);
124
125  // The color we will be using
126  float bckgr_gray_level = 0.0;  // Black
127  float txt_gray_lvl = 1.0 - bckgr_gray_level;
128
129  // Original point cloud is white
130  pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_in_color_h (cloud_in, (int) 255 * txt_gray_lvl, (int) 255 * txt_gray_lvl,
131                                                                             (int) 255 * txt_gray_lvl);
132  viewer.addPointCloud (cloud_in, cloud_in_color_h, "cloud_in_v1", v1);
133  viewer.addPointCloud (cloud_in, cloud_in_color_h, "cloud_in_v2", v2);
134
135  // Transformed point cloud is green
136  pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_tr_color_h (cloud_tr, 20, 180, 20);
137  viewer.addPointCloud (cloud_tr, cloud_tr_color_h, "cloud_tr_v1", v1);
138
139  // ICP aligned point cloud is red
140  pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_icp_color_h (cloud_icp, 180, 20, 20);
141  viewer.addPointCloud (cloud_icp, cloud_icp_color_h, "cloud_icp_v2", v2);
142
143  // Adding text descriptions in each viewport
144  viewer.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);
145  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);
146
147  std::stringstream ss;
148  ss << iterations;
149  std::string iterations_cnt = "ICP iterations = " + ss.str ();
150  viewer.addText (iterations_cnt, 10, 60, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "iterations_cnt", v2);
151
152  // Set background color
153  viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v1);
154  viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v2);
155
156  // Set camera position and orientation
157  viewer.setCameraPosition (-3.68332, 2.94092, 5.71266, 0.289847, 0.921947, -0.256907, 0);
158  viewer.setSize (1280, 1024);  // Visualiser window size
159
160  // Register keyboard callback :
161  viewer.registerKeyboardCallback (&keyboardEventOccurred, (void*) NULL);
162
163  // Display the visualiser
164  while (!viewer.wasStopped ())
165  {
166    viewer.spinOnce ();
167
168    // The user pressed "space" :
169    if (next_iteration)
170    {
171      // The Iterative Closest Point algorithm
172      time.tic ();
173      icp.align (*cloud_icp);
174      std::cout << "Applied 1 ICP iteration in " << time.toc () << " ms" << std::endl;
175
176      if (icp.hasConverged ())
177      {
178        printf ("\033[11A");  // Go up 11 lines in terminal output.
179        printf ("\nICP has converged, score is %+.0e\n", icp.getFitnessScore ());
180        std::cout << "\nICP transformation " << ++iterations << " : cloud_icp -> cloud_in" << std::endl;
181        transformation_matrix *= icp.getFinalTransformation ().cast<double>();  // WARNING /!\ This is not accurate! For "educational" purpose only!
182        print4x4Matrix (transformation_matrix);  // Print the transformation between original pose and current pose
183
184        ss.str ("");
185        ss << iterations;
186        std::string iterations_cnt = "ICP iterations = " + ss.str ();
187        viewer.updateText (iterations_cnt, 10, 60, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "iterations_cnt");
188        viewer.updatePointCloud (cloud_icp, cloud_icp_color_h, "cloud_icp_v2");
189      }
190      else
191      {
192        PCL_ERROR ("\nICP has not converged.\n");
193        return (-1);
194      }
195    }
196    next_iteration = false;
197  }
198  return (0);
199}

配准结果

图中红色为目标帧点云,蓝色为转换后的源点云。
在这里插入图片描述

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

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

相关文章

最新最全中小微企业研究数据:海量创业公司信息与获取投资信息(1985-2021年)

一、企业获取投资名单&资方信息 数据来源&#xff1a;搜企网、企查查、天眼查 时间跨度&#xff1a;1985年8月-2021年9月 区域范围&#xff1a;全国范围 数据字段&#xff1a;企业名称、时间、获得投资金额以及投资方信息 部分数据&#xff1a; DateCompany_nameUnit…

摄影师没了?!生成式人工智能即将降维打击摄影行业

本文是Mixlab无界社区成员的投稿&#xff1a;滚石deepfacelab和deepfacelive项目组成员摄影师失业了&#xff1f;&#xff1f;怎么说&#xff1f;##你还以为AI绘画影响的只是插画师行业吗&#xff1f;错了&#xff0c;摄影行业也即将面临技术洗牌。话不多说&#xff0c;先看一下…

java并发编程原理2 (AQS, ReentrantLock,线程池)

一、AQS&#xff1a; 1.1 AQS是什么&#xff1f; AQS就是一个抽象队列同步器&#xff0c;abstract queued sychronizer&#xff0c;本质就是一个抽象类。 AQS中有一个核心属性state&#xff0c;其次还有一个双向链表以及一个单项链表。 首先state是基于volatile修饰&#x…

分享113个HTML艺术时尚模板,总有一款适合您

分享113个HTML艺术时尚模板&#xff0c;总有一款适合您 113个HTML艺术时尚模板下载链接&#xff1a;https://pan.baidu.com/s/1ReoPNIRjkYov-SjsPo0vhg?pwdjk4a 提取码&#xff1a;jk4a Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 女性化妆用品网页模板 粉…

【Linux】用户分类+权限管理+umask+粘滞位说明

目录 1.用户分类 su指令 2.认识Linux权限 2.1 文件访问者的分类 2.2 文件类型和访问权限 a. 文件类型 file指令 b. 访问权限 2.3 文件权值的表示方法 a. 字母表示法 b. 八进制表示法 3.如何修改文件访问者的权限及相关指令 1. chmod指令 2. chown指令 3. chgrp指…

Python语言零基础入门教程(二十七)

Python OS 文件/目录方法 Python语言零基础入门教程&#xff08;二十六&#xff09; 61、Python os.utime() 方法 概述 os.utime() 方法用于设置指定路径文件最后的修改和访问时间。 在Unix&#xff0c;Windows中有效。 语法 utime()方法语法格式如下&#xff1a; os.uti…

SortableJS/Sortable拖拽组件,使用详细(Sortablejs安装使用)

简述 作为一名前端开发人员&#xff0c;在工作中难免会遇到拖拽功能&#xff0c;分享一个github上一个不错的拖拽js库&#xff0c;能满足我们在项目开发中的需要&#xff0c;支持Vue和React&#xff0c;下面是SortableJS的使用详细&#xff1b; 这个是sortableJS中文官方文档&…

kafka-6-python单线程操作kafka

使用Python操作Kafka&#xff1a;KafkaProducer、KafkaConsumer Python kafka-python API的帮助文档 1 kafka tools连接 (1)/usr/local/kafka_2.13-3.4.0/config/server.properties listeners PLAINTEXT://myubuntu:9092 advertised.listenersPLAINTEXT://192.168.1.8:2909…

MotoSimEG-VRC教程:动态输送带创建以及示教编程与仿真运行

目录 任务描述 简易输送带外部设备创建 输送带模型添加与配置 工件安装到输送带 输送带输送工件程序编写与仿真运行 任务描述 在MotoSimEG-VRC中创建1条输送带&#xff0c;并且能够实现将工件从输送带起始点位置处输送到结束点位置处。 简易输送带外部设备创建 在MotoS…

Linux系统之终端管理命令的基本使用

Linux系统之终端管理命令的基本使用一、检查本地系统环境1.检查系统版本2.检查系统内核版本二、终端介绍1.终端简介2.Linux终端简介3.终端的发展三、终端的相关术语1.终端模拟器2.tty终端3.pts终端4.pty终端5.控制台终端四、终端管理命令ps1.直接使用ps命令2.列出登录详细信息五…

RPA落地指南:什么是RPA

什么是RPA RPA在企业中起什么作用并扮演什么角色呢&#xff1f;想要充分了解RPA&#xff0c;我们需要知道RPA的相关概念、特点、功能以及能解决的问题。接下来对这些内容进行详细介绍。 1.1 RPA的3个核心概念 RPA的中文译名是“机器人流程自动化”&#xff0c;顾名思义&…

初始C语言 - 数组(一维数组、二维数组、数组越界、数组传参)

目录 一、一维数组的创建和初始化 1、数组的创建 2、 数组的初始化 3.一维数组的使用 数组通过下标来访问 总结: 1. 数组是使用下标来访问的&#xff0c;下标是从0开始。 2. 数组的大小可以通过计算得到。 4、一维数组在内存中的存储 二、 二维数组的创建和初始化 1.二…

算法导论【分治思想】—大数乘法、矩阵相乘、残缺棋盘

这里写自定义目录标题分治法概述特点大数相乘问题分治算法矩阵相乘分治算法残缺棋盘分治算法分治法概述 在分而治之的方法中&#xff0c;一个问题被划分为较小的问题&#xff0c;然后较小的问题被独立地解决&#xff0c;最后较小问题的解决方案被组合成一个大问题的解决。 通常…

【软件测试】自动化测试工程师必会的单元测试编写(总结),你真的了解吗......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 单元测试编写的目的…

supervisor-男程序员的福音

supervisor是什么 supervisor是用python语言编写的&#xff0c;只能用于类Unix系统上的进程管理工具。 supervisor有什么用 举一个常见的场景&#xff0c;比如你的项目已经到了测试联调阶段&#xff0c;QA需要你把程序启动起来&#xff0c;然后进行测试&#xff0c;那么启动…

用Java实现多线程打印奇偶数

用Java实现多线程打印奇偶数1. wait()和 notify() 方法的作用&#xff1a;2. Java实现&#xff08;1&#xff09;Thread1.class 奇数线程&#xff08;2&#xff09;Thread2.class 偶数线程&#xff08;3&#xff09;共享资源类&#xff08;4&#xff09;测试1. wait()和 notify…

一篇文章带你玩转 Kubernetes:组件、核心概念和Nginx实战演示

目录一、简介1.1 容器部署时代1.2 Kubernetes有哪些优点二、Kubernetes 组件介绍三、Kubernetes 核心概念3.1 Namespace3.2 Pod3.3 Deployment3.4 Service3.5 Ingress四、Kubernetes 核心概念实战4.1 部署yaml文件4.2 通过Pod IP访问Nginx4.3 通过Service IP访问Nginx4.4 修改i…

[数据结构]:顺序表(C语言实现)

目录 前言 顺序表实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-SeqListCommon.cpp 04-SeqListPositionOperation.cpp 05-SeqListValueOperation.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为…

node+vue微信小程序的社区后勤报修系统

社区后勤报修系统小程序进行总体设计和详细设计。总体设计主要包括小程序功能设计、小程序总体结构设计、小程序数据结构设计和小程序安全设计等&#xff1a;详细设计主要包括社区后勤报修系统小程序数据库访问的实现,主要功能模块的具体实现,模块实现关键代码等。最后对社区后…