点云PCL学习笔记-分割segmentation-RANSAC随机采样一致性算法&&欧式聚类提取

随机采样一致性算法RANSAC

程序实例参考网址:
https://pcl.readthedocs.io/projects/tutorials/en/latest/random_sample_consensus.html?highlight=console#the-explanation
PCLAPI文档:
https://pointclouds.org/documentation/
应用主要是:
1、对点云进行分割,根据设定不同的几何模型(PCL中主要支持的模型有:空间平面、直线、二维或三维圆周、圆球、锥体),估计对应的几何模型参数,在一定允许误差范围内分割出在模型上的点云。
2、点云的配准对的剔除,见点云配准内容

简介

从样本中随即抽选一个样本子集——最小方差估计算法对这个子集计算模型参数——计算所有样本与该模型的偏差——使用一个设定好的阈值与偏差比较——偏差<阈值,样本点属于模型内样本点(inliers);偏差>阈值,模型外样本点(outliers)——记录下当前inliers的个数,重复这一过程,记录当前最佳模型参数(即inliers个数最多,对应的inliers称为best_ninliers)——每次迭代后计算:期望的误差率、best_ninliers、总样本个数、当前迭代次数,——计算迭代结束评判因子,决定是否结束迭代——结束迭代,最佳模型参数为最终模型参数的估计值
问题:
1、预先设定的阈值,模型如果抽象不好设定
2、RANSAC的迭代次数未知

RANSAC优缺点

RANSAC 的一个优点是它能够对模型参数进行鲁棒估计,即,即使数据集中存在大量异常值,它也可以高度准确地估计参数。 RANSAC 的一个缺点是计算这些参数所需的时间没有上限。当计算的迭代次数有限时,获得的解可能不是最佳的,甚至可能不是很好地拟合数据的解。通过这种方式,RANSAC 提供了一种权衡;通过计算更多的迭代次数,可以增加生成合理模型的概率。 RANSAC 的另一个缺点是它需要设置特定于问题的阈值。

RANSAC 只能为特定数据集估计一个模型。对于存在两个(或更多)模型时的任何一种模型方法,RANSAC 可能无法找到任何一个。

PCL中RANSAC算法实现类

class pcl::RandomSampleConsensus

应用实例

学习如何用RandomSampleConsensus类获得点云的拟合平面模型

初始化点云

  // initialize PointClouds
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);//存储源点云
  pcl::PointCloud<pcl::PointXYZ>::Ptr final (new pcl::PointCloud<pcl::PointXYZ>);//存储提取的局内点inliers

//填充点云数据,作为处理前的原始点云
  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)
  {
    if (pcl::console::find_argument (argc, argv, "-s") >= 0 || pcl::console::find_argument (argc, argv, "-sf") >= 0)//如果命令行参数为-s或者-sf,则用x^2+y^2+z^2=1设置一部分点云数据
    {
      cloud->points[i].x =  rand () / (RAND_MAX + 1.0);
      cloud->points[i].y =  rand () / (RAND_MAX + 1.0);
      if (i % 5 == 0)//1/5的点云数据被随机放置作为局外点
        cloud->points[i].z =  rand () / (RAND_MAX + 1.0);
      else if(i % 2 == 0)//另外4/5的点云数据z=+或-(1-x^2-y^2)开根号
        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);//z=-(x+y)此处为局内点
    }
  }

创建 RandomSampleConsensus 对象并计算合适的模型

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

  // created RandomSampleConsensus object and compute the appropriated model
  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));//针对平面模型的对象
  if(pcl::console::find_argument (argc, argv, "-f") >= 0)//命令行输入的参数为-f时,随机估算对应的平面模型,并存储估计的局内点
  {
    pcl::RandomSampleConsensus<pcl::PointXYZ> ransac (model_p);//RANSAC,对象为平面
    ransac.setDistanceThreshold (.01);//设置与平面距离小于0.01的点作为局内点考虑
    ransac.computeModel();//执行随机参数估计
    ransac.getInliers(inliers);//存储估计所得的局内点
  }
  else if (pcl::console::find_argument (argc, argv, "-sf") >= 0 )//命令行输入的参数为-sf时,随机估算对应的球面模型,并存储估计的局内点
  {
    pcl::RandomSampleConsensus<pcl::PointXYZ> ransac (model_s);//RANSAC,对象为球面
    ransac.setDistanceThreshold (.01);//与球面距离小于0.01的点作为局内点考虑
    ransac.computeModel();//执行随机参数估计
    ransac.getInliers(inliers);//存储估计所得的局内点
  }

