ROS::点云PCL(6)随机采样一致性算法RANSAC *

场景

平面模型经常被应用到常见的室内平面分割提取中,比如墙、地板、桌面,其他模型常应用到根据几何结构检测识别和分割物体(比如用一个圆柱体模型分割出一个杯子)。

可用模型

在这里插入图片描述
这些模型有圆柱、线、圆等基本的一些形状可以直接使用,通过这些模型去分割点云。

RANSAC概念及作用

RANSAC是“RANdom SAmple Consensus”(随机抽样共识或采样一致性)的缩写,它是一种迭代方法,用于从包含异常值的一组数据中估计数学模型的参数。该算法由Fischler和Bolles于1981年发布。

RANSAC算法假定我们要查看的所有数据均由内部值和异常值组成。可以用带有一组特定参数值的模型来解释离群值,而离群值在任何情况下都不适合该模型。其过程可以从数据中估计所选模型的最佳参数。

左图和右图(来自[Wikipedia])显示了RANSAC算法在二维数据集上的简单应用。我们左边的图像是包含内部值和异常值的数据集的可视表示。右边的图像以红色显示所有异常值,以蓝色显示内部值。蓝线是RANSAC完成的工作的结果。在这种情况下,我们尝试拟合数据的模型是一条直线,看起来很适合我们的数据
在这里插入图片描述
在这里插入图片描述
PCL 中以随机采样一致性算法( RANSAC) 为核心,实现了五种类似于RANSAC的随机参数估计算法,例如随机采样一致性估计(RANSAC ) 、最大似然一致性估计 (MLESAC ) 、最小中值方差一致性估计 ( LMEDS )等,所有的估计参数算法都符合一致性准则。利用RANSAC可以实现点云分割,目前 PCL 中支持的几何模型分割有 空间平面、直线、二维或三维圆、圆球、锥体等 。 RANSAC的另一应用就是点云的配准对的剔除。

RANSAC算法简介

RANSAC从样本中随机抽选出一个样本子集,使用最小方差估计算法对这个子集计算模型参数,然后计算所有样本与该模型的偏差,再使用一个预先设定好的阈值与偏差比较,当偏差小于阈值时,该样本点属于模型内样本点 ( inliers),或称内部点、局内点或内点,否则为模型外样本点(outliers),或称外部点、局外点或外点,记录下当前的 inliers 的个数,然后重复这一过程。每一次重复都记录当前最佳的模型参数,所谓最佳即是inliers的个数最多 ,此时对应的inliers个数为 best_ninliers 。 每次迭代的末尾都会根据期望的误差率、 best_ninliers、总样本个数、当前迭代次数,计算一 个迭代结束评判因子,据此决定是否迭代结束。迭代结束后,最佳模型参数就是最终的模型参数估计值 。

RANSAC理论上可以剔除outliers的影响,并得到全局最优的参数估计。但是RANSAC 有两个问题,首先在每次迭代中都要区分 inliers 和 outlieres,因此需要事先设定阈值,当模型具有明显的物理意义时,这个阈值还比较容易设定,但是若模型比较抽象时,阈值就不那么容易设定了。而且固定阈值不适用于样本动态变化的应用;第二个问题是,RANSAC的迭代次数是运行期决定的,不能预知迭代的确切次数(当然迭代次数的范围是可以预测的)。除此之外, RANSAC 只能从一个特定数据集中估计一个模型,当两个(或者更多个)模型存在时,RANSAC 同时找到多个模型。

下面分别演示平面分割合球体分割

#include <typeinfo>
#include "boost/range.hpp"
#include <ros/ros.h> 
#include <pcl/point_cloud.h> 
#include <pcl_conversions/pcl_conversions.h> 
#include <sensor_msgs/PointCloud2.h> 

#include <pcl/io/io.h>
#include <pcl/io/pcd_io.h>

#include <iostream>
#include <thread>

#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>
#include <pcl/sample_consensus/sac_model_plane.h>
#include <pcl/sample_consensus/sac_model_sphere.h>

