本人超级小白,先是对每一块代码进行学习,结尾是无注释代码。

一、这些是使用正态分布变换算法和用来过滤数据的过滤器对应的头文件

#include <iostream>  //包含c++的标准输入输出头文件iostream

#include <pcl/io/pcd_io.h>  //PCL的PCD格式文件的输入输出头文件

#include <pcl/point_types.h> //PCL对各种格式的点的支持头文件

#include <pcl/registration/ndt.h>

//NDT配准类相关头文件,点(.)后面可改成icp.h,就成为ICP配准类相关头文件。

#include <pcl/filters/approximate_voxel_grid.h>

//滤波类头文件,ApproximateVoxelGrid体素采样引入头文件;也可以采用VoxelGrid体素采样引入头文件 #include <pcl/filters/voxel_grid.h>。对于体素下采样接口功能函数pcl提供了两种方式: pcl::ApproximateVoxelGrid和pcl::VoxelGrid。两种区别主要在于第一种是用每个体素栅格的中心点来近似该体素内的点,提升了速度,但是也损失了原始点云的局部形态精细度。(为什么进行点云滤波在文章结尾)

#include <pcl/visualization/pcl_visualizer.h>

//PCL的点云可视化的最常用的方法就是PCLVisualizer,它是基于VTK(Visualization Toolkit)的可视化类。引入头文件。

#include <boost/thread/thread.hpp>

//c++的boost库多线程(Thread)编程,不是很懂这个(boost库本文结尾可解释)

Int main(int argc, char** argv)

// argc和argv参数在用命令行编译程序时有用。int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数,在VS中默认值为1。char**型的argv,为字符串数组。有看到通过这种argc、argv传入参数到vs,正好解决了我不知道怎么加入PCD文件,之前用直接复制PCD文件到解决方案,可是总是找不到此文件,一直使用的是文件路径代码加入PCD文件。

 

{

二、加载房间的第一次扫描

pcl::PointCloud<pcl::PointXYZ>::Ptr target_cloud(new pcl::PointCloud<pcl::PointXYZ>);

//pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);这一句代码的作用是创建一个真实参数为PointXYZ的PointCloud类型的共享指针并初始化。PointCloud是一个模板类,这里使用PointXYZ作为它的真实参数,Ptr其实就是一个boost共享指针。创建点云指针,将这个点云指向它的副本目标点云,总的来说创建目标点云。

if(pcl::io::loadPCDFile<pcl::PointXYZ>("E://shuju//room_scan1.pcd",*target_cloud) == -1)

//读入PCD格式的文件,如果文件不存在则返回-1。指针指向的目标点云为E盘中room_scan1.pcd。

{

PCL_ERROR("Couldn't read file room_scan1.pcd \n");

//文件不存在时,返回错误,终止程序。

return (-1);

}

//::在c++中表示作用域和所属关系

std::cout << "Loaded "     //输出一句话,std::是个名称空间标识符。

<< target_cloud->size()

//用指针调用函数或者获取数据,对象指针/结构指针->成员变量/成员函数。size()返回的是字节数。

<< " data points from room_scan1.pcd" //输出一句话

<< std::endl;   //换行

三、加载从新视角得到的房间的第二次扫描

pcl::PointCloud<pcl::PointXYZ>::Ptr input_cloud(new pcl::PointCloud<pcl::PointXYZ>);

//创建点云指针,将这个点云指向它的副本输入点云。

if (pcl::io::loadPCDFile<pcl::PointXYZ>("E://shuju//room_scan2.pcd", *input_cloud) == -1)

//读入PCD格式的文件,如果文件不存在则返回-1。指针指向的输入点云为E盘中room_scan2.pcd

{

PCL_ERROR("Couldn't read file room_scan2.pcd \n");

return (-1);

}

std::cout << "Loaded "

<< input_cloud->size() //用指针调用函数或者获取数据

<< " data points from room_scan2.pcd"

<< std::endl;

//以上的代码加载了两个PCD文件得到共享指针,后续配准是完成对源点云到目标点云的参考坐标系的变换矩阵的估计,得到第二组点云变换到第一组点云坐标系下的变换矩阵

四、对如此多的点做优化是非常耗时的,使用了voxel_filter对输入点云(input_cloud)进行过滤,减少其数据量到10%左右,而对target_cloud不做滤波处理.在NDT算法中,目标点云对应的体素网格数据结构的统计计算不使用单个点,而是使用包含在每个体素单元格中的点的统计数据

pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_cloud(new pcl::PointCloud<pcl::PointXYZ>);

//创建点云指针,将这个点云指向它的副本被滤波的点云。创建了滤波的点云。

pcl::ApproximateVoxelGrid<pcl::PointXYZ> approximate_voxel_filter;

//初始化ApproximateVoxelGrid滤波器

approximate_voxel_filter.setLeafSize(0.2, 0.2, 0.2);

//设置每个体素单元格的大小,长宽高。用0.2*0.2*0.2的立方体对点云进行稀疏化

approximate_voxel_filter.setInputCloud(input_cloud);

//输入要滤波的点云

approximate_voxel_filter.filter(*filtered_cloud);

//执行滤波,保存过滤结果至filtered_cloud

std::cout << "Filtered cloud contains "

<< filtered_cloud->size()

//用指针调用函数或者获取数据,对象指针/结构指针->成员变量/成员函数。size()返回的是字节数。

<< " data points from room_scan2.pcd"

<< std::endl;

五、根据输入数据的尺度设置NDT相关参数, 主要涉及到以下四个参数:最小转换差异(TransformationEpsilon)、More-Thuente线搜索的最大步长(StepSize)、NDT网格结构的分辨率(Resolution)、匹配迭代的最大次数(MaximumIterations),NDT算法使用一个体素化数据结构和More-Thuente线搜索,因此需要缩放一些参数来适应数据集。

pcl::NormalDistributionsTransform<pcl::PointXYZ, pcl::PointXYZ> ndt;

//初始化正态分布变换(NDT)

ndt.setTransformationEpsilon(0.01);

//即设置变换的(两个连续变换之间允许的最大差值),这是判断我们的优化过程是否已经收敛到最终解的阈值。设置前后两次迭代的转换矩阵的最大容差(epsilion)(文章结尾有解释)。用处不大的参数,最小精度,一旦增量变化低于此阈值,对齐就会终止。在变换中Epsilon参数分别从长度和弧度,定义了变换矢量[x, y, z, roll, pitch, yaw]的最小许可的递增量,一旦递增量减小到这个临界值以下,那么配准算法就将终止.

ndt.setStepSize(0.1);

//为More-Thuente线搜索设置最大步长(不懂,本文结尾),即设置牛顿法优化的最大步长。需要调,允许的最大步长。搜索算法确定低于此最大值的最佳步长,在接近最佳解时缩小步长。调的经验:step的话可以先放大后变小。

ndt.setResolution(1.0);

//设置NDT网格结构的分辨率(VoxelGridCovariance),即设置网格化时立方体的边长,网格大小设置在NDT中非常重要,太大会导致精度不高,太小导致内存过高,并且只有两幅点云相差不大的情况才能匹配。需要调,网格的体素分辨率。它需要足够大,每个体素包含与点有关的统计数据,平均值,协方差等,统计数据作为一组多元高斯分布用来模拟点云,并且允许我们计算和优化体素内任意位置点的存在概率。该参数是与尺度最相关的,每个体素至少包含6个点,但小到足以唯一地描述环境。调的经验:resolution在追求精度的情况下最好与点密度相等或者低一个数量级,这里是激光点云,所以设了1米

ndt.setMaximumIterations(35);

//设置匹配迭代的最大次数,即当迭代次数达到35次或者收敛到阈值时,停止优化。 一般来说在这个限制值之前,优化程序会在epsilon(最大容差)变换阀值下终止,添加最大迭代次数限制能够增加程序的鲁棒性阻止了它在错误的方向上运行时间过长

ndt.setInputCloud(filtered_cloud);

// 设置要配准的点云,输入点云(滤波后)

ndt.setInputTarget(target_cloud);

//设置点云配准目标(目标点云)

六、设置使用机器人测距法得到的初始对准估计结果,我们对变换参数进行初始化(给一个估计值),虽然算法不指定初值也可以运行,但是有了它,易于得到更好的结果。Eigen是一个矩阵计算的开源库,PCL中用了Eigen作为第三方做矩阵运算,矩阵变换包括旋转(rotation),平移(translation)和尺度(scale)变换。三维点云要表示变换矩阵,只需要旋转和平移。

Eigen::AngleAxisf init_rotation(0.6931, Eigen::Vector3f::UnitZ());

// AngleAxisd旋转向量rotation,以Z轴为旋转轴,angle=0.6931为旋转角度

Eigen::Translation3f init_translation(1.79387, 0.720047, 0);

Eigen::Matrix4f init_guess = (init_translation * init_rotation).matrix();

// Eigen的API,Matrix4f 是4*4的float型矩阵,matrix为矩阵。init_guess 为ndt计算的初始换变换估计位置

七、计算需要的刚体变换(文章结尾解释)以便将输入的点云匹配到目标点云

pcl::PointCloud<pcl::PointXYZ>::Ptr output_cloud(new pcl::PointCloud<pcl::PointXYZ>);

//创建输出点云

ndt.align(*output_cloud, init_guess);

//使用了初始变换矩阵,不使用初始变换ndt.align (*output_cloud)可以试试,仿真结果不同。进行ndt配准,计算变换矩阵。output_cloud: 存储input_cloud经过配准后的点云(由于input_cloud被极大的降采样了,因此这个数据没什么用) 。此处output_cloud不能作为最终的源点云变换,因为上面对源点云进行了滤波处理

std::cout << "Normal Distributions Transform has converged:"

<< ndt.hasConverged() //英文翻译已收敛,此应该是收敛函数。

<< " score(分数越大,配准效果越差): "

<< ndt.getFitnessScore()

//欧几里得适合度得分FitnessScore,该分数计算为从输出云到目标云中最近点的距离的平方。分数越大,准确率越低,配准效果也就越差

<< std::endl;

八、初始变换后,进行点云的变换并保存。

pcl::transformPointCloud(*input_cloud, *output_cloud, ndt.getFinalTransformation());

//使用创建的变换对未过滤的输入点云(intput_cloud)进行变换

进行点云的变换主要用到的的函数是pcl::transformPointCloud,其原型pcl::transformPointCloud(const pcl::PointCloud< PointT > &  cloud_in,

pcl::PointCloud<PointT>& cloud_out,

const Eigen::Matrix4f  & transform )

cloud_in为源点云,cloud_out为变换后点云,transform为变换矩阵

pcl::io::savePCDFileASCII("room_scan2_transformed.pcd", *output_cloud);

//以ascii格式写入文件,保存转换后的点云

九、可视化界面背景设定,着色两种点云。

boost::shared_ptr<pcl::visualization::PCLVisualizer>viewer_final(new pcl::visualization::PCLVisualizer("3D Viewer"));

// 初始化点云可视化界面

viewer_final->setBackgroundColor(0, 0, 0);

//设置背景色为黑色。

pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>

target_color(target_cloud, 255, 0, 0);

viewer_final->addPointCloud<pcl::PointXYZ>(target_cloud, target_color, "target cloud");

viewer_final->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,1,"target cloud");

