点云配准ICP算法及其变种介绍及耗时对比

目录

点云配准

ICP算法

原理推导

基于PCL的ICP代码详解

ICP代码参数设置

完整代码

ICP变种之点线PL-ICP

ICP变种之点面ICP

基础 ICP 及点面 ICP 算法耗时对比


参考:【PCL】—— 点云配准ICP(Iterative Closest Point)算法_icp点云配准-CSDN博客

点云配准

       计算出两个点云簇之间的变换矩阵,从而计算出位姿等信息,学习点云配准的目的是想要计算相邻两帧物体的点云之间的变换位姿,从而得到物体的运动信息。

ICP算法

        ICP即 Iterative Closest Point 迭代最近点法,是一种经典的点云配准方法。ICP算法需要输入两个点云,一个是目标点云,另一个是源点云,输出的是从源点云到目标点云的位姿变换。目标点云不会移动,而源点云则会在迭代的过程中不断接近目标点云,直到收敛。

        但是ICP算法很容易陷入局部最优解,通常需要目标点云和源点云在配准前有比较高的重合率,因此在执行ICP之前通常会用其他方法进行一次粗配准,来获得比较好的初值,然后再使用ICP进行精配准。

原理推导

        对于给定的点集 P = {P1, P2, P3, ... , Pn} 和 点云 Q = {qi, q2, q3, ... , qn}, 经典的ICP算法会对以下目标函数进行优化

         该目标函数的含义也比较好理解,即找到一个刚体变换 < R, t > 使得源点云 P 和 目标点云 Q 的 差异尽可能的小,那么如何衡量这两个点云簇的差异呢?经典的ICP算法会遍历 < R, t > 变换后的源点云中的所有点 {p_i}^{'},从目标点云中搜索出每一个{p_i}^{'}的最近邻点q_i,形成点对({p_i}^{'}, q_i),计算点对距离的平方,并对所有点对的距离平方求和,最和最小则认为差异最小。

        当匹配点对确定好之后,ICP的优化函数其实是一个凸函数,可以找到最优解,求解过程可以使用SVD分解,具体过程可以参考 【PCL】—— 点云配准ICP(Iterative Closest Point)算法_icp点云配准-CSDN博客

基于PCL的ICP代码详解

PCL点云库已经实现了多种点云配准算法,结合pcl,本次配准的主要目的是:

  1. 对PCL中ICP算法进行一些注解
  2. 创建可视化窗口,通过设置键盘回调函数,控制迭代过程,观察ICP算法的计算过程

PCL中的ICP算法是基于SVD(Singular Value Decomposition)实现的.

PCL中ICP的官方参考文档 http://pointclouds.org/docume...

ICP代码参数设置

使用pcl的ICP之前要设置几个参数

//创建ICP的实例类
pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
// 设置源点云
icp.setInputSource(cloud_sources);
// 设置目标点云
icp.setInputTarget(cloud_target);
// 设置对应点对之间的最大距离,影响结果的精度
icp.setMaxCorrespondenceDistance(100); 
// 设置两次变化矩阵之间的差值(一般设置为1e-10即可) 
icp.setTransformationEpsilon(1e-10); 
// 设置收敛条件是均方误差和小于阈值, 停止迭代
icp.setEuclideanFitnessEpsilon(0.001); 
// 设置最大迭代次数
icp.setMaximumIterations(100);   
icc.align(final);

完整代码

CMakeLists.txt

cmake_minimum_required(VERSION 2.6)
project(TEST)

find_package(PCL REQUIRED)

include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})

add_executable(TEST icp_test.cpp)

target_link_libraries (TEST ${PCL_LIBRARIES})

install(TARGETS TEST RUNTIME DESTINATION bin)

运行方法:在代码cpp文件所在目录新建build文件夹

mkdir build && cd build
cmake ..
make
./TEST

算法C++代码

#include <string>
#include <pcl/io/pcd_io.h>
#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>   // TicToc
 
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));
}
/**
 * 此函数是查看器的回调。 当查看器窗口位于顶部时,只要按任意键,就会调用此函数。 如果碰到“空格”; 将布尔值设置为true。
 * @param event
 * @param nothing
 */