最后,将局内点复制到final点云中,在三维窗口中显示原始点云或者估计得到的局内点组成的点云

 pcl::copyPointCloud<pcl::PointXYZ>(*cloud, inliers, *final);

  // creates the visualization object and adds either our orignial cloud or all of the inliers
  // depending on the command line arguments specified.
  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);//命令行参数为-f或者-sf时,显示最终结果
  else
    viewer = simpleVis(cloud);//显示原始点云

segmentation

segmentation平面分割

使用到的头文件

#include <pcl/sample_consensus/method_types.h>//随机参数估计方法头文件
#include <pcl/sample_consensus/model_types.h>//模型定义头文件
#include <pcl/segmentation/sac_segmentation.h>//基于采样一致性分割的类的头文件

预先准备数据

 	pcl::PointCloud<pcl::PointXYZ> cloud;
//填充点云数据
	cloud.width  = 15;
	cloud.height = 1;
	cloud.points.resize (cloud.width * cloud.height);
//生成数据
for (size_t i = 0; i < cloud.points.size (); ++i)
  {
    cloud.points[i].x = 1024 * rand () / (RAND_MAX + 1.0f);
    cloud.points[i].y = 1024 * rand () / (RAND_MAX + 1.0f);
    cloud.points[i].z = 1.0;
  }
//设置几个局外点
cloud.points[0].z = 2.0;
cloud.points[3].z = -2.0;
cloud.points[6].z = 4.0;
//打印点云坐标值
  std::cerr << "Point cloud data: " << cloud.points.size () <<" points" << std::endl;
for (size_t i = 0; i < cloud.points.size (); ++i)
    std::cerr << "    " << cloud.points[i].x << " " 
<< cloud.points[i].y << " " 
<< cloud.points[i].z << std::endl;

分割

//创建分割时所需要的模型系数对象coefficients及存储内点的点索引集合对象inliers
pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
//创建分割对象
  pcl::SACSegmentation<pcl::PointXYZ> seg;
 //可选设置,设置模型系数需要优化
  seg.setOptimizeCoefficients (true);
//必须设置
	seg.setModelType (pcl::SACMODEL_PLANE);//设置分割的模型类型为平面
  seg.setMethodType (pcl::SAC_RANSAC);//所用参数估计方法为RANSAC
  seg.setDistanceThreshold (0.01);//设置距离阈值小于0.01m为局内点
  seg.setInputCloud (cloud.makeShared ());//输入原始点云
//引发分割实现,并存储分割结果到点集合inliers及存储平面模型系数
  seg.segment (*inliers, *coefficients);
if (inliers->indices.size () == 0)
  {
    PCL_ERROR ("Could not estimate a planar model for the given dataset.");
return (-1);
  }
 //打印平面模型参数ax+by+cz+d=0形式
  std::cerr << "Model coefficients: " << coefficients->values[0] << " " 
<<coefficients->values[1] << " "
<<coefficients->values[2] << " " 
<<coefficients->values[3] <<std::endl;
  std::cerr << "Model inliers: " << inliers->indices.size () << std::endl;
for (size_t i = 0; i < inliers->indices.size (); ++i)
    std::cerr << inliers->indices[i] << "    " <<cloud.points[inliers->indices[i]].x << " "
<<cloud.points[inliers->indices[i]].y << " "
<<cloud.points[inliers->indices[i]].z << std::endl;
return (0);

segmentation 圆柱体分割

