(1)正态分布变换进行配准(normal Distributions Transform)

介绍关于如何使用正态分布算法来确定两个大型点云之间的刚体变换,正态分布变换算法是一个配准算法,它应用于三维点的统计模型,使用标准最优化技术来确定两个点云间的最优匹配,因为其在配准的过程中不利用对应点的特征计算和匹配,所以时间比其他方法比较快,

对于代码的解析

  1. /*
  2. 使用正态分布变换进行配准的实验 。其中room_scan1.pcd room_scan2.pcd这些点云包含同一房间360不同视角的扫描数据
  3. */
  4. #include <iostream>
  5. #include <pcl/io/pcd_io.h>
  6. #include <pcl/point_types.h>
  7.  
  8. #include <pcl/registration/ndt.h> //NDT(正态分布)配准类头文件
  9. #include <pcl/filters/approximate_voxel_grid.h> //滤波类头文件 (使用体素网格过滤器处理的效果比较好)
  10.  
  11. #include <pcl/visualization/pcl_visualizer.h>
  12. #include <boost/thread/thread.hpp>
  13.  
  14. int
  15. main (int argc, char** argv)
  16. {
  17. // 加载房间的第一次扫描点云数据作为目标
  18. pcl::PointCloud<pcl::PointXYZ>::Ptr target_cloud (new pcl::PointCloud<pcl::PointXYZ>);
  19. if (pcl::io::loadPCDFile<pcl::PointXYZ> ("room_scan1.pcd", *target_cloud) == -)
  20. {
  21. PCL_ERROR ("Couldn't read file room_scan1.pcd \n");
  22. return (-);
  23. }
  24. std::cout << "Loaded " << target_cloud->size () << " data points from room_scan1.pcd" << std::endl;
  25.  
  26. // 加载从新视角得到的第二次扫描点云数据作为源点云
  27. pcl::PointCloud<pcl::PointXYZ>::Ptr input_cloud (new pcl::PointCloud<pcl::PointXYZ>);
  28. if (pcl::io::loadPCDFile<pcl::PointXYZ> ("room_scan2.pcd", *input_cloud) == -)
  29. {
  30. PCL_ERROR ("Couldn't read file room_scan2.pcd \n");
  31. return (-);
  32. }
  33. std::cout << "Loaded " << input_cloud->size () << " data points from room_scan2.pcd" << std::endl;
  34. //以上的代码加载了两个PCD文件得到共享指针,后续配准是完成对源点云到目标点云的参考坐标系的变换矩阵的估计,得到第二组点云变换到第一组点云坐标系下的变换矩阵
  35. // 将输入的扫描点云数据过滤到原始尺寸的10%以提高匹配的速度,只对源点云进行滤波,减少其数据量,而目标点云不需要滤波处理
  36. //因为在NDT算法中在目标点云对应的体素网格数据结构的统计计算不使用单个点,而是使用包含在每个体素单元格中的点的统计数据
  37. pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_cloud (new pcl::PointCloud<pcl::PointXYZ>);
  38. pcl::ApproximateVoxelGrid<pcl::PointXYZ> approximate_voxel_filter;
  39. approximate_voxel_filter.setLeafSize (0.2, 0.2, 0.2);
  40. approximate_voxel_filter.setInputCloud (input_cloud);
  41. approximate_voxel_filter.filter (*filtered_cloud);
  42. std::cout << "Filtered cloud contains " << filtered_cloud->size ()
  43. << " data points from room_scan2.pcd" << std::endl;
  44.  
  45. // 初始化正态分布(NDT)对象
  46. pcl::NormalDistributionsTransform<pcl::PointXYZ, pcl::PointXYZ> ndt;
  47.  
  48. // 根据输入数据的尺度设置NDT相关参数
  49.  
  50. ndt.setTransformationEpsilon (0.01); //为终止条件设置最小转换差异
  51.  
  52. ndt.setStepSize (0.1); //为more-thuente线搜索设置最大步长
  53.  
  54. ndt.setResolution (1.0); //设置NDT网格网格结构的分辨率(voxelgridcovariance)
  55. //以上参数在使用房间尺寸比例下运算比较好,但是如果需要处理例如一个咖啡杯子的扫描之类更小的物体,需要对参数进行很大程度的缩小
  56.  
  57. //设置匹配迭代的最大次数,这个参数控制程序运行的最大迭代次数,一般来说这个限制值之前优化程序会在epsilon变换阀值下终止
  58. //添加最大迭代次数限制能够增加程序的鲁棒性阻止了它在错误的方向上运行时间过长
  59. ndt.setMaximumIterations ();
  60.  
  61. ndt.setInputSource (filtered_cloud); //源点云
  62. // Setting point cloud to be aligned to.
  63. ndt.setInputTarget (target_cloud); //目标点云
  64.  
  65. // 设置使用机器人测距法得到的粗略初始变换矩阵结果
  66. Eigen::AngleAxisf init_rotation (0.6931, Eigen::Vector3f::UnitZ ());
  67. Eigen::Translation3f init_translation (1.79387, 0.720047, );
  68. Eigen::Matrix4f init_guess = (init_translation * init_rotation).matrix ();
  69.  
  70. // 计算需要的刚体变换以便将输入的源点云匹配到目标点云
  71. pcl::PointCloud<pcl::PointXYZ>::Ptr output_cloud (new pcl::PointCloud<pcl::PointXYZ>);
  72. ndt.align (*output_cloud, init_guess);
  73. //这个地方的output_cloud不能作为最终的源点云变换,因为上面对点云进行了滤波处理
  74. std::cout << "Normal Distributions Transform has converged:" << ndt.hasConverged ()
  75. << " score: " << ndt.getFitnessScore () << std::endl;
  76.  
  77. // 使用创建的变换对为过滤的输入点云进行变换
  78. pcl::transformPointCloud (*input_cloud, *output_cloud, ndt.getFinalTransformation ());
  79.  
  80. // 保存转换后的源点云作为最终的变换输出
  81. pcl::io::savePCDFileASCII ("room_scan2_transformed.pcd", *output_cloud);
  82.  
  83. // 初始化点云可视化对象
  84. boost::shared_ptr<pcl::visualization::PCLVisualizer>
  85. viewer_final (new pcl::visualization::PCLVisualizer ("3D Viewer"));
  86. viewer_final->setBackgroundColor (, , ); //设置背景颜色为黑色
  87.  
  88. // 对目标点云着色可视化 (red).
  89. pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>
  90. target_color (target_cloud, , , );
  91. viewer_final->addPointCloud<pcl::PointXYZ> (target_cloud, target_color, "target cloud");
  92. viewer_final->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE,
  93. , "target cloud");
  94.  
  95. // 对转换后的源点云着色 (green)可视化.
  96. pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>
  97. output_color (output_cloud, , , );
  98. viewer_final->addPointCloud<pcl::PointXYZ> (output_cloud, output_color, "output cloud");
  99. viewer_final->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE,
  100. , "output cloud");
  101.  
  102. // 启动可视化
  103. viewer_final->addCoordinateSystem (1.0); //显示XYZ指示轴
  104. viewer_final->initCameraParameters (); //初始化摄像头参数
  105.  
  106. // 等待直到可视化窗口关闭
  107. while (!viewer_final->wasStopped ())
  108. {
  109. viewer_final->spinOnce ();
  110. boost::this_thread::sleep (boost::posix_time::microseconds ());
  111. }
  112.  
  113. return ();
  114. }

