PCL分割學習

1.點雲切割概述

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

2.PCL中常用的點雲分割方法

Plane model segmentation (平面模型分割):這個算法能夠把地面等一些平面給分割出來,方便後面的物體的點雲分割。
Cylinder model segmentation(圓柱模型分割):這個算法能夠把一些圓柱體分割出來,方便後面的物體的點雲分割。
Euclidean Cluster Extraction (歐幾里德聚類提取):這個算法通俗來講就是先從點雲中找出一個點p0,然後尋找p0周圍最近的n個點,如果這n個點與p0之間的距離小於預先設定的閾值,那麼就把這個點取出,依次重複。
Region growing segmentation (區域蔓延分割):對於普通點雲,其可由法線、曲率估計算法獲得其法線和曲率值。通過法線和曲率來判斷某點是否屬於該類,向周邊蔓延直至完成。
Progressive Morphological Filter(漸進形態過濾器):這個算法本身用於處理高空獲取的激光雷達數據,把地面與非地面的物體分割,也就是可以將地面分割出來使其他物體懸空,爲下一步打基礎。

3.幾種分割的使用實例

3.1.簡單平面分割

這個算法分割點雲的平面的原理就是,通過改變平面模型(ax+by+cz+d=0)的參數,找出哪一組的參數能使得這個模型一定程度上擬合最多的點。這個程度就是由distancethreshold這個參數來設置。那找到這組參數後,這些能夠被擬合的點就是平面的點。總體感受就是這個算法如果理解的不好調參有點複雜。
代碼:

#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>   //基於採樣一致性分割的類的頭文件
#include <pcl/filters/extract_indices.h>
#include <pcl/visualization/cloud_viewer.h>

int
 main (int argc, char** argv)
{
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);

  // 讀取一個PCD文件
   if (pcl::io::loadPCDFile<pcl::PointXYZ>("d:/2.pcd", *cloud) != 0)
   {
       return -1;
   }


  //創建分割時所需要的模型係數對象,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 (5);    //設定距離閥值,距離閥值決定了點被認爲是局內點是必須滿足的條件
                                       //表示點到估計模型的距離最大值,

  seg.setInputCloud (cloud);
  //引發分割實現,存儲分割結果到點幾何inliers及存儲平面模型的係數coefficients
  seg.segment (*inliers, *coefficients);

  if (inliers->indices.size () == 0)
  {
    return (-1);
  }

   pcl::ExtractIndices<pcl::PointXYZ> extract;        //創建點雲提取對象
  extract.setInputCloud (cloud);
      extract.setIndices (inliers);
      extract.setNegative (false);
   extract.filter (*cloud);

   //點雲可視化
   pcl::visualization::CloudViewer viewer("sa");

       viewer.showCloud(cloud);
        while (!viewer.wasStopped()){
        }

  return (0);
}

處理前:
在這裏插入圖片描述
處理後:
在這裏插入圖片描述
要的就是這種效果,這樣就可以將地面分割出去,使車輛懸空,然後通過後面的進一步處理將貨車顯示出來

3.2.圓柱模型分割

圓柱模型分割的具體原理與平面模型分割類似,如果理解了平面模型分割那麼圓柱模型分割也就容易理解了,這裏就不再詳述。

3.3.歐幾里德聚類提取

這個算法通俗來講就是先從點雲中找出一個點p0,然後尋找p0周圍最近的n個點,如果這n個點與p0之間的距離小於預先設定的閾值,那麼就把這個點取出,依次重複,最終分割出多個點雲。
代碼:

#include <pcl/io/pcd_io.h>
#include <pcl/segmentation/extract_clusters.h>
#include <QDebug>
#include <pcl/visualization/cloud_viewer.h>

#include <iostream>

int
main(int argc, char** argv)
{
    // 申明存儲點雲的對象.
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);

    // 讀取一個PCD文件
    if (pcl::io::loadPCDFile<pcl::PointXYZ>("d:/2.pcd", *cloud) != 0)
    {
        return -1;
    }
    // 建立kd-tree對象用來搜索
    pcl::search::KdTree<pcl::PointXYZ>::Ptr kdtree(new pcl::search::KdTree<pcl::PointXYZ>);
    kdtree->setInputCloud(cloud);

    // Euclidean 聚類對象.
    pcl::EuclideanClusterExtraction<pcl::PointXYZ> clustering;
    // 設置聚類的最小值 2cm
    clustering.setClusterTolerance(0.2);
    // 設置聚類的小點數和最大點雲數
    clustering.setMinClusterSize(1000);
    //clustering.setMaxClusterSize(25000);
    clustering.setSearchMethod(kdtree);
    clustering.setInputCloud(cloud);
    std::vector<pcl::PointIndices> clusters;
    clustering.extract(clusters);
    // For every cluster...
    for (std::vector<pcl::PointIndices>::const_iterator i = clusters.begin(); i != clusters.end(); ++i)
    {
        //添加所有的點雲到一個新的點雲中
        pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>);
        for (std::vector<int>::const_iterator point = i->indices.begin(); point != i->indices.end(); point++)
        cluster->points.push_back(cloud->points[*point]);
        cluster->width = cluster->points.size();
        cluster->height = 1;
        cluster->is_dense = true;
        //實驗預先知道了最大的點雲數量,偷懶就不排序查找點雲數量最大的點雲了
        if(cluster->points.size()>10000){
            //處理後點雲顯示
            pcl::visualization::CloudViewer viewer("PCL濾波");
            viewer.showCloud(cluster);
            while (!viewer.wasStopped()){
            }
        }
    }
}