任务:
1、估计每个点的表面法线
2、分割出平面模型,存储
3、分割出圆柱体模型,存储

  // All the objects needed
  pcl::PCDReader reader;//PCD文件读取对象
  pcl::PassThrough<PointT> pass;//直通滤波对象
  pcl::NormalEstimation<PointT, pcl::Normal> ne;//法线估计对象
  pcl::SACSegmentationFromNormals<PointT, pcl::Normal> seg;//分割对象 
  pcl::PCDWriter writer;//PCD文件写入对象
  pcl::ExtractIndices<PointT> extract;//点提取对象
  pcl::ExtractIndices<pcl::Normal> extract_normals;
  pcl::search::KdTree<PointT>::Ptr tree (new pcl::search::KdTree<PointT> ());

  // Datasets
  pcl::PointCloud<PointT>::Ptr cloud (new pcl::PointCloud<PointT>);
  pcl::PointCloud<PointT>::Ptr cloud_filtered (new pcl::PointCloud<PointT>);
  pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);
  pcl::PointCloud<PointT>::Ptr cloud_filtered2 (new pcl::PointCloud<PointT>);
  pcl::PointCloud<pcl::Normal>::Ptr cloud_normals2 (new pcl::PointCloud<pcl::Normal>);
  pcl::ModelCoefficients::Ptr coefficients_plane (new pcl::ModelCoefficients), coefficients_cylinder (new pcl::ModelCoefficients);
  pcl::PointIndices::Ptr inliers_plane (new pcl::PointIndices), inliers_cylinder (new pcl::PointIndices);

  // Read in the cloud data
  reader.read ("table_scene_mug_stereo_textured.pcd", *cloud);
  std::cerr << "PointCloud has: " << cloud->points.size () << " data points." << std::endl;

  // Build a passthrough filter to remove spurious NaNs
  pass.setInputCloud (cloud);
  pass.setFilterFieldName ("z");
  pass.setFilterLimits (0, 1.5);
  pass.filter (*cloud_filtered);
  std::cerr << "PointCloud after filtering has: " << cloud_filtered->points.size () << " data points." << std::endl;
  // Estimate point normals
  ne.setSearchMethod (tree);
  ne.setInputCloud (cloud_filtered);
  ne.setKSearch (50);
  ne.compute (*cloud_normals);
 // Create the segmentation object for the planar model and set all the parameters
  seg.setOptimizeCoefficients (true);
  seg.setModelType (pcl::SACMODEL_NORMAL_PLANE);//设置分割模型为平面
  seg.setNormalDistanceWeight (0.1);
  seg.setMethodType (pcl::SAC_RANSAC);
  seg.setMaxIterations (100);
  seg.setDistanceThreshold (0.03);
  seg.setInputCloud (cloud_filtered);
  seg.setInputNormals (cloud_normals);
  // Obtain the plane inliers and coefficients
  seg.segment (*inliers_plane, *coefficients_plane);
  std::cerr << "Plane coefficients: " << *coefficients_plane << std::endl;  

抽取分割点云并保存

  // Extract the planar inliers from the input cloud
  extract.setInputCloud (cloud_filtered);
  extract.setIndices (inliers_plane);
  extract.setNegative (false);
 // Write the planar inliers to disk
  pcl::PointCloud<PointT>::Ptr cloud_plane (new pcl::PointCloud<PointT> ());
  extract.filter (*cloud_plane);
  std::cerr << "PointCloud representing the planar component: " << cloud_plane->points.size () << " data points." << std::endl;
  writer.write ("table_scene_mug_stereo_textured_plane.pcd", *cloud_plane, false);

  // Remove the planar inliers, extract the rest
  extract.setNegative (true);
  extract.filter (*cloud_filtered2);
  extract_normals.setNegative (true);
  extract_normals.setInputCloud (cloud_normals);
  extract_normals.setIndices (inliers_plane);
  extract_normals.filter (*cloud_normals2);

ExtractIndices基于某一分割算法提取点云中的一个子集

class pcl::ExtractIndices< PointT >
例程详见:
https://pointclouds.org/documentation/classpcl_1_1_extract_indices.html#details

pcl::ExtractIndices<PointType> eifilter (true); // Initializing with true will allow us to extract the removed indices
eifilter.setInputCloud (cloud_in);
eifilter.setIndices (indices_in);
eifilter.filter (*cloud_out);
// The resulting cloud_out contains all points of cloud_in that are indexed by indices_in
indices_rem = eifilter.getRemovedIndices ();
// The indices_rem array indexes all points of cloud_in that are not indexed by indices_in
eifilter.setNegative (true);
eifilter.filter (*indices_out);
// Alternatively: the indices_out array is identical to indices_rem
eifilter.setNegative (false);
eifilter.setUserFilterValue (1337.0);
eifilter.filterDirectly (cloud_in);
// This will directly modify cloud_in instead of creating a copy of the cloud
// It will overwrite all fields of the filtered points by the user value: 1337

欧式聚类

概念

聚类是指把不同物体的点云分别组合聚集起来, 从而能让你跟踪汽车, 行人等多个目标. 其中一种对点云数据进行分组和聚类的方法称为欧氏聚类.

欧式聚类是指将距离紧密度高的点云聚合起来. 为了有效地进行最近邻搜索, 可以使用 KD-Tree 数据结构, 这种结构平均可以加快从 o (n)到 o (log (n))的查找时间. 这是因为Kd-Tree允许你更好地分割你的搜索空间. 通过将点分组到 KD-Tree 中的区域中, 您可以避免计算可能有数千个点的距离, 因为你知道它们不会被考虑在一个紧邻的区域中.

欧氏聚类对象 ec 具有距离容忍度. 在这个距离之内的任何点都将被组合在一起. 它还有用于表示集群的点数的 min 和 max 参数. 如果一个集群真的很小, 它可能只是噪音, min参数会限制使用这个集群. 而max参数允许我们更好地分解非常大的集群, 如果一个集群非常大, 可能只是许多其他集群重叠, 最大容差可以帮助我们更好地解决对象检测. 欧式聚类的最后一个参数是 Kd-Tree. Kd-Tree是使用输入点云创建和构建的, 这些输入云点是初始点云过滤分割后得到障碍物点云.

