PCL学习笔记(二十三)-- 交互式迭代最近点配准

一、简介

    在完成初始配准的情况下,一般采用ICP算法进行精细配准,标准的ICP配准算法是对源点云中的每个点,在目标点云中采用穷尽式凑所方法搜索距离最近的点作为对应点。然后求出配准对其所有对应点对的变换矩阵,最后将源点云数据通过该矩阵进行变换。算法一般设计一种或多种收敛规则,重复迭代地进行上述操作知道设计的收敛规则条件满足。如果不考虑测量误差,ICP算法的精度收到测量采样密度的影响,误差值正比于平均采样距离,即采样密度越高,拼合的精度也就越高。

二、代码分析

    1)为了进行测试,首先创建一变换矩阵,对原始模型进行变换,然后再将变换后的模型与原始模型进行配准,以测试ICP:

  // 旋转矩阵的具体定义 (请参考 https://en.wikipedia.org/wiki/Rotation_matrix)
  double theta = M_PI / 20;  // 设置旋转弧度的角度
  transformation_matrix (0, 0) = cos (theta);
  transformation_matrix (0, 1) = -sin (theta);
  transformation_matrix (1, 0) = sin (theta);
  transformation_matrix (1, 1) = cos (theta);

  // 设置平移矩阵
  transformation_matrix (0, 3) = 0.0;
  transformation_matrix (1, 3) = 0.0;
  transformation_matrix (2, 3) = 0.0;
  // 在终端输出转换矩阵
  std::cout << "Applying this rigid transformation to: cloud_in -> cloud_icp" << std::endl;
  print4x4Matrix (transformation_matrix);

  // 执行初始变换
  pcl::transformPointCloud (*cloud_in, *cloud_icp, transformation_matrix);

    2)利用刚体变换对初始点云进行转换,cloud_in为初始模型,cloud_icp为变换后的模型,cloud_tr是变换后数据的备份用于后期显示:

  // 设置ICP配准算法输入参数
  time.tic ();
  pcl::IterativeClosestPoint<PointT, PointT> icp;
  icp.setMaximumIterations (iterations);//设置迭代次数
  icp.setInputSource (cloud_icp);
  icp.setInputTarget (cloud_in);
  icp.align (*cloud_icp);

    3)创建ICP配准对象,设置ICP算法参数,setMaximumIterations函数设置ICP每次迭代最大次数为1,主要是为了后续对每一步迭代的效果进行可视化。然后 ,变换模型在初次配准进行后,当后续ICP配准再被调用时(当用户按下space按键时),每次调用迭代次数都与这里设置的参数一致:

  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);
  }

    4)为了可视化,在绘制窗口中创建两个视口,添加初始点云到创建的两个视口,并用相同的变量txt_gray_1v1赋予初始点云颜色。初始转换后的点云被设置为绿色,添加到左视口,ICP配准后的点云被设置为红色,添加到右视口。对每个视口添加文字描述,以便用户了解该是口相关信息,需要注意的是,因为输入的迭代次数变量iteration为整型,需要转换其格式为字符串:

  // 可视化部分
  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;  // 黑色
  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);

  //  在两个视口,分别添加文字描述
  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);

    5)根据变量bckgr_gray_level,设置两个视口的背景颜色,用户只需要按下C按键,就可以获取相机参数。然后我像这个函数复制参数以保存相机位置、朝向和焦点。当查看显示器窗口时,任何时候只要按下按键,通过registerKeyboardCallback函数,我们来回调相关函数,以实现用户交互式的进行配准。

    当用户按下键盘的任何键,keyboardEventOccurred将会被调用,这个函数将会检查这个按键是否是空格键。如果是空格键,全局变量布尔变量next_iteration被设置为真,允许可视化循环进入下面部分代码,ICP配准对象被调用来配准点云。前面我们已经对该ICP配准对象设置了源和目标点云,且设置循环时的最大迭代次数为1,但要注意的是这里用函数setInputSource设置源点云模型为cloud_icp,配准输出也是cloud_icp。也就是说每次迭代配准的源点云模型其实就是上次迭代配准的结果:

  // 设置背景颜色
  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");  
        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>();  // 
        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;
  }

    6)整体代码:

#include <iostream>
#include <string>
#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>

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(" |%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(" |%6.3f %6.3f %6.3f|\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);

	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;

	if (argc > 2)
	{
		iterations = 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::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;

	Eigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity();					//实例化一个旋转矩阵

	double theta = M_PI / 20;																//设置旋转矩阵的旋转角度
	transformation_matrix(0, 0) = cos(theta);												//初始化旋转矩阵
	transformation_matrix(0, 1) = -sin(theta);
	transformation_matrix(1, 0) = sin(theta);
	transformation_matrix(1, 1) = cos(theta);

	transformation_matrix(0, 3) = 0.0;														//设置平移矩阵
	transformation_matrix(1, 3) = 0.0;
	transformation_matrix(2, 3) = 0.0;

	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_in为初始模型,cloud_icp为变换后的模型
	*cloud_tr = *cloud_icp;																	//cloud_tr是变换后的数据的备份用于后期的显示

	time.tic();
	pcl::IterativeClosestPoint<PointT, PointT>icp;											//实例化一个icp对象
	icp.setMaximumIterations(iterations);													//设置迭代次数
	icp.setInputSource(cloud_icp);
	icp.setInputTarget(cloud_in);
	icp.align(*cloud_icp);
	icp.setMaximumIterations(1);
	std::cout << "Applied" << iterations << "ICP iterations in " << time.toc() << "ms" << std::endl;
	if (icp.hasConverged())																	//当或许ICP配准再被调用时,每次调用迭代次数都与这里的参数一致
	{
		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);
	}
	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;															//使用到的颜色为黑色
	float txt_gray_1v1 = 1.0 - bckgr_gray_level;

	pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_in_color_h(cloud_in, (int)255 * txt_gray_1v1, (int)255 * txt_gray_1v1, (int)255 * txt_gray_1v1);

	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);					//设置初始转换后的颜色

	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);					//设置icp配准后的颜色

	viewer.addText("White: original point cloud\nGreen: Matrix transformed point cloud", 10, 15, 16, txt_gray_1v1, txt_gray_1v1, txt_gray_1v1, "icp_info_1", v1);
	viewer.addText("White: original point cloud\nRed: ICP aligned point cloud", 10, 15, 16, txt_gray_1v1, txt_gray_1v1, txt_gray_1v1, "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_1v1, txt_gray_1v1, txt_gray_1v1, "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)																	//开始进行icp配准
		{
			time.tic();
			icp.align(*cloud_icp);
			std::cout << "Applied 1 ICP iteration in " << time.toc() << "ms" << std::endl;
			if (icp.hasConverged())
			{
				printf("\033[11A");
				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>();
				print4x4Matrix(transformation_matrix);										//输出初始矩阵和当前矩阵的转换矩阵

				ss.str("");
				ss << iterations;
				std::string iterations_cnt = "ICP iterations = " + ss.str();
				viewer.updateText(iterations_cnt, 10, 60, 16, txt_gray_1v1, txt_gray_1v1, txt_gray_1v1, "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);
}

三、编译结果

    1)迭代次数为1次时:

    2)迭代次数为8次时:

    3)迭代次数为20次时:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值