//对目标点云着色(红色)并可视化。(255,0,0)与RGB有关。

pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>

output_color(output_cloud, 0, 255, 0);

viewer_final->addPointCloud<pcl::PointXYZ>(output_cloud, output_color, "output cloud");

viewer_final->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,1, "output cloud");

//对转换后的点云着色(绿色)并可视化

十、启动可视化

viewer_final->addCoordinateSystem(1.0);

//显示xyz指示轴

viewer_final->initCameraParameters();

//初始化摄像头参数等

while (!viewer_final->wasStopped())

{

viewer_final->spinOnce(100);

boost::this_thread::sleep(boost::posix_time::microseconds(100000));

}

return (0);

}

//等待直到可视化窗口关闭。

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/registration/ndt.h>
#include <pcl/filters/approximate_voxel_grid.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>
int
main(int argc, char** argv)
{pcl::PointCloud<pcl::PointXYZ>::Ptr target_cloud(new pcl::PointCloud<pcl::PointXYZ>);if (pcl::io::loadPCDFile<pcl::PointXYZ>("E://shuju//room_scan1.pcd", *target_cloud) == -1){PCL_ERROR("Couldn't read file room_scan1.pcd \n");return (-1);}std::cout << "Loaded " << target_cloud->size() << " data points from room_scan1.pcd" << std::endl;pcl::PointCloud<pcl::PointXYZ>::Ptr input_cloud(new pcl::PointCloud<pcl::PointXYZ>);if (pcl::io::loadPCDFile<pcl::PointXYZ>("E://shuju//room_scan2.pcd", *input_cloud) == -1){PCL_ERROR("Couldn't read file room_scan2.pcd \n");return (-1);}std::cout << "Loaded " << input_cloud->size() << " data points from room_scan2.pcd" << std::endl;pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_cloud(new pcl::PointCloud<pcl::PointXYZ>);pcl::ApproximateVoxelGrid<pcl::PointXYZ> approximate_voxel_filter;approximate_voxel_filter.setLeafSize(0.2, 0.2, 0.2);approximate_voxel_filter.setInputCloud(input_cloud);approximate_voxel_filter.filter(*filtered_cloud);std::cout << "Filtered cloud contains " << filtered_cloud->size()<< " data points from room_scan2.pcd" << std::endl;pcl::NormalDistributionsTransform<pcl::PointXYZ, pcl::PointXYZ> ndt;ndt.setTransformationEpsilon(0.01);ndt.setStepSize(0.1);ndt.setResolution(1.0);ndt.setMaximumIterations(35);ndt.setInputCloud(filtered_cloud);ndt.setInputTarget(target_cloud);Eigen::AngleAxisf init_rotation(0.6931, Eigen::Vector3f::UnitZ());Eigen::Translation3f init_translation(1.79387, 0.720047, 0);Eigen::Matrix4f init_guess = (init_translation * init_rotation).matrix();pcl::PointCloud<pcl::PointXYZ>::Ptr output_cloud(new pcl::PointCloud<pcl::PointXYZ>);ndt.align(*output_cloud, init_guess);std::cout << "Normal Distributions Transform has converged:" << ndt.hasConverged()<< " score: " << ndt.getFitnessScore() << std::endl;pcl::transformPointCloud(*input_cloud, *output_cloud, ndt.getFinalTransformation());pcl::io::savePCDFileASCII("room_scan2_transformed.pcd", *output_cloud);boost::shared_ptr<pcl::visualization::PCLVisualizer>viewer_final(new pcl::visualization::PCLVisualizer("3D Viewer"));viewer_final->setBackgroundColor(0, 0, 0);pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>target_color(target_cloud, 255, 0, 0);viewer_final->addPointCloud<pcl::PointXYZ>(target_cloud, target_color, "target cloud");viewer_final->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,1, "target cloud");pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>output_color(output_cloud, 0, 255, 0);viewer_final->addPointCloud<pcl::PointXYZ>(output_cloud, output_color, "output cloud");viewer_final->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,1, "output cloud");viewer_final->addCoordinateSystem(1.0);viewer_final->initCameraParameters();
。while (!viewer_final->wasStopped()){viewer_final->spinOnce(100);boost::this_thread::sleep(boost::posix_time::microseconds(100000));}return (0);
}

