前言

在计算机视觉领域广泛的使用各种不同的采样一致性参数估计算法用于排除错误的样本,样本不同对应的应用不同。

功能
例如 剔除错误的配准点对,分割出处在模型上的点集

PCL中以随机采样一致性算法(RANSAC)为核心,同时实现了五种类似与随机采样一致形算法的随机参数估计算法,例如随机采样一致性算法(RANSAC)最大似然一致性算法(MLESAC),最小中值方差一致性算法(LMEDS)等,所有估计参数算法都符合一致性原则。

点云分割方面 :根据设定的不同的几个模型,估计对应的几何参数模型的参数,在一定容许的范围内分割出在模型上的点云。

目前PCL中支持的几何模型分割有空间平面、直线、二维或三维圆周、圆球、椎体等。

随机采样一致性算法(RANSAC)

定义: RANSAC是“RANdom SAmple Consensus(随机抽样一致)”的缩写,是一种随机参数估计算法

原理: RANSAC从样本中随机抽选出一个样本子集,使用最小方差估计算法对这个子集计算模型参数,然后计算所有样本与该模型的偏差,再使用一个预先设定好的阈值与偏差比较,当偏差小于阈值时,该样本点属于模型内样本点,否则为外点

问题: 主要有三个问题:1、阈值不好确定 2、不能预知迭代的确定次数 3、只能从一个特点数据中估计一个模型

PCL中的 Sample_consensus 模块

PCL中的 Sample_consensus库实现了随机采样一致性及其泛化估计算法,以及例如平面、柱面等各种常见几何模型,用不同的估计算法和不同的几何模型自由结合估算点云中隐含的具体几何模型的系数,实现点云中所处的几何模型的分割。

线、平面、柱面、和球面等模型已经在PCL库中实现。

PCL中Sample_consensus模块支持的几何模型

SACMODEL_PLANE 模型

定义为平面模型

有4个参数 normal_x 、 normal_y 、normal_z(Hessian范式中的法向量坐标) 、d(常量)
ax+by+cz+d=0

分割出的内点就是 估计参数对应的平面上或平面距离在一定范围内

SACMODEL_LINE 模型

定义为 直线模型
有6个参数
point_on_line.x 、point_on_line.y、point_on_line.z 直线上一点的三维坐标
line_direction.x、line_direction.y、line_direction.z 直线方向向量

分割出的内点就是 估计参数对应的直线上或直线距离在一定范围内

其它模型之后再补充

Code

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#include <iostream>
#include <pcl/console/parse.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/sample_consensus/ransac.h>  //随机采样一致性算 ransac 算法 
#include <pcl/sample_consensus/sac_model_plane.h>//采样一致性 平面模型
#include <pcl/sample_consensus/sac_model_sphere.h>//采样一致性 球面模型
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>

需要包含的各头文件
注意:使用 随机采样一致性算法 ransac 算法 需要包含: ransac.h
使用 采样一致性 平面模型 需要包含: sac_model_plane.h
使用 采样一致性 球面模型 需要包含: sac_model_sphere.h

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /*声明点云 */
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);//处理前的原始点云
    pcl::PointCloud<pcl::PointXYZ>::Ptr final (new pcl::PointCloud<pcl::PointXYZ>);//存储局内点

声明两个点云
一个 原始点云
一个 存储局内点
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /* 填充点云数据 */
    cloud->width    = 5000;  //设置填充点云数目
    cloud->height   = 1;    //设置为无序点云
    cloud->is_dense = false;
    cloud->points.resize (cloud->width * cloud->height);//设置填充点云数目

填充点云 这块主要是设置点云大小

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    for (size_t i = 0; i < cloud->points.size (); ++i)//遍历每一个点 赋值坐标
    {
        //参数是 s 或者 sf的话  设置球体点云
        if (pcl::console::find_argument (argc, argv, "-s") >= 0 || pcl::console::find_argument (argc, argv, "-sf") >= 0)
        {
        //根据命令行参数用x^2+y^2+Z^2=1设置一部分点云数据,此时点云组成1/4个球体作为内点
        cloud->points[i].x =  rand () / (RAND_MAX + 1.0);
        cloud->points[i].y =  rand () / (RAND_MAX + 1.0);
        if (i % 5 == 0)
            cloud->points[i].z =  rand () / (RAND_MAX + 1.0);//产生局外点
        else if(i % 2 == 0)  //产生局内点  球体的上部分
            cloud->points[i].z =  sqrt( 1 - (cloud->points[i].x * cloud->points[i].x)
                                        - (cloud->points[i].y * cloud->points[i].y));
        else  //产生局内点  球体的下部分
            cloud->points[i].z =  - sqrt( 1 - (cloud->points[i].x * cloud->points[i].x)
                                            - (cloud->points[i].y * cloud->points[i].y));
        }
        else
        {
        //用x+y+z=1设置一部分点云数据,此时点云组成的菱形平面作为内点
        cloud->points[i].x =  rand () / (RAND_MAX + 1.0);
        cloud->points[i].y =  rand () / (RAND_MAX + 1.0);
        if( i % 5 == 0)//产生局外点
            cloud->points[i].z = rand () / (RAND_MAX + 1.0);
        else//产生局内点
            cloud->points[i].z = -1 * (cloud->points[i].x + cloud->points[i].y);
        }
    }