void
keyboardEventOccurred (const pcl::visualization::KeyboardEvent& event,
                       void* nothing)
{
    if (event.getKeySym () == "space" && event.keyDown ())
        next_iteration = true;
}

int
main (int argc,
      char* argv[])
{
    // The point clouds we will be using
    PointCloudT::Ptr cloud_in (new PointCloudT);  // Original point cloud
    PointCloudT::Ptr cloud_tr (new PointCloudT);  // Transformed point cloud
    PointCloudT::Ptr cloud_icp (new PointCloudT);  // ICP output point cloud

//    我们检查程序的参数,设置初始ICP迭代的次数,然后尝试加载PCD文件。
    // Checking program arguments
    if (argc < 2)
    {
        printf ("Usage :\n");
        printf ("\t\t%s file.pcd number_of_ICP_iterations\n", argv[0]);
        PCL_ERROR ("Provide one pcd file.\n");
        return (-1);
    }

    int iterations = 1;  // Default number of ICP iterations
    if (argc > 2)
    {
        // If the user passed the number of iteration as an argument
        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::loadPCDFile (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;

    // 我们使用刚性矩阵变换来变换原始点云。
    // cloud_in包含原始点云。
    // cloud_tr和cloud_icp包含平移/旋转的点云。
    // cloud_tr是我们将用于显示的备份(绿点云)。

    // Defining a rotation matrix and translation vector
    Eigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity ();

    // A rotation matrix (see https://en.wikipedia.org/wiki/Rotation_matrix)
    double theta = M_PI / 8;  // The angle of rotation in radians
    transformation_matrix (0, 0) = std::cos (theta);
    transformation_matrix (0, 1) = -sin (theta);
    transformation_matrix (1, 0) = sin (theta);
    transformation_matrix (1, 1) = std::cos (theta);

    // A translation on Z axis (0.4 meters)
    transformation_matrix (2, 3) = 0.4;

    // Display in terminal the transformation matrix
    std::cout << "Applying this rigid transformation to: cloud_in -> cloud_icp" << std::endl;
    print4x4Matrix (transformation_matrix);

    // Executing the transformation
    pcl::transformPointCloud (*cloud_in, *cloud_icp, transformation_matrix);
    *cloud_tr = *cloud_icp;  // We backup cloud_icp into cloud_tr for later use

    // 这是ICP对象的创建。 我们设置ICP算法的参数。
    // setMaximumIterations(iterations)设置要执行的初始迭代次数(默认值为1)。
    // 然后,我们将点云转换为cloud_icp。 第一次对齐后,我们将在下一次使用该ICP对象时(当用户按下“空格”时)将ICP最大迭代次数设置为1。

    // The Iterative Closest Point algorithm
    time.tic ();
    pcl::IterativeClosestPoint<PointT, PointT> icp;
    icp.setMaximumIterations (iterations);
    icp.setInputSource (cloud_icp);
    icp.setInputTarget (cloud_in);
    icp.align (*cloud_icp);
    icp.setMaximumIterations (1);  // We set this variable to 1 for the next time we will call .align () function
    std::cout << "Applied " << iterations << " ICP iteration(s) in " << time.toc () << " ms" << std::endl;

    // 检查ICP算法是否收敛; 否则退出程序。 如果返回true,我们将转换矩阵存储在4x4矩阵中,然后打印刚性矩阵转换。
    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);
    }

    // Visualization
    pcl::visualization::PCLVisualizer viewer ("ICP demo");
    // Create two vertically separated viewports
    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);

    // The color we will be using
    float bckgr_gray_level = 0.0;  // Black
    float txt_gray_lvl = 1.0 - bckgr_gray_level;

    // Original point cloud is white
    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);

    // Transformed point cloud is green
    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 aligned point cloud is red
    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);

    // Adding text descriptions in each viewport
    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);

    // Set background color
    viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v1);
    viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v2);

    // Set camera position and orientation
    viewer.setCameraPosition (-3.68332, 2.94092, 5.71266, 0.289847, 0.921947, -0.256907, 0);
    viewer.setSize (1280, 1024);  // Visualiser window size

    // Register keyboard callback :
    // 如果用户按下空格键,则将全局布尔值next_iteration设置为true
    viewer.registerKeyboardCallback (&keyboardEventOccurred, (void*) NULL);

    // Display the visualiser
    while (!viewer.wasStopped ())
    {
        viewer.spinOnce ();

        // The user pressed "space" :
        if (next_iteration)
        {
            // The Iterative Closest Point algorithm
            time.tic ();      
            // icp.align 对于通过矩阵转换后的源点云A1,查找其每一个点在目前点云中的最近邻点
            // 并将目标点云的顺序调整为与A1对应的顺序,将调整对齐后的目标点云保存为 cloud_icp

            icp.align (*cloud_icp);
            std::cout << "Applied 1 ICP iteration in " << time.toc () << " ms" << std::endl;

            // 和以前一样,我们检查ICP是否收敛,如果不收敛,则退出程序。
            if (icp.hasConverged ())
            {
                // printf(“ 033 [11A”); 在终端增加11行以覆盖显示的最后一个矩阵是一个小技巧。
                // 简而言之,它允许替换文本而不是编写新行; 使输出更具可读性。 我们增加迭代次数以更新可视化器中的文本值。
                printf ("\033[11A");  // Go up 11 lines in terminal output.
                printf ("\nICP has converged, score is %+.0e\n", icp.getFitnessScore ());

                // 这意味着,如果您已经完成了10次迭代,则此函数返回矩阵以将点云从迭代10转换为11。
                std::cout << "\nICP transformation " << ++iterations << " : cloud_icp -> cloud_in" << std::endl;

                // 函数getFinalTransformation()返回在迭代过程中完成的刚性矩阵转换(此处为1次迭代)。
                transformation_matrix *= icp.getFinalTransformation ().cast<double>();  // WARNING /!\ This is not accurate! For "educational" purpose only!
                print4x4Matrix (transformation_matrix);  // Print the transformation between original pose and current pose

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

ICP变种之点线PL-ICP

参考:https://zhuanlan.zhihu.com/p/506823350#:~:text=CP%E7%AE%97%E6%B3%95.%20%E5%AF%B9%E4%BA%8Epo

代码:

        上述介绍的ICP算法特指 点到点的ICP,即 point-to-point ICP,但是点到点的ICP算存在以下缺点:

  1. 依赖初始值,初始值不好时,迭代次数增加;
  2. 对于较大的初始误差,可能会出现错误的迭代结果;
  3. ICP是一阶收敛,收敛速度慢(为了弥补这一点,通常使用K-D树加快搜索);
  4. 存在离群点及噪声。

        为此改善上述缺点,有研究者提出了PL-ICP,顾名思义,这种方式使用源点云到目标点云直线的距离度量来估计变换。主要区别在于误差函数的构建上。ICP是找最近邻的一点,以点与点之间的距离作为误差,而PLICP是找到最近邻的两点,两点连线,是以点到线的距离作为误差,实际上,后者的误差度量方式更符合结构化场景中的雷达点云的实际情况。因此具有更小的误差(图2)。然而,它对非常大的初始位移误差的鲁棒性较差,因此需要比较精确的初始值。

图2 点到线度量比普通 ICP 中使用的点到点度量更接近表面的距离

点到线的误差函数写为:

        n_s 为目标点云中匹配到的最近两个点对应直线的法线。

ICP变种之点面ICP

完整代码:运行后看不到点云可以缩小一下视图

#include <string>
#include <pcl/io/pcd_io.h>
#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>   // TicToc
#include <time.h>   // or  #include <ctime>
#include <iostream>   // or  #include <ctime>


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));
}
/**
 * 此函数是查看器的回调。 当查看器窗口位于顶部时,只要按任意键,就会调用此函数。 如果碰到“空格”; 将布尔值设置为true。
 * @param event
 * @param nothing
 */