名词解释:

1、点云滤波作为常见的点云处理算法,一般是点云处理的第一步,对后续处理(配准、特征提取、曲面重建等)有重要作用。滤波有很多功能,比如去除噪声点、离散点、点云平滑以及数据压缩等。接下来几种需要点云滤波处理的情况:点云数据密度不规则需要平滑;离散点需要去除;大量数据需要下采样;噪声数据需要去除。

本文章中用到的是体素滤波器(下采样),体素滤波器可以达到向下采样同时不破坏点云本身几何结构的功能,但是会移动点的位置。 此外体素滤波器可以去除一定程度的噪音点及离群点,主要功能是用来进行降采样。它的原理是根据输入的点云,首先计算一个能够刚好包裹住该点云的立方体,然后根据设定的分辨率,将该大立方体分割成不同的小立方体。对于每一个小立方体内的点,计算他们的质心,并用该质心的坐标来近似该立方体内的若干点。ApproximateVoxelGrid的不同在于这种方法是利用每一个小立方体的中心来近似该立方体内的若干点。相比于 VoxelGrid,计算速度稍快,但也损失了原始点云局部形态的精细度。点云处理之点云滤波去噪_DayDayUp-CSDN博客_点云去噪和点云滤波  各个滤波器

2、配准算法的三个迭代终止条件设置:

1) setMaximumIterations() 设置最大的迭代次数。迭代停止条件之一

2) setTransformationEpsilon()为终止条件设置最小转换差异,设置前后两次迭代的转换矩阵的最大容差(epsilion),一旦两次迭代小于这个最大容差,则认为已经收敛到最优解,迭代停止。迭代停止条件之二,默认值为:0 。本文中NDT算法有使用。

3) setEuclideanFitnessEpsilon() 设置前后两次迭代的点对的欧式距离均值的最大容差,迭代终止条件之三,默认值为:-std::numeric_limits::max ()

文中的ndt.setTransformationEpsilon(0.01);  ndt.setStepSize(0.1);  ndt.setResolution(1.0);以上参数在使用房间尺寸比例下运算比较好,但是对于处理更小物体,需要改参数。

3、刚体变换:变换前后两点间的距离依旧保持不变则被称为刚体变换(Rigid Transform)。 刚体变换可分解为平移变换、旋转变换和反转(镜像)变换。

4、More-Thuente线搜索:非约束最优化:步控制。如果不使用步控制进行根搜索,迭代在两个点之间交替进行却不收敛,下面是从无步控制到有步控制

                                   

一个良好的步控制算法可以避免出现重复的情况或从接近根或极小值附近的区域远离的情况。在一个线搜索方法中,模型函数给出一个步骤方向,然后沿着这个方向搜索以找到一个可以达到收敛的适当的点。

步长参数定义了More-Thuente线搜索允许的最大步长,这个线搜索算法确定了最大值以下的最佳步长,当你靠近最优解时该算法会缩短迭代步长,在最大步长将会在较少的迭代次数下遍历较大的距离,但是却有过度迭代和在不符合要求的局部最小值处结束的风险。

Unconstrained Optimization: Step Control—Wolfram Language Documentation

5、在浏览文献中又看到一篇挺好的文章,关于点云配准相关的类:

无人驾驶算法学习(五):激光里程计之帧间匹配算法_蒋成的博客-CSDN博客_激光里程计

