场景
平面模型经常被应用到常见的室内平面分割提取中,比如墙、地板、桌面,其他模型常应用到根据几何结构检测识别和分割物体(比如用一个圆柱体模型分割出一个杯子)。
可用模型
这些模型有圆柱、线、圆等基本的一些形状可以直接使用,通过这些模型去分割点云。
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参考第一张插图。