1. 程式人生 > >PCL點雲分割(1)

PCL點雲分割(1)

點雲分割是根據空間,幾何和紋理等特徵對點雲進行劃分,使得同一劃分內的點雲擁有相似的特徵,點雲的有效分割往往是許多應用的前提,例如逆向工作,CAD領域對零件的不同掃描表面進行分割,然後才能更好的進行空洞修復曲面重建,特徵描述和提取,進而進行基於3D內容的檢索,組合重用等。

案例分析

用一組點雲資料做簡單的平面的分割:

planar_segmentation.cpp

#include <iostream>
#include <pcl/ModelCoefficients.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/sample_consensus/method_types.h>   //隨機引數估計方法標頭檔案
#include <pcl/sample_consensus/model_types.h>   //模型定義標頭檔案
#include <pcl/segmentation/sac_segmentation.h> //基於取樣一致性分割的類的標頭檔案 int main (int argc, char** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); // 填充點雲 cloud->width = 15; cloud->height = 1; cloud->points.resize (cloud->width * cloud->height);
// 生成資料,採用隨機數填充點雲的x,y座標,都處於z為1的平面上 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; } // 設定幾個局外點,即重新設定幾個點的z值,使其偏離z為1的平面 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); //設定隨機取樣一致性方法型別 seg.setDistanceThreshold (0.01); //設定距離閥值,距離閥值決定了點被認為是局內點是必須滿足的條件 //表示點到估計模型的距離最大值, seg.setInputCloud (cloud); //引發分割實現,儲存分割結果到點幾何inliers及儲存平面模型的係數coefficients seg.segment (*inliers, *coefficients); if (inliers->indices.size () == 0) { PCL_ERROR ("Could not estimate a planar model for the given dataset."); return (-1); } //打印出平面模型 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); }

結果如下:開始列印的資料為手動新增的點雲資料,並非都處於z為1的平面上,通過分割物件的處理後提取所有內點,即過濾掉z不等於1的點集

(2)實現圓柱體模型的分割:採用隨機取樣一致性估計從帶有噪聲的點雲中提取一個圓柱體模型。

新建檔案cylinder_segmentation.cpp

#include <pcl/ModelCoefficients.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/filters/passthrough.h>
#include <pcl/features/normal_3d.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>

typedef pcl::PointXYZ PointT;

int
main (int argc, char** argv)
{
  // 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;

  // 直通濾波,將Z軸不在(0,1.5)範圍的點過濾掉,將剩餘的點儲存到cloud_filtered物件中
  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;

  // 過濾後的點雲進行法線估計,為後續進行基於法線的分割準備資料
  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);
  //獲取平面模型的係數和處在平面的內點
  seg.segment (*inliers_plane, *coefficients_plane);
  std::cerr << "Plane coefficients: " << *coefficients_plane << std::endl;

  // 從點雲中抽取分割的處在平面上的點集
  extract.setInputCloud (cloud_filtered);
  extract.setIndices (inliers_plane);
  extract.setNegative (false);

  // 儲存分割得到的平面上的點到點雲檔案
  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);

  // Create the segmentation object for cylinder segmentation and set all the parameters
  seg.setOptimizeCoefficients (true);   //設定對估計模型優化
  seg.setModelType (pcl::SACMODEL_CYLINDER);  //設定分割模型為圓柱形
  seg.setMethodType (pcl::SAC_RANSAC);       //引數估計方法
  seg.setNormalDistanceWeight (0.1);       //設定表面法線權重係數
  seg.setMaxIterations (10000);              //設定迭代的最大次數10000
  seg.setDistanceThreshold (0.05);         //設定內點到模型的距離允許最大值
  seg.setRadiusLimits (0, 0.1);             //設定估計出的圓柱模型的半徑的範圍
  seg.setInputCloud (cloud_filtered2);
  seg.setInputNormals (cloud_normals2);

  // Obtain the cylinder inliers and coefficients
  seg.segment (*inliers_cylinder, *coefficients_cylinder);
  std::cerr << "Cylinder coefficients: " << *coefficients_cylinder << std::endl;

  // Write the cylinder inliers to disk
  extract.setInputCloud (cloud_filtered2);
  extract.setIndices (inliers_cylinder);
  extract.setNegative (false);
  pcl::PointCloud<PointT>::Ptr cloud_cylinder (new pcl::PointCloud<PointT> ());
  extract.filter (*cloud_cylinder);
  if (cloud_cylinder->points.empty ()) 
    std::cerr << "Can't find the cylindrical component." << std::endl;
  else
  {
      std::cerr << "PointCloud representing the cylindrical component: " << cloud_cylinder->points.size () << " data points." << std::endl;
      writer.write ("table_scene_mug_stereo_textured_cylinder.pcd", *cloud_cylinder, false);
  }
  return (0);
}

試驗列印的結果如下

原始點雲視覺化的結果.三維場景中有平面,杯子,和其他物體

產生分割以後的平面和圓柱點雲,檢視的結果如下

   

(3)PCL中實現歐式聚類提取。對三維點雲組成的場景進行分割

#include <pcl/ModelCoefficients.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/normal_3d.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/segmentation/extract_clusters.h>

/******************************************************************************
 開啟點雲資料,並對點雲進行濾波重取樣預處理,然後採用平面分割模型對點雲進行分割處理
 提取出點雲中所有在平面上的點集,並將其存檔
******************************************************************************/
int 
main (int argc, char** argv)
{
  // 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; //*
   //建立平面模型分割的物件並設定引數
  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;
    }

   
    pcl::ExtractIndices<pcl::PointXYZ> extract;
    extract.setInputCloud (cloud_filtered);
    extract.setIndices (inliers);
    extract.setNegative (false);

    // Get the points associated with the planar surface
    extract.filter (*cloud_plane);
    std::cout << "PointCloud representing the planar component: " << cloud_plane->points.size () << " data points." << std::endl;

    //  // 移去平面局內點,提取剩餘點雲
    extract.setNegative (true);
    extract.filter (*cloud_f);
    *cloud_filtered = *cloud_f;
  }

  // Creating the KdTree object for the search method of the extraction
  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中
  //迭代訪問點雲索引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>);
    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++;
  }

  return (0);
}

執行結果:

不再一一檢視視覺化的結果

為了更切合實際的應用我會在這些基本的程式的基礎上,進行與實際結合的例項,因為這些都是官方給的例項,我是首先學習一下,至少過一面,這樣在後期結合實際應用的過程中會更加容易一點。(因為我也是一邊學習,然後回頭再在基礎上進行更修)

同時有很多在我的微信公眾號上的同學後臺與我交流,有時候不能即時回覆敬請諒解,(之前,就有一個不知道哪個學校的關注後就一直問我問題,告訴它基本的案例,還要我告訴他怎麼實現,本人不才,我也是入門者阿,)

請掃面二維碼關注