编译完成运行的结果:

(2)本实验将学习如何编写一个交互式ICP可视化的程序。该程序将加载点云并对其进行刚性变换。之后,使用ICP算法将变换后的点云与原来的点云对齐。每次用户按下“空格”,进行ICP迭代,刷新可视化界面。

在这里原始例程使用的是PLY格式的文件,可以找一个PLY格式的文件进行实验,也可以使用格式转换文件 把PCD 文件转为PLY文件

  1. #include <iostream>
  2. #include <string>
  3.  
  4. #include <pcl/io/ply_io.h> //PLY相关头文件
  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>
  9.  
  10. typedef pcl::PointXYZ PointT;
  11. typedef pcl::PointCloud<PointT> PointCloudT; //定义点云的格式
  12.  
  13. bool next_iteration = false;
  14.  
  15. void
  16. print4x4Matrix (const Eigen::Matrix4d & matrix) //打印旋转矩阵和平移矩阵
  17. {
  18. printf ("Rotation matrix :\n");
  19. printf (" | %6.3f %6.3f %6.3f | \n", matrix (, ), matrix (, ), matrix (, ));
  20. printf ("R = | %6.3f %6.3f %6.3f | \n", matrix (, ), matrix (, ), matrix (, ));
  21. printf (" | %6.3f %6.3f %6.3f | \n", matrix (, ), matrix (, ), matrix (, ));
  22. printf ("Translation vector :\n");
  23. printf ("t = < %6.3f, %6.3f, %6.3f >\n\n", matrix (, ), matrix (, ), matrix (, ));
  24. }
  25.  
  26. void
  27. keyboardEventOccurred (const pcl::visualization::KeyboardEvent& event,
  28. void* nothing)
  29. { //使用空格键来增加迭代次数,并更新显示
  30. if (event.getKeySym () == "space" && event.keyDown ())
  31. next_iteration = true;
  32. }
  33.  
  34. int
  35. main (int argc,
  36. char* argv[])
  37. {
  38. // 申明点云将要使用的
  39. PointCloudT::Ptr cloud_in (new PointCloudT); // 原始点云
  40. PointCloudT::Ptr cloud_tr (new PointCloudT); // 转换后的点云
  41. PointCloudT::Ptr cloud_icp (new PointCloudT); // ICP 输出点云
  42.  
  43. // 检查程序输入命令的合法性
  44. if (argc < ) //如果只有一个命令说明没有指定目标点云,所以会提示用法
  45. {
  46. printf ("Usage :\n");
  47. printf ("\t\t%s file.ply number_of_ICP_iterations\n", argv[]);
  48. PCL_ERROR ("Provide one ply file.\n");
  49. return (-);
  50. }
  51.  
  52. int iterations = ; // 默认的ICP迭代次数
  53. if (argc > )
  54. {
  55. //如果命令的有两个以上。说明用户是将迭代次数作为传递参数
  56. iterations = atoi (argv[]); //传递参数的格式转化为int型
  57. if (iterations < ) //同时不能设置迭代次数为1
  58. {
  59. PCL_ERROR ("Number of initial iterations must be >= 1\n");
  60. return (-);
  61. }
  62. }
  63.  
  64. pcl::console::TicToc time; //申明时间记录
  65. time.tic (); //time.tic开始 time.toc结束时间
  66. if (pcl::io::loadPLYFile (argv[], *cloud_in) < )
  67. {
  68. PCL_ERROR ("Error loading cloud %s.\n", argv[]);
  69. return (-);
  70. }
  71. std::cout << "\nLoaded file " << argv[] << " (" << cloud_in->size () << " points) in " << time.toc () << " ms\n" << std::endl;
  72.  
  73. //定义旋转矩阵和平移向量Matrix4d是为4*4的矩阵
  74. Eigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity (); //初始化
  75.  
  76. // 旋转矩阵的定义可以参考 ( https://en.wikipedia.org/wiki/Rotation_matrix)
  77. double theta = M_PI / ; // 旋转的角度用弧度的表示方法
  78. transformation_matrix (, ) = cos (theta);
  79. transformation_matrix (, ) = -sin (theta);
  80. transformation_matrix (, ) = sin (theta);
  81. transformation_matrix (, ) = cos (theta);
  82.  
  83. // Z轴的平移向量 (0.4 meters)
  84. transformation_matrix (, ) = 0.4;
  85.  
  86. //打印转换矩阵
  87. std::cout << "Applying this rigid transformation to: cloud_in -> cloud_icp" << std::endl;
  88. print4x4Matrix (transformation_matrix);
  89.  
  90. // 执行点云转换
  91. pcl::transformPointCloud (*cloud_in, *cloud_icp, transformation_matrix);
  92. *cloud_tr = *cloud_icp; // 备份cloud_icp赋值给cloud_tr为后期使用
  93.  
  94. // 迭代最近点算法
  95. time.tic (); //时间
  96. pcl::IterativeClosestPoint<PointT, PointT> icp;
  97. icp.setMaximumIterations (iterations); //设置最大迭代次数iterations=true
  98. icp.setInputSource (cloud_icp); //设置输入的点云
  99. icp.setInputTarget (cloud_in); //目标点云
  100. icp.align (*cloud_icp); //匹配后源点云
  101. icp.setMaximumIterations (); // 设置为1以便下次调用
  102. std::cout << "Applied " << iterations << " ICP iteration(s) in " << time.toc () << " ms" << std::endl;
  103.  
  104. if (icp.hasConverged ())//icp.hasConverged ()=1(true)输出变换矩阵的适合性评估
  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 (-);
  115. }
  116.  
  117. // 可视化ICP的过程与结果
  118. pcl::visualization::PCLVisualizer viewer ("ICP demo");
  119. // 创建两个观察视点
  120. int v1 ();
  121. int v2 ();
  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. // 定义显示的颜色信息
  126. float bckgr_gray_level = 0.0; // Black
  127. float txt_gray_lvl = 1.0 - bckgr_gray_level;
  128.  
  129. // 原始的点云设置为白色的
  130. pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_in_color_h (cloud_in, (int) * txt_gray_lvl, (int) * txt_gray_lvl,
  131. (int) * 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. // 转换后的点云显示为绿色
  136. pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_tr_color_h (cloud_tr, , , );
  137. viewer.addPointCloud (cloud_tr, cloud_tr_color_h, "cloud_tr_v1", v1);
  138.  
  139. // ICP配准后的点云为红色
  140. pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_icp_color_h (cloud_icp, , , );
  141. viewer.addPointCloud (cloud_icp, cloud_icp_color_h, "cloud_icp_v2", v2);
  142.  
  143. // 加入文本的描述在各自的视口界面
  144. //在指定视口viewport=v1添加字符串“white 。。。”其中"icp_info_1"是添加字符串的ID标志,(10,15)为坐标16为字符大小 后面分别是RGB值
  145. viewer.addText ("White: Original point cloud\nGreen: Matrix transformed point cloud", , , , txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "icp_info_1", v1);
  146. viewer.addText ("White: Original point cloud\nRed: ICP aligned point cloud", , , , txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "icp_info_2", v2);
  147.  
  148. std::stringstream ss;
  149. ss << iterations; //输入的迭代的次数
  150. std::string iterations_cnt = "ICP iterations = " + ss.str ();
  151. viewer.addText (iterations_cnt, , , , txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "iterations_cnt", v2);
  152.  
  153. // 设置背景颜色
  154. viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v1);
  155. viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v2);
  156.  
  157. // 设置相机的坐标和方向
  158. viewer.setCameraPosition (-3.68332, 2.94092, 5.71266, 0.289847, 0.921947, -0.256907, );
  159. viewer.setSize (, ); // 可视化窗口的大小
  160.  
  161. // 注册按键回调函数
  162. viewer.registerKeyboardCallback (&keyboardEventOccurred, (void*) NULL);
  163.  
  164. // 显示
  165. while (!viewer.wasStopped ())
  166. {
  167. viewer.spinOnce ();
  168.  
  169. //按下空格键的函数
  170. if (next_iteration)
  171. {
  172. // 最近点迭代算法
  173. time.tic ();
  174. icp.align (*cloud_icp);
  175. std::cout << "Applied 1 ICP iteration in " << time.toc () << " ms" << std::endl;
  176.  
  177. if (icp.hasConverged ())
  178. {
  179. printf ("\033[11A"); // Go up 11 lines in terminal output.
  180. printf ("\nICP has converged, score is %+.0e\n", icp.getFitnessScore ());
  181. std::cout << "\nICP transformation " << ++iterations << " : cloud_icp -> cloud_in" << std::endl;
  182. transformation_matrix *= icp.getFinalTransformation ().cast<double>(); // WARNING /!\ This is not accurate!
  183. print4x4Matrix (transformation_matrix); // 打印矩阵变换
  184.  
  185. ss.str ("");
  186. ss << iterations;
  187. std::string iterations_cnt = "ICP iterations = " + ss.str ();
  188. viewer.updateText (iterations_cnt, , , , txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "iterations_cnt");
  189. viewer.updatePointCloud (cloud_icp, cloud_icp_color_h, "cloud_icp_v2");
  190. }
  191. else
  192. {
  193. PCL_ERROR ("\nICP has not converged.\n");
  194. return (-);
  195. }
  196. }
  197. next_iteration = false;
  198. }
  199. return ();
  200. }

