1. 点云配准介绍
1.1 概念简介
将从各个视角得到的点集合并到统一的坐标系下形成一个完整的点云
,然后就可以方便进行可视化的操作,这就是点云数据的配准。
例如下列6个独立视角的点云数据,每个数据只代表了完整视图下的一小部分,因此需要一种对齐方式对他们进行配准。
对齐后的数据如下所示:
1.2 两两配准
两两配准(pairwise registration 或 pair-wise registration)也就是一对点云数据集的配准问题是。两两配准也就是求它的旋转矩阵和平移矩阵,通常矩阵为4*4的矩阵。
一次迭代的配准流程如下:
步骤如下:
- 首先从两个数据集中按照同样的关键点选取的标准,提取关键点
- 对选择所有的关键点分别计算其特征描述子
- 结合特征描述子在两个数据集中的坐标位置,以两者之间的特征和位置的相似度为基础,来估算它们的对应关系,初步的估计对应点对。
- 假设数据是有噪声,出去对配准有影响的错误的对应点对
- 利用剩余的正确的对应关系来估算刚体变换,完整配准。
这里先介绍ICP的配准方法。
2. 最近点迭代算法-ICP
ICP(Iterative Closest Point),即最近点迭代算法,是最为经典的数据配准算法。其特征在于,通过求取源点云和目标点云之间的对应点对,基于对应点对构造旋转平移矩阵,并利用所求矩阵,将源点云变换到目标点云的坐标系下,估计变换后源点云与目标点云的误差函数,若误差函数值大于阀值,则迭代进行上述运算直到满足给定的误差要求.
ICP算法采用最小二乘估计计算变换矩阵,原理简单且具有较好的精度,但是由于采用了迭代计算,导致算法计算速度较慢,而且采用ICP进行配准计算时,其对待配准点云的初始位置有一定要求,若所选初始位置不合理,则会导致算法陷入局部最优
。
代码示例:
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/registration/icp.h>
int
main (int argc, char** argv)
{
//创建点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in (new pcl::PointCloud<pcl::PointXYZ>(5,1));//输入点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_out (new pcl::PointCloud<pcl::PointXYZ>);//输出点云
// Fill in the CloudIn data
//填充输入点云
for (auto& point : *cloud_in)
{
point.x = 1024 * rand() / (RAND_MAX + 1.0f);
point.y = 1024 * rand() / (RAND_MAX + 1.0f);
point.z = 1024 * rand() / (RAND_MAX + 1.0f);
}
std::cout << "Saved " << cloud_in->size () << " data points to input:" << std::endl;
for (auto& point : *cloud_in)
std::cout << point << std::endl;
//设置cloud_out点云,每个x增加0.7
*cloud_out = *cloud_in;
std::cout << "size:" << cloud_out->size() << std::endl;
for (auto& point : *cloud_out)
point.x += 0.7f;
std::cout << "Transformed " << cloud_in->size () << " data points:" << std::endl;
for (auto& point : *cloud_out)
std::cout << point << std::endl;
pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
icp.setInputSource(cloud_in);//source点云
icp.setInputTarget(cloud_out);//target点云
pcl::PointCloud<pcl::PointXYZ> Final;//经过配准之后的点云
icp.align(Final);
//icp.hasConverged()为true时,表示可以进行配准
std::cout << "has converged:" << icp.hasConverged() << " score: " <<
icp.getFitnessScore() << std::endl;//匹配的得分
std::cout << icp.getFinalTransformation() << std::endl;//变换矩阵
return (0);
}
说明:
- icp.hasConverged() :是否可以进行配准
- icp.getFitnessScore():匹配的得分
- icp.getFinalTransformation():变换矩阵
3. 增量式匹配多个点云
上一步我们知道怎么去匹配两个点云,那么如何两两增量式的去匹配点云,形成一个完成的视图呢。
代码举例:
#include <boost/make_shared.hpp> //boost指针相关头文件
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/point_representation.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/filter.h>
#include <pcl/features/normal_3d.h>//法线特征头文件
#include <pcl/registration/icp.h>
#include <pcl/registration/icp_nl.h>
#include <pcl/registration/transforms.h> //变换矩阵
#include <pcl/visualization/pcl_visualizer.h>
using pcl::visualization::PointCloudColorHandlerGenericField;
using pcl::visualization::PointCloudColorHandlerCustom;
//convenient typedefs
typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloud;
typedef pcl::PointNormal PointNormalT;
typedef pcl::PointCloud<PointNormalT> PointCloudWithNormals;
// This is a tutorial so we can afford having global variables
//our visualizer
pcl::visualization::PCLVisualizer *p;
//its left and right viewports
int vp_1, vp_2;
//convenient structure to handle our pointclouds
struct PCD
{
PointCloud::Ptr cloud;
std::string f_name;
PCD() : cloud (new PointCloud) {};
};
struct PCDComparator
{
bool operator () (const PCD& p1, const PCD& p2)
{
return (p1.f_name < p2.f_name);
}
};
// Define a new point representation for < x, y, z, curvature >
class MyPointRepresentation : public pcl::PointRepresentation <PointNormalT>
{
using pcl::PointRepresentation<PointNormalT>::nr_dimensions_;
public:
MyPointRepresentation ()
{
// Define the number of dimensions
nr_dimensions_ = 4;
}
// Override the copyToFloatArray method to define our feature vector
virtual void copyToFloatArray (const PointNormalT &p, float * out) const
{
// < x, y, z, curvature >
out[0] = p.x;
out[1] = p.y;
out[2] = p.z;
out[3] = p.curvature;
}
};
/** \brief Display source and target on the first viewport of the visualizer
*
*/
//显示未配准的源和目标点
void showCloudsLeft(const PointCloud::Ptr cloud_target, const PointCloud::Ptr cloud_source)
{
p->removePointCloud ("vp1_target");
p->removePointCloud ("vp1_source");
PointCloudColorHandlerCustom<PointT> tgt_h (cloud_target, 0, 255, 0);
PointCloudColorHandlerCustom<PointT> src_h (cloud_source, 255, 0, 0);
p->addPointCloud (cloud_target, tgt_h, "vp1_target", vp_1);
p->addPointCloud (cloud_source, src_h, "vp1_source", vp_1);
PCL_INFO ("Press q to begin the registration.\n");
p-> spin();
}
/** \brief Display source and target on the second viewport of the visualizer
*
*/
//显示配准后的源和目标点云
void showCloudsRight(const PointCloudWithNormals::Ptr cloud_target, const PointCloudWithNormals::Ptr cloud_source)
{
p->removePointCloud ("source");
p->removePointCloud ("target");
PointCloudColorHandlerGenericField<PointNormalT> tgt_color_handler (cloud_target, "curvature");
if (!tgt_color_handler.isCapable ())
PCL_WARN ("Cannot create curvature color handler!");
PointCloudColorHandlerGenericField<PointNormalT> src_color_handler (cloud_source, "curvature");
if (!src_color_handler.isCapable ())
PCL_WARN ("Cannot create curvature color handler!");
p->addPointCloud (cloud_target, tgt_color_handler, "target", vp_2);
p->addPointCloud (cloud_source, src_color_handler, "source", vp_2);
p->spinOnce();
}
/** \brief Load a set of PCD files that we want to register together
* \param argc the number of arguments (pass from main ())
* \param argv the actual command line arguments (pass from main ())
* \param models the resultant vector of point cloud datasets
*/
//加载数据
void loadData (int argc, char **argv, std::vector<PCD, Eigen::aligned_allocator<PCD> > &models)
{
std::string extension (".pcd");
// Suppose the first argument is the actual test model
for (int i = 1; i < argc; i++)
{
std::string fname = std::string (argv[i]);
// Needs to be at least 5: .plot
if (fname.size () <= extension.size ())
continue;
std::transform (fname.begin (), fname.end (), fname.begin (), (int(*)(int))tolower);
//check that the argument is a pcd file
if (fname.compare (fname.size () - extension.size (), extension.size (), extension) == 0)
{
// Load the cloud and saves it into the global list of models
//加载后的点云存储在m中
PCD m;
m.f_name = argv[i];
pcl::io::loadPCDFile (argv[i], *m.cloud);
//remove NAN points from the cloud
std::vector<int> indices;
//去除无效点,NAN点
pcl::removeNaNFromPointCloud(*m.cloud,*m.cloud, indices);
models.push_back (m);
}
}
}
/** \brief Align a pair of PointCloud datasets and return the result
* \param cloud_src the source PointCloud
* \param cloud_tgt the target PointCloud
* \param output the resultant aligned source PointCloud
* \param final_transform the resultant transform between source and target
*/
//实现匹配,其中参数有输入一组需要配准的点云,以及是否需要进行下采样,其他参数输出配准后的点云以及变换矩阵
void pairAlign (const PointCloud::Ptr cloud_src, const PointCloud::Ptr cloud_tgt, PointCloud::Ptr output, Eigen::Matrix4f &final_transform, bool downsample = false)
{
//
// Downsample for consistency and speed
// \note enable this for large datasets
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;
}
// Compute surface normals and curvature
// 计算表面的法向量和曲率
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);
//
// Instantiate our custom point representation (defined above) ...
MyPointRepresentation point_representation;
// ... and weight the 'curvature' dimension so that it is balanced against x, y, and z
float alpha[4] = {1.0, 1.0, 1.0, 1.0};
point_representation.setRescaleValues (alpha);
//
// Align
//配准
pcl::IterativeClosestPointNonLinear<PointNormalT, PointNormalT> reg;
reg.setTransformationEpsilon (1e-6);///设置收敛判断条件,越小精度越大,收敛也越慢
// Set the maximum distance between two correspondences (src<->tgt) to 10cm
// Note: adjust this based on the size of your datasets
reg.setMaxCorrespondenceDistance (0.1);
// Set the point representation
reg.setPointRepresentation (boost::make_shared<const MyPointRepresentation> (point_representation));
reg.setInputSource (points_with_normals_src);
reg.setInputTarget (points_with_normals_tgt);
//
// Run the same optimization in a loop and visualize the results
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);
// save cloud for visualization purpose
points_with_normals_src = reg_result;
// Estimate
reg.setInputSource (points_with_normals_src);
reg.align (*reg_result);
//accumulate transformation between each Iteration
Ti = reg.getFinalTransformation () * Ti;
//if the difference between this transformation and the previous one
//is smaller than the threshold, refine the process by reducing
//the maximal correspondence distance
if (std::abs ((reg.getLastIncrementalTransformation () - prev).sum ()) < reg.getTransformationEpsilon ())
reg.setMaxCorrespondenceDistance (reg.getMaxCorrespondenceDistance () - 0.001);
prev = reg.getLastIncrementalTransformation ();
// visualize current state
showCloudsRight(points_with_normals_tgt, points_with_normals_src);
}
//
// Get the transformation from target to source
targetToSource = Ti.inverse();
//
// Transform target back in source frame
pcl::transformPointCloud (*cloud_tgt, *output, targetToSource);
p->removePointCloud ("source");
p->removePointCloud ("target");
PointCloudColorHandlerCustom<PointT> cloud_tgt_h (output, 0, 255, 0);
PointCloudColorHandlerCustom<PointT> cloud_src_h (cloud_src, 255, 0, 0);
p->addPointCloud (output, cloud_tgt_h, "target", vp_2);
p->addPointCloud (cloud_src, cloud_src_h, "source", vp_2);
PCL_INFO ("Press q to continue the registration.\n");
p->spin ();
p->removePointCloud ("source");
p->removePointCloud ("target");
//add the source to the transformed target
*output += *cloud_src;
final_transform = targetToSource;
}
int main (int argc, char** argv)
{
// Load data加载pcd文件
std::vector<PCD, Eigen::aligned_allocator<PCD> > data;
loadData (argc, argv, data);//加载所有点云到data中
// Check user input
//检查pcd是否为空
if (data.empty ())
{
PCL_ERROR ("Syntax is: %s <source.pcd> <target.pcd> [*]", argv[0]);
PCL_ERROR ("[*] - multiple files can be added. The registration results of (i, i+1) will be registered against (i+2), etc");
return (-1);
}
PCL_INFO ("Loaded %d datasets.", (int)data.size ());
// Create a PCLVisualizer object可视化对象
p = new pcl::visualization::PCLVisualizer (argc, argv, "Pairwise Incremental Registration example");
p->createViewPort (0.0, 0, 0.5, 1.0, vp_1);//用左半窗口创建视口vp_1
p->createViewPort (0.5, 0, 1.0, 1.0, vp_2);//用右半窗口创建视口vp_2
PointCloud::Ptr result (new PointCloud), source, target;
Eigen::Matrix4f GlobalTransform = Eigen::Matrix4f::Identity (), pairTransform;
for (std::size_t i = 1; i < data.size (); ++i)//循环处理所有点云
{
source = data[i-1].cloud;//连续配准
target = data[i].cloud;// 相邻两组点云
// Add visualization data
showCloudsLeft(source, target);//可视化为配准的源和目标点云
//调用子函数完成一组点云的配准,temp返回配准后两组点云在第一组点云坐标下的点云
PointCloud::Ptr temp (new PointCloud);
PCL_INFO ("Aligning %s (%zu) with %s (%zu).\n", data[i-1].f_name.c_str (), static_cast<std::size_t>(source->size ()), data[i].f_name.c_str (), static_cast<std::size_t>(target->size ()));
// pairTransform返回从目标点云target到source的变换矩阵
pairAlign (source, target, temp, pairTransform, true);
//transform current pair into the global transform
//把当前两两配准后的点云temp转化到全局坐标系下返回result
pcl::transformPointCloud (*temp, *result, GlobalTransform);
//update the global transform
//用当前的两组点云之间的变换更新全局变换
GlobalTransform *= pairTransform;
//save aligned pair, transformed into the first cloud's frame
//保存转换到第一个点云坐标下的当前配准后的两组点云result到文件i.pcd
std::stringstream ss;
ss << i << ".pcd";
pcl::io::savePCDFile (ss.str (), *result, true);
}
}
运行命令:
./pairwise_incremental_registration capture000[1-5].pcd
运行结果如下(左边为原始的未匹配的源和目标点云,右边显示配准后的源和目标点云):
3. 交互式ICP可视化
该示例指定迭代次数。
#include <iostream>
#include <string>
#include <pcl/io/ply_io.h> //PLY相关头文件
#include <pcl/point_types.h> //
#include <pcl/registration/icp.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/console/time.h>
typedef 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));
}
void keyboardEventOccurred (const pcl::visualization::KeyboardEvent& event,
void* nothing)
{ //使用空格键来增加迭代次数,并更新显示
if (event.getKeySym () == "space" && event.keyDown ())
next_iteration = true;
}
int main (int argc,
char* argv[])
{
// 申明点云将要使用的
PointCloudT::Ptr cloud_in (new PointCloudT); // 原始点云
PointCloudT::Ptr cloud_tr (new PointCloudT); // 转换后的点云
PointCloudT::Ptr cloud_icp (new PointCloudT); // ICP 输出点云
// 检查程序输入命令的合法性
if (argc < 2) //如果只有一个命令说明没有指定目标点云,所以会提示用法
{
printf ("Usage :\n");
printf ("\t\t%s file.ply number_of_ICP_iterations\n", argv[0]);
PCL_ERROR ("Provide one ply file.\n");
return (-1);
}
int iterations = 1; // 默认的ICP迭代次数
if (argc > 2)
{
//如果命令的有两个以上。说明用户是将迭代次数作为传递参数
iterations = atoi (argv[2]); //传递参数的格式转化为int型
if (iterations < 1) //同时不能设置迭代次数为1
{
PCL_ERROR ("Number of initial iterations must be >= 1\n");
return (-1);
}
}
pcl::console::TicToc time; //申明时间记录
time.tic (); //time.tic开始 time.toc结束时间
if (pcl::io::loadPLYFile (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;
//定义旋转矩阵和平移向量Matrix4d是为4*4的矩阵
Eigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity (); //初始化
// 旋转矩阵的定义可以参考 ( https://en.wikipedia.org/wiki/Rotation_matrix)
double theta = M_PI / 8; // 旋转的角度用弧度的表示方法
transformation_matrix (0, 0) = cos (theta);
transformation_matrix (0, 1) = -sin (theta);
transformation_matrix (1, 0) = sin (theta);
transformation_matrix (1, 1) = cos (theta);
// Z轴的平移向量 (0.4 meters)
transformation_matrix (2, 3) = 0.4;
//打印转换矩阵
std::cout << "Applying this rigid transformation to: cloud_in -> cloud_icp" << std::endl;
print4x4Matrix (transformation_matrix);
// 执行点云转换
pcl::transformPointCloud (*cloud_in, *cloud_icp, transformation_matrix);
*cloud_tr = *cloud_icp; // 备份cloud_icp赋值给cloud_tr为后期使用
// 迭代最近点算法
time.tic (); //时间
pcl::IterativeClosestPoint<PointT, PointT> icp;
icp.setMaximumIterations (iterations); //设置最大迭代次数iterations=true
icp.setInputSource (cloud_icp); //设置输入的点云
icp.setInputTarget (cloud_in); //目标点云
icp.align (*cloud_icp); //匹配后源点云
icp.setMaximumIterations (1); // 设置为1以便下次调用
std::cout << "Applied " << iterations << " ICP iteration(s) in " << time.toc () << " ms" << std::endl;
if (icp.hasConverged ())//icp.hasConverged ()=1(true)输出变换矩阵的适合性评估
{
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);
}
// 可视化ICP的过程与结果
pcl::visualization::PCLVisualizer viewer ("ICP demo");
// 创建两个观察视点
int 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);
// 定义显示的颜色信息
float bckgr_gray_level = 0.0; // Black
float txt_gray_lvl = 1.0 - bckgr_gray_level;
// 原始的点云设置为白色的
pcl::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);
// 转换后的点云显示为绿色
pcl::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配准后的点云为红色
pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_icp_color_h (cloud_icp, 180, 20, 20);
viewer.addPointCloud (cloud_icp, cloud_icp_color_h, "cloud_icp_v2", v2);
// 加入文本的描述在各自的视口界面
//在指定视口viewport=v1添加字符串“white 。。。”其中"icp_info_1"是添加字符串的ID标志,(10,15)为坐标16为字符大小 后面分别是RGB值
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);
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);
// 设置背景颜色
viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v1);
viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v2);
// 设置相机的坐标和方向
viewer.setCameraPosition (-3.68332, 2.94092, 5.71266, 0.289847, 0.921947, -0.256907, 0);
viewer.setSize (1280, 1024); // 可视化窗口的大小
// 注册按键回调函数
viewer.registerKeyboardCallback (&keyboardEventOccurred, (void*) NULL);
// 显示
while (!viewer.wasStopped ())
{
viewer.spinOnce ();
//按下空格键的函数
if (next_iteration)
{
// 最近点迭代算法
time.tic ();
icp.align (*cloud_icp);
std::cout << "Applied 1 ICP iteration in " << time.toc () << " ms" << std::endl;
if (icp.hasConverged ())
{
printf ("\033[11A"); // Go up 11 lines in terminal output.
printf ("\nICP has converged, score is %+.0e\n", icp.getFitnessScore ());
std::cout << "\nICP transformation " << ++iterations << " : cloud_icp -> cloud_in" << std::endl;
transformation_matrix *= icp.getFinalTransformation ().cast<double>(); // WARNING /!\ This is not accurate!
print4x4Matrix (transformation_matrix); // 打印矩阵变换
ss.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);
}
运行命令(数字参数表示迭代顺序,按空格键会进行迭代
):
./interactive_icp ../monkey.ply 25
输出结果如下: