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通過歐幾里得聚類提取獲取路面上不同物體,然後車輛就提取了出來。