0. 简介

PCL作为目前最为强大的点云库,内部存在有大量集成好的算法。而对于数据量大、非同源、含大量噪声且部分重叠的激光点云与影像重建点云,其稀疏程度、噪声程度等不同,非重叠区域的面积很大。真实场景的点云尤其是影像重建点云噪声较多,提取的法向量误差也很大,有的时候NDT和ICP并不能形成良好的匹配,这个时候我们该怎么样评估通过IPC或NDT算出的变换矩阵来估算出算法的精度呢?这个时候就需要通过均方根误差以及重合度来综合评判结果了。

1. 从Cloudcompare对点云配准进行了解

Cloudcompare是一个开源的免费点云处理软件,可以实现常用的点云处理功能,使用也是简单方便。官网网址为http://www.cloudcompare.org/,由于其良好的可视化,这里我们来从这个软件开始,向读者来介绍配准的一些基础概念。
首先,我们打开安装好的cloudcompare,打开要配准的点云文件。这里使用斯坦福的兔子点云
选中45°点云,改变颜色(edit–>colors–>set unique,或者快捷键alt+c)
在这里插入图片描述
选中两个点云,点击配准按钮,参考点云(reference)选择0°,重叠度改成70%(不同数据视情况而定,这里目测70%重叠差不多),精度(RMS difference)没改变。
在这里插入图片描述
在这里插入图片描述
此时,我们就可以快捷的拿到我们想要的配准结果了。在上面我们注意到在Cloudcompare中提到了两个重要的概念,分别是重叠度和精度(均方根误差)。而这也是我们下面文章中主要介绍的内容。

2. 均方根误差(RMSE)

下面这部分的内容是计算方根误差的,其中内部主要是通过kd-tree完成了dist的搜索,从而计算出最近邻匹配点对欧氏距离的平方,并最终拿到我们的RMSE,从而评判出点云的重合性。

float caculateRMSE(pcl::PCLPointCloud2::Ptr& cloud_source, pcl::PCLPointCloud2::Ptr& cloud_target)
{
    pcl::PointCloud<pcl::PointXYZ>::Ptr xyz_source(new pcl::PointCloud<pcl::PointXYZ>());
    fromPCLPointCloud2(*cloud_source, *xyz_source);
    pcl::PointCloud<pcl::PointXYZ>::Ptr xyz_target(new pcl::PointCloud<pcl::PointXYZ>());
    fromPCLPointCloud2(*cloud_target, *xyz_target);

    float rmse = 0.0f;

    pcl::KdTreeFLANN<pcl::PointXYZ>::Ptr tree(new pcl::KdTreeFLANN<pcl::PointXYZ>());
    tree->setInputCloud(xyz_target);

    for (auto point_i : *xyz_source)
    {
        // 去除无效的点
        if (!pcl_isfinite(point_i.x) || !pcl_isfinite(point_i.y) || !pcl_isfinite(point_i.z))
            continue; 
        pcl::Indices nn_indices(1);
        std::vector<float> nn_distances(1);
        if (!tree->nearestKSearch(point_i, 1, nn_indices, nn_distances)) // K近邻搜索获取匹配点对
            continue;
        /*dist的计算方法之一
        size_t point_nn_i = nn_indices.front();
        float dist = squaredEuclideanDistance(point_i, xyz_target->points[point_nn_i]);
        */

        float dist = nn_distances[0]; // 获取最近邻对应点之间欧氏距离的平方
        rmse += dist;                 // 计算平方距离之和
    }
    rmse = std::sqrt(rmse / static_cast<float> (xyz_source->points.size())); // 计算均方根误差

    return rmse;
}

// ---------------------------计算均方根误差------------------------------
//auto Rmse= caculateRMSE(cloud_source, cloud_target);
//cout << "配准误差为:" << Rmse << endl;

3. 重合率计算

下面的代码提供了两种方法计算匹配的区域的点云的重合率,分别使用CorrespondenceEstimation以及kd-tree来实现了点云重合率的计算,当然现在的PCL算法已经自带了均方误差以及变换矩阵和当前变换矩阵的差性等情况,但是这也不妨碍我们通过RMSE以及重合率来单帧分析问题所在

//点云重合率计算
float calaPointCloudCoincide(pcl::PointCloud::Ptr cloud_src, pcl::PointCloud::Ptr cloud_target, float para1)
{
    pcl::registration::CorrespondenceEstimation<pcl::PointXYZ, pcl::PointXYZ> core;
    core.setInputSource(cloud_src);
    core.setInputTarget(cloud_target);

    boost::shared_ptr<pcl::Correspondences> cor(new pcl::Correspondences);   //共享所有权的智能指针,以kdtree做索引

    core.determineReciprocalCorrespondences(*cor, para1);   //点之间的最大距离,cor对应索引
    cout << "点云原来的数量:" << cloud_target->size() << endl;
    cout << "重合的点云数: " << cor->size() << endl;
    cout << "重合率: " << float(cor->size()/ cloud_target->size())*100<< "%"<< endl;

    // 方法2
    //构造重叠点云的PCD格式文件
    pcl::PointCloud<pcl::PointXYZ>::Ptr overlapA;
    pcl::PointCloud<pcl::PointXYZ>::Ptr overlapB;

    overlapA->width = cor->size();
    overlapA->height = 1;
    overlapA->is_dense = false;
    overlapA->points.resize(overlapA.width*overlapA.height);
    pcl::copyPointCloud(*overlapA, *overlapB);

    pcl::copyPointCloud(*cloud_src, *overlapA);
    pcl::copyPointCloud(*cloud_target, *overlapB);

    pcl::KdTreeFLANN<pcl::PointXYZ>::Ptr tree(new pcl::KdTreeFLANN<pcl::PointXYZ>());
    tree->setInputCloud(overlapB);

    double num = 0;
    for (auto point_i : *overlapA)
    {
        // 去除无效的点
        if (!pcl_isfinite(point_i.x) || !pcl_isfinite(point_i.y) || !pcl_isfinite(point_i.z))
            continue; 
        pcl::Indices nn_indices(1);
        std::vector<float> nn_distances(1);
        if (!tree->nearestKSearch(point_i, 1, nn_indices, nn_distances)) // K近邻搜索获取匹配点对
            continue;
        float dist = sqrt(pow(point_i.x - overlapB->points[point_nn_i].x, 2) + 
            pow(point_i.y - overlapB->points[point_nn_i].points[i].y, 2) +
            pow(point_i.z - overlapB->points[point_nn_i].points[i].z, 2));
        rmse += dist;                 // 计算平方距离之和
        if (dist < para1)
            num++;
    }

    cout << "重叠区域的点云数:" << num << endl;
    cout << "重合率: " << float(num / overlapA->size())*100<< "%"<< endl;
    return float(num / overlapA->size());
}
// ---------------------------计算重合度------------------------------
//auto Coincide= calaPointCloudCoincide(cloud_source, cloud_target);
//cout << "重合度为:" << Coincide<< endl;

4. 参考链接

https://zhuanlan.zhihu.com/p/457508984

https://www.i4k.xyz/article/qq_41102371/111713066

https://its201.com/article/weixin_39484607/112981401

https://its401.com/article/qq_41102371/111755559

https://www.opticsjournal.net/Articles/OJe743254e97b145b3/FullText