main (int argc, char **argv) 
{

    ros::init (argc, argv, "pcl_create"); 

    ros::NodeHandle nh; 
    ros::Publisher pcl_pub = nh.advertise<sensor_msgs::PointCloud2> ("pcl_output", 1);  
   //分割前
	pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGBA>);
   //分割后
    pcl::PointCloud<pcl::PointXYZRGBA>::Ptr finalRes(new pcl::PointCloud<pcl::PointXYZRGBA>);

    // populate our PointCloud with points
    cloud->width = 500;
    cloud->height = 1;
    cloud->is_dense = false;
    cloud->points.resize(cloud->width * cloud->height);
    for (std::size_t i = 0; i < cloud->points.size(); ++i) {
 		cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0);
        cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0);
		cloud->points[i].r=0;
		cloud->points[i].g=255;
		cloud->points[i].b=0;
		if (i % 2 == 0){
			cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0);
			cloud->points[i].r=0;
			cloud->points[i].g=255;
			cloud->points[i].b=0;
		}else{
			cloud->points[i].z = -1 * (cloud->points[i].x + cloud->points[i].y);
			cloud->points[i].r=0;
			cloud->points[i].g=255;
			cloud->points[i].b=0;
		}
	}

	std::cout << boost::size(cloud->points) << std::endl;
	std::vector<int> inliers;

   
	//创建随机采样一致性对象    
    // 平面
    pcl::SampleConsensusModelPlane<pcl::PointXYZRGBA>::Ptr model_p(new pcl::SampleConsensusModelPlane<pcl::PointXYZRGBA>(cloud));

	//平面模型
	pcl::RandomSampleConsensus<pcl::PointXYZRGBA> ransac(model_p);
	ransac.setDistanceThreshold(.01); //与平面距离小于0.01的点作为局内点考虑
	ransac.computeModel();            //执行随机参数估计
 	ransac.getInliers(inliers);       //存储估计所得的局内点

	// 将cloud中指定索引的点拷贝到final点云中
    pcl::copyPointCloud(*cloud, inliers, *finalRes);
	std::cout << "ok" << std::endl;
	std::cout << boost::size(cloud->points) << std::endl;
	std::cout << boost::size(inliers) << std::endl;
	std::cout << boost::size(finalRes->points) << std::endl;

	for (int i=0;i<inliers.size();i++){
		std::cout<<inliers.at(i)<<"   "<<std::endl;
		cloud->points[inliers.at(i)].r=255;
		cloud->points[inliers.at(i)].g=0;
		cloud->points[inliers.at(i)].b=0;
	}
	
	std::cout<<"done...."<<std::endl;

	sensor_msgs::PointCloud2 output;
	pcl::toROSMsg(*cloud, output); 
    output.header.frame_id = "odom"; 
    ros::Rate loop_rate(1); 

    while (ros::ok()) { 
		pcl_pub.publish(output);
		ros::spinOnce(); 
		loop_rate.sleep(); 
    } 

    return 0;

}



分割前
在这里插入图片描述
分割后
在这里插入图片描述

#include <typeinfo>
#include "boost/range.hpp"
#include <ros/ros.h> 
#include <pcl/point_cloud.h> 
#include <pcl_conversions/pcl_conversions.h> 
#include <sensor_msgs/PointCloud2.h> 

#include <pcl/io/io.h>
#include <pcl/io/pcd_io.h>

#include <iostream>
#include <thread>

#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>
#include <pcl/sample_consensus/sac_model_plane.h>
#include <pcl/sample_consensus/sac_model_sphere.h>


main (int argc, char **argv) 
{

    ros::init (argc, argv, "pcl_create"); 

    ros::NodeHandle nh; 
    ros::Publisher pcl_pub = nh.advertise<sensor_msgs::PointCloud2> ("pcl_output", 1);  

	pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGBA>);
    pcl::PointCloud<pcl::PointXYZRGBA>::Ptr finalRes(new pcl::PointCloud<pcl::PointXYZRGBA>);

    // populate our PointCloud with points
    cloud->width = 500;
    cloud->height = 1;
    cloud->is_dense = false;
    cloud->points.resize(cloud->width * cloud->height);
    for (std::size_t i = 0; i < cloud->points.size(); ++i) {
 		cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0);
        cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0);
		cloud->points[i].r=0;
		cloud->points[i].g=255;
		cloud->points[i].b=0;
            if (i % 5 == 0){     // 可能会散落在球体外
                cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0);
				cloud->points[i].r=0;
				cloud->points[i].g=255;
				cloud->points[i].b=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));
				cloud->points[i].r=0;
				cloud->points[i].g=255;
				cloud->points[i].b=0;
			}else {// 在球体负方向内
                cloud->points[i].z = -sqrt(1 - (cloud->points[i].x * cloud->points[i].x)
                                           - (cloud->points[i].y * cloud->points[i].y));
				cloud->points[i].r=0;
				cloud->points[i].g=255;
				cloud->points[i].b=0;
			}
    }


	std::cout << boost::size(cloud->points) << std::endl;
	std::vector<int> inliers;

   
	//创建随机采样一致性对象    
	// 圆形
    pcl::SampleConsensusModelSphere<pcl::PointXYZRGBA>::Ptr model_s(new pcl::SampleConsensusModelSphere<pcl::PointXYZRGBA>(cloud));


	//平面模型
	pcl::RandomSampleConsensus<pcl::PointXYZRGBA> ransac(model_s);
	ransac.setDistanceThreshold(.01); //与平面距离小于0.01的点作为局内点考虑
	ransac.computeModel();            //执行随机参数估计
 	ransac.getInliers(inliers);       //存储估计所得的局内点

	// 将cloud中指定索引的点拷贝到final点云中
    pcl::copyPointCloud(*cloud, inliers, *finalRes);
	std::cout << "ok" << std::endl;
	std::cout << boost::size(cloud->points) << std::endl;
	std::cout << boost::size(inliers) << std::endl;
	std::cout << boost::size(finalRes->points) << std::endl;

	for (int i=0;i<inliers.size();i++){
		std::cout<<inliers.at(i)<<"   "<<std::endl;
		cloud->points[inliers.at(i)].r=255;
		cloud->points[inliers.at(i)].g=0;
		cloud->points[inliers.at(i)].b=0;
	}
	
	std::cout<<"done...."<<std::endl;

	sensor_msgs::PointCloud2 output;
	pcl::toROSMsg(*cloud, output); 
    output.header.frame_id = "odom"; 
    ros::Rate loop_rate(1); 

    while (ros::ok()) { 
		pcl_pub.publish(output);
		ros::spinOnce(); 
		loop_rate.sleep(); 
    } 

    return 0;

}


在这里插入图片描述
两段程序并没有太大的区别主要:
SampleConsensusModelSphere
SampleConsensusModelPlane
其它的Model参考第一张插图。


版权声明:本文为weixin_43928944原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。