NDT算法配准代码学习,很详细,很多不懂,一句一句解释。相关推荐

  1. 【算法设计与分析】流水作业调度问题 动态规划算法与代码实现 超详细

    题目描述 "加工顺序问题"又被称为"批处理作业调度问题". 设有n个工件需要在机器M1和M2上加工,每个工件的加工顺序都是先在M1上加工,然后在M2上加工.t1 ...

  2. 小数化分数(C++ 代码讲解很详细)

    [问题描述] 任何小数都能表示成分数的形式,对于給定的小数,编写程序其化为最简分数输出,小数包括简单小数和循环小数. [输入形式] 第一行是一个整数N,表示有多少组数据. 每组数据只有一个纯小数,也就 ...

  3. StoryBoard学习..(很详细.)

    http://blog.sina.com.cn/s/blog_5a6efa3301016z4n.html http://blog.sina.com.cn/s/blog_5a6efa330101dgd3 ...

  4. matlab 弗洛伊德算法,手写matlab的弗洛伊德算法的函数(注释很详细)

    function [dist,mypath]=myfloyd(a,sb,db) %输入邻接矩阵a:元素a(i,j)--顶点i到j之间的直达距离,可以是有向的 %sb起点的标号:db终点的标号 %输出: ...

  5. VVC/VTM:代码学习——CIIP详细过程

    位于函数void EncCu::xCheckRDCostMerge2Nx2N()中 原理介绍:VVC/VTM:帧间预测--Combined inter and intra prediction (CI ...

  6. 随机森林分类算法python代码_独家 | 一文读懂随机森林的解释和实现(附python代码)...

    作者:William Koehrsen 翻译:和中华 校对:李润嘉 本文约6000字,建议阅读15分钟. 本文从单棵决策树讲起,然后逐步解释了随机森林的工作原理,并使用sklearn中的随机森林对某个 ...

  7. 点云配准之NDT算法

    1.算法原理 已知有两幅点云,分别为源点云P和目标云Q. 1)将源点云P所在空间划分为一个一个的单元网格,(即三维空间在二维空间上的投影). 2)根据所划分单元网格内点的分布情况,计算单元网格的正态分 ...

  8. ML之MaL: 流形学习MaL的概念认知、算法分类、案例应用、代码实现之详细攻略

    ML之MaL: 流形学习MaL的概念认知.算法分类.案例应用.代码实现之详细攻略 目录 MaL的概念认知 MaL的算法分类 MaL的案例应用 MaL的代码实现 MaL的概念认知 流形学习,全称流形学习 ...

  9. Python编程语言学习:sklearn.manifold的TSNE函数的简介、使用方法、代码实现之详细攻略

    Python编程语言学习:sklearn.manifold的TSNE函数的简介.使用方法.代码实现之详细攻略 目录 Manifold简介 TSNE简介-数据降维且可视化 TSNE使用方法 TSNE代码 ...

  10. CPD配准算法及代码的简单理解(Coherent Point Drift)

    我的毕业设计用到了CPD算法,以及CPD代码工具包,当时刚开始接触CPD时在网上没找到几篇教程或是经验总结的东西.现在毕设做完了,就结合我的理解写一篇文章.可能写的会有点乱,因为我也是一时兴起想要做一 ...

最新文章

  1. 解题报告(五)组合计数(ACM / OI)超高质量题解
  2. 便携式不锈钢管道焊接机器人_304不锈钢管居然可以发黑!?
  3. 解决编译报错:duplicate value for resource
  4. 计算机组装维护的概念,实用计算机组装与维护库及概念.doc
  5. 好久不见(致win7)
  6. centos lnmp源码安装mysql_CentOS 6.6 下源码编译安装MySQL 5.7.5
  7. kafka streams实战 pdf_spring框架实战口试材料
  8. ant之property关键字
  9. 无法读取源文件或磁盘_系统监控之磁盘和网络监控工具
  10. Echarts:Vue3中引入地图,展示不同省市数据
  11. firefox金山词霸取词
  12. linux添加多网站,linux添加多个网站-微笑の风
  13. 激光投影仪和大屏液晶电视有哪些优缺点?二者应该怎么选?
  14. WordPress活动日历和预订插件指南
  15. 【Photoshop】常用快捷键记录—持续记录
  16. vue倒计时:天时分秒
  17. Linux和Mac下的微信电脑版electronic-wechat(非官方)
  18. 数学建模——规划模型
  19. java中jsp内建对象有_JSP内置对象有哪些
  20. marked.js讲解

热门文章

  1. phyton的函数与类的学习
  2. 【190227】VC++视频采集程序源代码
  3. JS编程建议——16:防止switch贯穿
  4. 英文科技论文写作与学术报告2021秋期末考答案|网课期末考答案|学堂在线|清华大学管晓宏教授
  5. cmd常用命令-快捷
  6. 大年初一,给大家发红包了!
  7. python输出列表的子列表_python切片(获取一个子列表(数组))详解
  8. python转码时出现'illegal multibyte sequen'错误
  9. xss.haozi.me靶机
  10. 【B站弹幕游戏开发笔记02】Win10系统下给Python项目导入Protobuf