處理前:
在這裏插入圖片描述
處理後:
在這裏插入圖片描述
效果很明顯了,這已經很接近想要的結果了,但是目前默認的是貨車的點雲數量最多,所以很多地方還不夠嚴謹。更好的實現方法應該是與其他處理結合。

3.4.區域蔓延分割

區域生長算法直觀感覺上和歐幾里德算法相差不大,都是從一個點出發,最終佔領整個被分割區域。歐幾里德算法是通過距離遠近,來判斷該點是否爲要找的點,而區域蔓延分割算法是通過曲率、法線方向的來判斷的。對於普通點雲,先按照點的曲率值對點進行排序,找到最小曲率值點,並把它添加到種子點集,從種子的位置出發,開始往四周搜索點,然後比對點於點之間的曲率和法線方向,如果差距小於閾值就視爲同一個簇。如果一個簇無法再蔓延,在剩下的點雲裏再找曲率小的面播種,然後繼續重複直到遍歷完畢。
代碼:

#include <iostream>
#include <vector>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/search/search.h>
#include <pcl/search/kdtree.h>
#include <pcl/features/normal_3d.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/filters/passthrough.h>
#include <pcl/segmentation/region_growing.h>

int
main (int argc, char** argv)
{
    //讀取PCL文件
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
  if ( pcl::io::loadPCDFile <pcl::PointXYZ> ("d:/2.pcd", *cloud) == -1)
  {
    std::cout << "Cloud reading failed." << std::endl;
    return (-1);
  }


  //創建法線估計對象,估計點雲法向量
  pcl::search::Search<pcl::PointXYZ>::Ptr tree = boost::shared_ptr<pcl::search::Search<pcl::PointXYZ> > (new pcl::search::KdTree<pcl::PointXYZ>);
  pcl::PointCloud <pcl::Normal>::Ptr normals (new pcl::PointCloud <pcl::Normal>);
  pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normal_estimator;
  normal_estimator.setSearchMethod (tree);
  normal_estimator.setInputCloud (cloud);
  normal_estimator.setKSearch (50);
  normal_estimator.compute (*normals);


  //創建區域綿延分割對象
  pcl::RegionGrowing<pcl::PointXYZ, pcl::Normal> reg;
  //設置最小點簇的點數min
  reg.setMinClusterSize (50);
  //reg.setMaxClusterSize (1000000);
  reg.setSearchMethod (tree);
  reg.setNumberOfNeighbours (30);
  //輸入點雲
  reg.setInputCloud (cloud);

  reg.setInputNormals (normals);
  //設置法線角度差
  reg.setSmoothnessThreshold (3.0 / 180.0 * M_PI);
  //設置曲率閾值
  reg.setCurvatureThreshold (1.0);

  std::vector <pcl::PointIndices> clusters;
  //切割處理保存到clusters中
  reg.extract (clusters);


  pcl::PointCloud <pcl::PointXYZRGB>::Ptr colored_cloud = reg.getColoredCloud ();
  pcl::visualization::CloudViewer viewer ("Cluster viewer");
  viewer.showCloud(colored_cloud);
  while (!viewer.wasStopped ())
  {
  }

  return (0);
}

處理前:
在這裏插入圖片描述
處理後:
在這裏插入圖片描述

4.總結

通過濾波和分割的學習,對分離出車輛已經有了一個實際不知是否可行的思路,具體思路如下:
1.首先由於點雲數據中點的數量很大,做一些處理時耗時較多,所以第一步是使用體素濾波,實現下采樣,即在保留點雲原有形狀的基礎上減少點的數量 減少點雲數據,以提高後面對點雲處理的速度。
2.通過隨機採樣一致性(前面多出用到)分割地面,將地面從點雲中分離出來,這時候地面上的物體就懸空了
3.使用統計濾波取出離散點
4.物體懸空後就更好分離了,現在就構建Kdtree通過歐幾里得聚類提取獲取路面上不同物體,然後車輛就提取了出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章