生成可执行文件后结果

窗口输出的基本信息

刚开始迭代第一次的结果

按空格键不断迭代的结果

完毕,如有错误请与我联系交流,谢谢,大牛请忽略

微信公众号号可扫描二维码一起共同学习交流

PCL点云配准(2)的更多相关文章

  1. PCL点云配准(1)

    在逆向工程,计算机视觉,文物数字化等领域中,由于点云的不完整,旋转错位,平移错位等,使得要得到的完整的点云就需要对局部点云进行配准,为了得到被测物体的完整数据模型,需要确定一个合适的坐标系,将从各个视 ...

  2. PCL点云配准(3)

    (1)关于点云的配准 1.首先给定源点云与目标点云. 2.提取特征确定对应点 3.估计匹配点对应的变换矩阵 4.应用变换矩阵到源点云到目标点云的变换 配准的流程图 通过特征点的匹配步骤 (1)计算源点 ...

  3. PCL点云库:ICP算法

    ICP(Iterative Closest Point迭代最近点)算法是一种点集对点集配准方法.在VTK.PCL.MRPT.MeshLab等C++库或软件中都有实现,可以参见维基百科中的ICP Alg ...

  4. 使用正态分布变换(Normal Distributions Transform)进行点云配准

    正态分布变换算法是一个配准算法,它应用于三维点的统计模型,使用标准优化技术来确定两个点云间的最优的匹配,因为其在配准过程中不利用对应点的特征计算和匹配,所以时间比其他方法快.下面是PCL官网上的一个例 ...

  5. PCL点云特征描述与提取(1)

    3D点云特征描述与提取是点云信息处理中最基础也是最关键的一部分,点云的识别.分割,重采样,配准曲面重建等处理大部分算法,都严重依赖特征描述与提取的结果.从尺度上来分,一般分为局部特征的描述和全局特征的 ...

  6. PCL点云库(Point Cloud Library)简介

    博客转载自:http://www.pclcn.org/study/shownews.php?lang=cn&id=29 什么是PCL PCL(Point Cloud Library)是在吸收了 ...

  7. MeshLab中进行点云配准

    MeshLab是一个开源.可移植和可扩展的三维几何处理系统,主要用于交互处理和非结构化编辑三维三角形网格.它支持多种文件格式: import:PLY, STL, OFF, OBJ, 3DS, COLL ...

  8. PCL中点云数据格式之间的转化

    (1) 关于pcl::PCLPointCloud2::Ptr和pcl::PointCloud<pcl::PointXYZ>两中数据结构的区别 pcl::PointXYZ::PointXYZ ...

  9. PCL点云库中的坐标系(CoordinateSystem)

    博客转载自:https://blog.csdn.net/qq_33624918/article/details/80488590 引言 世上本没有坐标系,用的人多了,便定义了坐标系统用来定位.地理坐标 ...