实例

pcl::EuclideanClusterExtractionpcl::PointXYZ类
采用欧式聚类对三维点云进行分割
预先的步骤是:读入一个PCD文件,进行滤波处理,然后RANSAC平面模型分割处理,提取出点云所有在平面上的点集,存储

  // Read in the cloud data
  pcl::PCDReader reader;
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>);
  reader.read ("table_scene_lms400.pcd", *cloud);
  std::cout << "PointCloud before filtering has: " << cloud->points.size () << " data points." << std::endl; //*

  // Create the filtering object: downsample the dataset using a leaf size of 1cm
  pcl::VoxelGrid<pcl::PointXYZ> vg;
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);
  vg.setInputCloud (cloud);
  vg.setLeafSize (0.01f, 0.01f, 0.01f);
  vg.filter (*cloud_filtered);
  std::cout << "PointCloud after filtering has: " << cloud_filtered->points.size ()  << " data points." << std::endl; //*

  // Create the segmentation object for the planar model and set all the parameters
  pcl::SACSegmentation<pcl::PointXYZ> seg;
  pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
  pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_plane (new pcl::PointCloud<pcl::PointXYZ> ());
  pcl::PCDWriter writer;
  seg.setOptimizeCoefficients (true);
  seg.setModelType (pcl::SACMODEL_PLANE);
  seg.setMethodType (pcl::SAC_RANSAC);
  seg.setMaxIterations (100);
  seg.setDistanceThreshold (0.02);

  int i=0, nr_points = (int) cloud_filtered->points.size ();
  while (cloud_filtered->points.size () > 0.3 * nr_points)
  {
    // Segment the largest planar component from the remaining cloud
    seg.setInputCloud (cloud_filtered);
    seg.segment (*inliers, *coefficients);
    if (inliers->indices.size () == 0)
    {
      std::cout << "Could not estimate a planar model for the given dataset." << std::endl;
      break;
    }

    // Extract the planar inliers from the input cloud
    pcl::ExtractIndices<pcl::PointXYZ> extract;
    extract.setInputCloud (cloud_filtered);
    extract.setIndices (inliers);
    extract.setNegative (false);

    // Write the planar inliers to disk
    extract.filter (*cloud_plane);
    std::cout << "PointCloud representing the planar component: " << cloud_plane->points.size () << " data points." << std::endl;

    // Remove the planar inliers, extract the rest
    extract.setNegative (true);
    extract.filter (*cloud_f);
    cloud_filtered = cloud_f;
  }
  // Creating the KdTree object for the search method of the extraction创建一个kd树作为提取点云时的搜索方法,再创建一个点云索引向量,用于存储实际的点云索引信息,每个检测到的点云聚类被保存在这里,如cluster_indices[0]包含点云中第一个聚类包含的点集的所有索引
  pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>);
  tree->setInputCloud (cloud_filtered);

  std::vector<pcl::PointIndices> cluster_indices;//创建点云索引向量
  pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
  ec.setClusterTolerance (0.02); // 设置近邻搜索半径为2cm
  ec.setMinClusterSize (100);//设置一个聚类需要的最少点数目为100
  ec.setMaxClusterSize (25000);//设置一个聚类需要的最大点数目为25000
  ec.setSearchMethod (tree);//设置点云的搜索机制
  ec.setInputCloud (cloud_filtered);
  ec.extract (cluster_indices);//从点云中提取聚类,并将点云索引保存在cluster_indices

ClusterTolerance 搜索半径需要通过测试来设置一个合适的聚类搜索半径

	//迭代访问点云索引cluster_indices,直到分割出所有聚类
	int j=0;
  for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin (); it != cluster_indices.end (); ++it)
  {
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster (new pcl::PointCloud<pcl::PointXYZ>);//创建新的点云数据集cloud_cluster,将所有当前聚类写入到点云数据集中
    for (std::vector<int>::const_iterator pit = it->indices.begin (); pit != it->indices.end (); pit++)
      cloud_cluster->points.push_back (cloud_filtered->points[*pit]); //*
    cloud_cluster->width = cloud_cluster->points.size ();
    cloud_cluster->height = 1;
    cloud_cluster->is_dense = true;

    std::cout << "PointCloud representing the Cluster: " << cloud_cluster->points.size () << " data points." << std::endl;
    std::stringstream ss;
    ss << "cloud_cluster_" << j << ".pcd";
    writer.write<pcl::PointXYZ> (ss.str (), *cloud_cluster, false); //*
    j++;
  }
  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值