遍历每一个点 赋值坐标

根据 执行时的参数 ,创建不同的点云
有球体的点云、平面的点云
在创建的时候 分成 局内点和局外点

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    //创建随机采样一致性对象
    //针对球模型的对象  并输入点云
    pcl::SampleConsensusModelSphere<pcl::PointXYZ>::Ptr model_s(new pcl::SampleConsensusModelSphere<pcl::PointXYZ> (cloud));
    //针对平面模型的对象 并输入点云
    pcl::SampleConsensusModelPlane<pcl::PointXYZ>::Ptr model_p (new pcl::SampleConsensusModelPlane<pcl::PointXYZ> (cloud));

创建随机采样一致性对象
针对球模型的对象 并输入点云
针对平面模型的对象 并输入点云

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


     std::vector<int> inliers;  //存储局内点集合的点的索引的向量

    if(pcl::console::find_argument (argc, argv, "-f") >= 0)
    {
        //根据命令行参数,来随机估算对应平面模型,并存储估计的局内点
        pcl::RandomSampleConsensus<pcl::PointXYZ> ransac (model_p);//声明随机采样一致性对象 并把平面模型传入
        ransac.setDistanceThreshold (0.01); //与平面距离小于0.01 的点称为局内点考虑
        ransac.computeModel();//执行随机参数估计
        ransac.getInliers(inliers);//存储估计所得的局内点
    }
    else if (pcl::console::find_argument (argc, argv, "-sf") >= 0 )
    {
        //根据命令行参数  来随机估算对应的圆球模型,存储估计的内点
        pcl::RandomSampleConsensus<pcl::PointXYZ> ransac (model_s);//声明随机采样一致性对象 并把球面模型传入
        ransac.setDistanceThreshold (0.01);//与球面距离小于0.01 的点称为局内点考虑
        ransac.computeModel();//执行随机参数估计
        ransac.getInliers(inliers);//存储估计所得的局内点
    }

根据执行参数,进行 随机估算对应模型,并存储估计的局内点

可以看到基本的计算流程不同模型都是一样的
总结如下:
1、创建随机采样一致性对象(此时指定针对什么模型的对象) 并输入点云
2、声明随机采样一致性对象 并把平面模型传入
3、设置阈值
4、执行随机参数估计
5、存储估计所得的局内点

此时计算的局内点的点云索引就存在了 一个向量里面
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    // 复制估算模型的所有的局内点到final中
    pcl::copyPointCloud<pcl::PointXYZ>(*cloud, inliers, *final);

复制估算模型的所有的局内点到final中

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /*点云显示*/
    boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer;
    if (pcl::console::find_argument (argc, argv, "-f") >= 0 || pcl::console::find_argument (argc, argv, "-sf") >= 0)
        viewer = simpleVis(final);
    else
        viewer = simpleVis(cloud);

    while (!viewer->wasStopped ())
    {
        viewer->spinOnce (100);
        boost::this_thread::sleep (boost::posix_time::microseconds (100000));
    }

仅3d点云可视化
调用了simpleVis()函数 该函数功能就是显示输入的点云
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//点云显示
boost::shared_ptr<pcl::visualization::PCLVisualizer>
simpleVis (pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud)
{
  boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
  viewer->setBackgroundColor (0, 0, 0);
  viewer->addPointCloud<pcl::PointXYZ> (cloud, "sample cloud");
  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");
  //viewer->addCoordinateSystem (1.0);
  viewer->initCameraParameters ();
  return (viewer);
}

simpleVis() 函数
该函数功能就是显示输入的点云

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CMakeLists.txt

# 声明要求的 cmake 最低版本
cmake_minimum_required(VERSION 2.8 )
# 声明工程名称
project(random_sample_consensus)
# 添加c++ 11 标准支持
set(CMAKE_CXX_FLAGS "-std=c++11")
# 寻找PCL的库
find_package(PCL REQUIRED COMPONENT common io visualization filters features keypoints sample_consensus)
link_directories(${PCL_LIBRARY_DIRS})
# 添加头文件
include_directories(${PCL_INCLUDE_DIRS})
add_definitions( ${PCL_DEFINITIONS} )
#添加一个可执行程序
add_executable(random_sample_consensus random_sample_consensus.cpp)
#链接PCL 库
target_link_libraries(random_sample_consensus ${PCL_LIBRARIES})

注意 find_package() 包含 sample_consensus

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Result


创建的平面模型 包含 随机数立方体的局外 点 和 平面的局内点


通过计算得到的 平面模型 的 局内点 点云


创建的球面模型 包含 随机数立方体的局外 点 和 球面的局内点


通过计算得到的 球面模型 的 局内点 点云

应用

上面的代码究竟可以干什么呢?

通过上面的代码总结起来 可以实现的功能 比如 把点云中的球体识别出来。那识别出来做什么呢?

举一个栗子:
双深度相机标定 双激光雷达标定

在两个深度相机或者激光雷达的共同视野下,放3个球体。
那么可以通过上面的功能在各自的坐标系下识别出球体,然后求得3个圆心的坐标。
即在两组坐标系中拥有两个对应的3个坐标点,这样就可以通过变换矩阵求解方程,来估计出变换矩阵了,从而实现了双深度相机或者双激光雷达标定。