随机推荐

  1. django -- 插入行的不同方式

    在django中行是Model的一个实例.也就是说一个Model的实例就对应着一行. 一.通过构造函数创建行: import django django.setup() from polls.mode ...

  2. 有用的 Mongo命令行 db.currentOp() db.collection.find().explain() - 摘自网络

    在Heyzap 和 Bugsnag 我已经使用MongoDB超过一年了,我发现它是一个非常强大的数据库.和其他的数据库一样,它有一些缺陷,但是这里有一些东西我希望有人可以早一点告诉我的. 即使建立索引 ...

  3. Ingress 原理及实例

    什么是Ingress 在Kubernetes中,Service和Pod的IP地址只能在集群内部网络中路由,所有到达“边界路由器”(Edge Router)的网络流量要么被丢弃,要么被转发到别处,从概念 ...

  4. Android:使用 DownloadManager 进行版本更新

    app 以前的版本更新使用的自己写的代码从服务器下载,结果出现了下载完成以后,提示解析包错误的问题,但是呢,找到该 apk 点击安装是可以安装成功的,估计就是最后几秒安装包没有下载完成然后点击了安装出 ...

  5. js函数调用二种常用方法的例子

    js中函数调用的两种常用方法. 一个js函数 function test(aa){ window.alert("你输入的是"+aa); } 方法一:直接调用 test(" ...

  6. [na]tcp&udp扫描原理(nmap常用10条命令)

    nmap软件使用思路及常见用法 Nmap高级用法与典型场景 namp -sn 4种包 使用nmap -sn 查询网段中关注主机或者整个网段的IP存活状态 nmap -sn nmap针对局域网和广域网( ...

  7. Robolectric使用教程

    转载请标明出处:http://blog.csdn.net/shensky711/article/details/53561172 本文出自: [HansChen的博客] 概述 怎样使用 为项目加入依赖 ...

  8. hdu Constructing Roads (最小生成树)

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1102 /************************************************* ...

  9. jsack

    https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr016.html#BABGHEJB

  10. Flink的keyby延时源码

    public class RecordWriter<T extends IOReadableWritable> { ==FullBuffer /** * This is used to s ...