void
keyboardEventOccurred (const pcl::visualization::KeyboardEvent& event,
                       void* nothing)
{
    if (event.getKeySym () == "space" && event.keyDown ())
        next_iteration = true;
}

int
main (int argc,
      char* argv[])
{
    // The point clouds we will be using
    PointCloudT::Ptr cloud_in (new PointCloudT);  // Original point cloud
    PointCloudT::Ptr cloud_tr (new PointCloudT);  // Transformed point cloud
    PointCloudT::Ptr cloud_icp (new PointCloudT);  // ICP output point cloud

//    我们检查程序的参数,设置初始ICP迭代的次数,然后尝试加载PCD文件。
    // Checking program arguments
    // if (argc < 2)
    // {
    //     printf ("Usage :\n");
    //     printf ("\t\t%s file.pcd number_of_ICP_iterations\n", argv[0]);
    //     PCL_ERROR ("Provide one pcd file.\n");
    //     return (-1);
    // }

    int iterations = 1;  // Default number of ICP iterations
    // if (argc > 2)
    // {
    //     // If the user passed the number of iteration as an argument
    //     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 ();
    
    std::string pc_f17 = "1.pcd";
    std::string pc_f18 = "2.pcd";

    // if (pcl::io::loadPCDFile(argv[1], *cloud_in) < 0)
    PointCloudT::Ptr cloud_in_raw (new PointCloudT);  // Original point cloud
    if (pcl::io::loadPCDFile(pc_f17, *cloud_in_raw) < 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;

    // std::cout << "----------------\n";
    printf("----------------\n");
    printf("\nLoaded file %d, time: %f\n", (int)cloud_in_raw->size(), time.toc ());

    for (int i = 0; i < (int)cloud_in_raw->size(); ++i)
    {
        auto pi = cloud_in_raw->points[i];
        if (pi.z > 0.2 && pi.z < 0.8 && i % 4 == 0)
        // if (i % 2 == 0)

        {
            cloud_in->points.push_back(pi);
        }
    }

    printf("============cloud_in size: %d \n", (int)cloud_in->size());


    // 我们使用刚性矩阵变换来变换原始点云。
    // cloud_in包含原始点云。
    // cloud_tr和cloud_icp包含平移/旋转的点云。
    // cloud_tr是我们将用于显示的备份(绿点云)。

    // Defining a rotation matrix and translation vector
    Eigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity ();

    // A rotation matrix (see https://en.wikipedia.org/wiki/Rotation_matrix)
    // x,y平面上旋转 PI / 8, z方向平移0.4
    double theta = M_PI / 8;  // The angle of rotation in radians
    transformation_matrix (0, 0) = std::cos (theta);
    transformation_matrix (0, 1) = -sin (theta);
    transformation_matrix (1, 0) = sin (theta);
    transformation_matrix (1, 1) = std::cos (theta);

    // A translation on Z axis (0.4 meters)
    transformation_matrix (2, 3) = 0.4;

    // Display in terminal the transformation matrix
    std::cout << "Applying this rigid transformation to: cloud_in -> cloud_icp" << std::endl;
    print4x4Matrix (transformation_matrix);

    // Executing the transformation
    // pcl::transformPointCloud (*cloud_in, *cloud_icp, transformation_matrix);
    PointCloudT::Ptr cloud_icp_raw (new PointCloudT);  // Original point cloud
    if (pcl::io::loadPCDFile(pc_f18, *cloud_icp_raw) < 0)

    {
        PCL_ERROR ("Error loading cloud %s.\n", argv[1]);
        return (-1);
    }

    for (int i = 0; i < (int)cloud_icp_raw->size(); ++i)
    {
        auto pi = cloud_icp_raw->points[i];
        if (pi.z > 0.2 && pi.z < 0.8 && i % 4 == 0)
        // if (i % 2 == 0)
        {
            cloud_icp->points.push_back(pi);
        }
    }

    printf("============cloud_icp size: %d \n", (int)cloud_icp->size());

    *cloud_tr = *cloud_icp;  // We backup cloud_icp into cloud_tr for later use

    // 这是ICP对象的创建。 我们设置ICP算法的参数。
    // setMaximumIterations(iterations)设置要执行的初始迭代次数(默认值为1)。
    // 然后,我们将点云转换为cloud_icp。 第一次对齐后,我们将在下一次使用该ICP对象时(当用户按下“空格”时)将ICP最大迭代次数设置为1。

    // The Iterative Closest Point algorithm
    time.tic ();
    pcl::IterativeClosestPoint<PointT, PointT> icp;
    icp.setMaximumIterations (iterations);
    icp.setInputSource (cloud_icp);
    icp.setInputTarget (cloud_in);
    icp.align (*cloud_icp);
    icp.setMaximumIterations (1);  // We set this variable to 1 for the next time we will call .align () function
    std::cout << "-------------------Applied " << iterations << " ICP iteration(s) in " << time.toc () << " ms" << std::endl;

    // 检查ICP算法是否收敛; 否则退出程序。 如果返回true,我们将转换矩阵存储在4x4矩阵中,然后打印刚性矩阵转换。
    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);
    }

    // Visualization
    pcl::visualization::PCLVisualizer viewer ("ICP demo");
    // Create two vertically separated viewports
    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);

    // The color we will be using
    float bckgr_gray_level = 0.0;  // Black
    float txt_gray_lvl = 1.0 - bckgr_gray_level;

    // Original point cloud is white
    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);

    // Transformed point cloud is green
    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 aligned point cloud is red
    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);

    // Adding text descriptions in each viewport
    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);

    // Set background color
    viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v1);
    viewer.setBackgroundColor (bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v2);

    // Set camera position and orientation
    viewer.setCameraPosition (-3.68332, 2.94092, 5.71266, 0.289847, 0.921947, -0.256907, 0);
    viewer.setSize (1280, 1024);  // Visualiser window size

    // Register keyboard callback :
    // 如果用户按下空格键,则将全局布尔值next_iteration设置为true
    viewer.registerKeyboardCallback (&keyboardEventOccurred, (void*) NULL);

    // Display the visualiser
    while (!viewer.wasStopped ())
    {
        viewer.spinOnce();

        // The user pressed "space" :
        if (next_iteration)
        {
            // The Iterative Closest Point algorithm
            time.tic ();
            // icp.align 对于通过矩阵转换后的源点云A1,查找其每一个点在目前点云中的最近邻点
            // 并将目标点云的顺序调整为与A1对应的顺序,将调整对齐后的目标点云保存为 cloud_icp
            icp.align (*cloud_icp);
            std::cout << "Applied 1 ICP iteration in " << time.toc () << " ms" << std::endl;
            printf("Applied 1 ICP iteration in %f ms \n", time.toc ());


            // 和以前一样,我们检查ICP是否收敛,如果不收敛,则退出程序。
            if (icp.hasConverged ())
            {
                // printf(“ 033 [11A”); 在终端增加11行以覆盖显示的最后一个矩阵是一个小技巧。
                // 简而言之,它允许替换文本而不是编写新行; 使输出更具可读性。 我们增加迭代次数以更新可视化器中的文本值。
                printf ("\033[11A");  // Go up 11 lines in terminal output.
                printf ("\nICP has converged, score is %+.0e\n", icp.getFitnessScore ());

                // 这意味着,如果您已经完成了10次迭代,则此函数返回矩阵以将点云从迭代10转换为11。
                std::cout << "\nICP transformation " << ++iterations << " : cloud_icp -> cloud_in" << std::endl;

                // 函数getFinalTransformation()返回在迭代过程中完成的刚性矩阵转换(此处为1次迭代)。
                transformation_matrix *= icp.getFinalTransformation ().cast<double>();  // WARNING /!\ This is not accurate! For "educational" purpose only!
                print4x4Matrix (transformation_matrix);  // Print the transformation between original pose and current pose

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

基础 ICP 及点面 ICP 算法耗时对比

设备信息:处理器:12th Gen Intel® Core™ i7-1260P × 16   显卡:Mesa Intel® Graphics (ADL GT2)

前后帧点云信息:点云位置和位姿前后帧差距不大,位置相差最多 30cm 左右 

物体类型

降采样后点数

点云降采样倍数

ICP总耗时

点面ICP耗时
行人

96 和 198

2约 2 ms
每次迭代 0.37 ms
迭代 5 次
约 45 ms
第一次迭代耗时45ms,一次的精度尚可
后续迭代耗时类似
大车都820左右11约 30 ms
每次迭代 1.6 ms
迭代 20 次
381ms + 80ms
第一次迭代 381ms
第二次迭代 80ms。2次后精度较好
车辆

135 和 461

11约 1.2 ms
每次迭代 0.4 ms
迭代 3 次
约 17  ms
一次迭代约 17 ms,一次的精度尚可
后续迭代耗时类似

这种耗时下,每帧物体较多时,想要实时运行,几乎不太可能,耗时还是较大

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值