<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文詳解點云分割算法

          共 5360字,需瀏覽 11分鐘

           ·

          2022-05-19 12:22

          點擊下方卡片,關注“新機器視覺”公眾號

          重磅干貨,第一時間送達

          作者丨書生意封侯@知乎
          來源丨h(huán)ttps://zhuanlan.zhihu.com/p/470782623
          編輯丨3D視覺工坊
          從某種意義上說,地面點剔除(分割)也屬于點云分割的一種,但兩者技術路線有所不同,因此本節(jié)主要是針對地面點剔除后的點云分割。
          傳統(tǒng)點云分割的方式,通過查閱文檔后發(fā)現(xiàn)網(wǎng)上文檔大都雷同,但由于時間關系且并不是本次學習的重點,因此本次記錄暫不詳細探究,但對相關論文進行下載。

          1.1基于邊緣的分割方法

          邊緣是描述點云物體形狀的基本特征,這種方法檢測點云一些區(qū)域的邊界來獲取分割區(qū)域,這些方法的原理是定位出邊緣點的強度變化。論文[1]提出了一種邊緣檢測技術,通過計算梯度,檢測表面上單位法向量方向的變化來擬合線段。論文[2]是基于掃描線的分組進行快速分割。
          基于邊緣的方法雖然分割速度比較快但是準確度不能保證,因為邊緣對于噪聲和不均勻的或稀疏的點云非常敏感。
          [1]Range data processing:Representation of surfaces by edges. In proc.int. Pattern recognition conf, 1995——范圍數(shù)據(jù)處理:用邊界表示表面
          [2] Fast range image segmentation using high-level segmentation primitives, In 3rd IEEE Workshop on Applications of Computer Vision, USA, 1996——基礎掃描線分組的快速分割

          1.2基于區(qū)域的分割方法

          基于區(qū)域的方法使用鄰域信息來將具有相似屬性的附近點歸類,以獲得到分割區(qū)域,并區(qū)分出不同區(qū)域之間的差異性?;趨^(qū)域的方法比基于邊緣的方法更準確。但是他們在分割過度或不足以及在如何準確確定區(qū)域邊界方面存在問題。研究者們將基于區(qū)域的方法分為兩類:種子區(qū)域(自下而上)方法和非種子區(qū)域(自上而下)方法。

          1.2.1種子區(qū)域方法

          基于種子的區(qū)域分割通過選擇多個種子點來開始做分割,從這些種子點為起始點,通過添加種子的鄰域點的方式逐漸形成點云區(qū)域,算法主要包含了兩個步驟(論文[3]):
          (1)基于每個點的曲率識別種子點,
          (2)根據(jù)預定標準,該標準可以是點的相似度和點云的表面的相似度來生長種子點。
          這種方法對噪聲點也非常敏感,并且耗時。但后續(xù)有很多基于這種方法的改進,比如對于激光雷達數(shù)據(jù)的區(qū)域增長的方法,提出了基于種子點的法向量和與生長平面的距離來生長種子點。種子區(qū)域方法高度依賴于選定的種子點。不準確選擇種子點會影響分割過程,并可能導致分割不足或過度。選擇種子點以及控制生長過程是耗時的。分割結果可能對所選的兼容性閾值敏感。另一個困難是決定是否在給定區(qū)域中添加點,因為這種方法對點云的噪聲也很敏感。該方法詳情由5.2.4介紹。
          pcl中提供了基于歐式聚類的區(qū)域增長方式,以第一個點為標準作為種子點,候選其周邊的點作為它的對比或者比較的對象,如果滿足條件就加入到聚類的對象中。其主要的缺點:該算法沒有初始化種子系統(tǒng),沒有過度分割或者分割不足的控制,還有就是從主循環(huán)運算中調用條件函數(shù)時,效率比較低。該方法詳情由5.3.2介紹。

          1.2.2非種子區(qū)域方法

          這種方法時基于自上而下的方法。首先,所有點都分為一個區(qū)域。然后細分過程開始將其劃分為更小的區(qū)域。使用這種方法(論文[4])指導聚類平面區(qū)域的過程,以重建建筑物的完整幾何形狀。該工作引入了基于局部區(qū)域的置信率為平面的分割方法。這種方法的局限性在于它也會可能過度分割,并且在分割其他對象(例如樹)時它不能很好地執(zhí)行。非種子區(qū)域方法的主要困難是決定細分的位置和方式。這些方法的另一個限制是它們需要大量的先驗知識(例如,對象模型,區(qū)域數(shù)量等),然后這些未知的先驗知識在復雜場景中通常是未知的。
          [3]P.J. Besl, R.C. Jain, Segmentation through variable order surface fitting, IEEE Transaction on Pattern Analysis and Machine Intelligence 10, 1988.——變階曲面擬合分割
          [4]Architectural Modeling from Sparsely Scanned Range Data——稀疏掃描范圍內的建筑建模

          1.2.3基于圖像區(qū)域增長方法

          該方法主要是通過將點云轉換成二值圖像,再通過圖像方法中的區(qū)域增長進行聚類,再轉換成點云。其優(yōu)點為速度快,而缺點是存在過度分割以及分割不足。
          1.將激光點云數(shù)據(jù)放入柵格中,并生成對應的深度圖像,每格圖像中的像素值用轉換后的高度表示,分別用像素值中的r、g、b中的0-b表示高度最大值,1-g-表示高度最小值,2-r表示有無聚類的標志(0-未聚類,1-255-聚類ID)
          /* @brief:將點云轉換為vector,俯視圖柵格化

          * @param [in]: in_cloud,輸入點云
          * @param [out]: out_cloud,轉換的點云vector
          * @return NONE
          */
          void FrontLidarAlg::convetCloudToVector(pcl::PointCloud::Ptr in_cloud, \
          cv::Mat& out_img, \
          std::vector>* out_cloud)
          {
          out_img = cv::Mat::zeros(cv::Size(img_width_,img_height_), CV_8UC3);

          volatile int row;
          volatile int col;

          for(int i=0;isize();i++)
          {
          //將橫向在檢測范圍之外的去掉
          if(in_cloud->points[i].x <= cloud_x_min_ || in_cloud->points[i].x >= cloud_x_max_)
          {
          continue;
          }
          //將縱向在檢測范圍之外的去掉
          if(in_cloud->points[i].y >= cloud_y_max_ || in_cloud->points[i].y <= 0)
          {
          continue;
          }

          //限制最高和最低點大小
          if(in_cloud->points[i].z < min_dect_height_)//高度低于最低點,則賦值為最低點
          {
          in_cloud->points[i].z = min_dect_height_;
          }

          if(in_cloud->points[i].z > max_dect_height_)//高度高于最高點,則賦值最高點
          {
          in_cloud->points[i].z = max_dect_height_;
          }

          //計算點云所在圖像的行數(shù)和列數(shù),四舍五入
          col = int((in_cloud->points[i].x - cloud_x_min_)/img_res_);
          row = int(in_cloud->points[i].y/img_res_);
          out_cloud->at(col+row*img_width_).points.push_back(in_cloud->points[i]);

          //輸入點云高度值轉換到圖像坐標系下的數(shù)值
          int value = (int)((in_cloud->points[i].z - min_dect_height_)/dect_height_res_);

          if(out_img.at(row,col)[0] < value)//b-最大高度
          {
          out_img.at(row,col)[0] = value;
          if(0 == out_img.at(row,col)[1])
          {
          out_img.at(row,col)[1] = value;
          }
          }
          else if(out_img.at(row,col)[1] > value)//b-最小高度
          {
          out_img.at(row,col)[1] = value;
          }

          }//for in_cloud->size
          }
          2、判斷圖像的邊界是否越界;
          3、對生成的二值圖像做形態(tài)學濾波操作,進行閉運算,將臨近的小目標進行合并,一定程度上優(yōu)化過分割;
          4、采用遞歸的方式對圖像中的任意一個像素值附近的8鄰域進行標記操作,即對所有的像素對應的r值進行賦值(ID);
          5、將相同ID(r值)對應的像素和柵格內的點云取出,即可得到不同類簇的點云。
          6、濾波操作,濾除點云較少的類簇。
          #include "object_segment.h"

          namespace front_lidar {

          /* @brief:點云分割構造函數(shù),初始化參數(shù)

          * @param [in]: NONE
          * @param [out]: NONE
          * @return NONE
          */
          ObjectSegment::ObjectSegment(string config_file_str)
          {
          config_parser_ptr_.reset(new ConfigParser(config_file_str));

          use_seg_raw_cloud_ = config_parser_ptr_->getInt("use_seg_raw_cloud");//聚類后是否輸出原始數(shù)據(jù)
          img_res_ = config_parser_ptr_->getDouble("img_res");//圖像分辨率
          cloud_x_min_ = config_parser_ptr_->getDouble("cloud_x_min");//x軸最小值
          cloud_x_max_ = config_parser_ptr_->getDouble("cloud_x_max");//x軸最大值
          cloud_y_min_ = config_parser_ptr_->getDouble("cloud_y_min");//y軸最小值
          cloud_y_max_ = config_parser_ptr_->getDouble("cloud_y_max");//y軸最大值
          min_cluster_size_ = config_parser_ptr_->getDouble("object_detect_min_cluster_size");//最小聚類個數(shù)
          max_cluster_size_ = config_parser_ptr_->getDouble("object_detect_max_cluster_size");//最大聚類個數(shù)
          cell_size_ = config_parser_ptr_->getDouble("cell_size");//聚類臨域尺寸

          min_dect_height_ = -10;//最小檢測高度
          max_dect_height_ = 10;//最大檢測高度
          dect_height_res_ = (max_dect_height_ - min_dect_height_)/255;//檢測高度分辨率

          region_growing_clusters_.clear();
          region_growing_img_.resize(0);//區(qū)域增長后的圖像

          }

          /* @brief:點云分割處理,將點云分割為不同障礙物點云

          * @param [in]: in_img,輸入圖像柵格, in_vector-輸入柵格化的點云
          * @param [out]: cloud_clusters_ptr,輸出聚類結果
          * @return NONE
          */
          void ObjectSegment::process(const std::vector>* in_vector, \
          const cv::Mat& in_img, \
          std::vector::Ptr>* cloud_clusters_ptr)
          {
          cloud_clusters_ptr->clear();

          cluster_num_ = 0;

          //區(qū)域增長,圖像聚類分割
          pcl::StopWatch regin_grow_timer;
          reginGrowing(in_img, region_growing_img_);
          LOG_Debug()<<"regin_grow time:"<
          //將圖像轉換為點云
          pcl::StopWatch vector_to_cloud_timer;
          if(1 == use_seg_raw_cloud_)
          {
          convetVectorToCloud(in_vector, region_growing_img_, ®ion_growing_clusters_);
          }
          else
          {
          convetImageToCloud(region_growing_img_, ®ion_growing_clusters_);
          }
          LOG_Debug()<<"vector_to_cloud time:"<
          //濾除聚類點數(shù)少于設定閾值的類別
          pcl::StopWatch filter_timer;
          filterObjectsCloud(region_growing_clusters_, cloud_clusters_ptr);
          LOG_Debug()<<"filter time:"<
          }


          /* @brief:區(qū)域增長,8鄰域聚類

          * @param [in]: in_img-輸入圖像
          * @param [out]: out_img,轉換的圖像,0-b-最大值,1-g-最小值,2-r-有無聚類標志(0-未聚類,1-255-已聚類ID)
          * @return NONE
          */
          void ObjectSegment::reginGrowing(const cv::Mat &in_img, cv::Mat &out_img)
          {
          unsigned short class_id = 0;
          unsigned short element_class_id = 0;
          out_img = in_img.clone();

          for(int row = 1; row < out_img.rows -1; row++)
          {
          for(int col = 1; col < out_img.cols - 1; col++)
          {
          //像素的高度最大值為0,則該像素為空,無效像素
          if(0 == out_img.at(row,col)[0])
          {
          continue;
          }
          //像素的類別標記為空,即為未標記類別的像素,則分配類別ID
          if(0 == out_img.at(row,col)[2])
          {
          if(class_id > 250)//超出限定類別總數(shù),則返回
          {
          return;
          }
          out_img.at(row,col)[2] = ++class_id;//給該像素賦值類別ID
          }
          element_class_id = out_img.at(row,col)[2];
          //根據(jù)輸入種子像素,遞歸區(qū)域增長
          elementReginGrowing(out_img, row, col, element_class_id);
          }//col
          }//row
          cluster_num_ = class_id;
          }

          /* @brief:單個元素區(qū)域增長,cell_size_鄰域聚類

          * @param [in]: 輸入圖像-in_img,當前元素的r和c,輸出圖像-in_img
          * @param [out]: out_img,轉換的圖像,0-b-最大值,1-g-最小值,2-r-有無聚類標志(0-未聚類,1-255-聚類ID)
          * @return NONE
          */
          void ObjectSegment::elementReginGrowing(cv::Mat &in_img, int row, int col, unsigned short class_id)
          {
          int start_row;//起始行
          int end_row;//截止行

          int start_col;//起始列
          int end_col;//截止列

          //判斷起始行 截止行 起始列 截止列,防止越界
          start_row = row - cell_size_;
          end_row = row + cell_size_;
          start_col = col - cell_size_;
          end_col = col + cell_size_;

          if(start_row < 0)
          start_row = 0;
          if(end_row > in_img.rows - 1)
          end_row = in_img.rows - 1;
          if(start_col < 0)
          start_col = 0;
          if(end_col > in_img.cols - 1)
          end_col = in_img.cols - 1;

          for(int m = start_row; m <= end_row; m++)
          {
          for(int n = start_col; n <= end_col; n++)
          {
          //該像素對應最大高度為0,則為無效像素
          if(0 == in_img.at(m,n)[0])
          {
          continue;
          }
          //該元素為初始輸入,已經(jīng)標記過
          if(m==row && n==col)
          {
          continue;
          }
          //未標記過的元素,標記該元素,以該元素為種子,進行區(qū)域增長
          if(0 == in_img.at(m,n)[2])
          {
          in_img.at(m,n)[2] = class_id;//標記元素
          elementReginGrowing(in_img, m, n, class_id);//以該元素為初始值,進行區(qū)域增長
          }
          }//for col
          }//for row
          }

          /* @brief:將vector轉化為聚類結果,2D到3D

          * @param [in]: in_img,輸入的柵格圖像
          * @param [out]: out_cloud_ptr,轉換的聚類結果
          * @return NONE
          */
          void ObjectSegment::convetImageToCloud(const cv::Mat& in_img, \
          std::vector::Ptr>* out_cloud_ptr)
          {
          out_cloud_ptr->clear();
          pcl::PointXYZI point;
          int class_id = 0;//類ID,1開始遞增

          //根據(jù)類別分配向量空間
          out_cloud_ptr->resize(cluster_num_);//擴充輸出向量大小
          //分配點云空間
          for(int i=0;i {
          out_cloud_ptr->at(i).reset(new pcl::PointCloud);
          }
          //將圖像轉換為點云
          for(int row=0;row {
          for(int col=0;col {
          //如果分類標記為0,則為無效數(shù)據(jù)
          if(0 == in_img.at(row,col)[2])
          {
          continue;
          }

          class_id = in_img.at(row,col)[2];//獲取該區(qū)域的類別ID

          //圖像坐標轉換為點云坐標
          point.x = (col+0.5)*img_res_ + cloud_x_min_;
          point.y = (row+0.5)*img_res_;

          //點云坐標高度最大值
          point.z = in_img.at(row,col)[0]*dect_height_res_ + min_dect_height_;
          out_cloud_ptr->at(class_id-1)->points.push_back(point);

          //點云坐標高度最小值
          point.z = in_img.at(row,col)[1]*dect_height_res_ + min_dect_height_;
          out_cloud_ptr->at(class_id-1)->points.push_back(point);
          }

          }
          }

          /* @brief:將vector轉化為聚類結果,2D到3D

          * @param [in]: in_vector,輸入的柵格化后點云,in_img,輸入的聚類標志位的圖像
          * @param [out]: out_cloud_ptr,轉換的聚類結果
          * @return NONE
          */
          void ObjectSegment::convetVectorToCloud(const std::vector>* in_vector, \
          const cv::Mat& in_img, \
          std::vector::Ptr>* out_cloud_ptr)
          {
          out_cloud_ptr->clear();

          int class_id = 0;//類ID,1開始遞增

          //根據(jù)類別分配向量空間
          out_cloud_ptr->resize(cluster_num_);//擴充輸出向量大小
          //分配點云空間
          for(int i=0;i {
          out_cloud_ptr->at(i).reset(new pcl::PointCloud);
          }
          //將圖像轉換為點云
          for(int row=0;row {
          for(int col=0;col {
          //如果分類標記為0,則為無效數(shù)據(jù)
          if(0 == in_img.at(row,col)[2])
          {
          continue;
          }

          class_id = in_img.at(row,col)[2];//獲取該區(qū)域的類別ID

          int count = in_vector->at(col+row*in_img.cols).points.size();
          if(count > 25)
          count = 25;

          for(int j = 0; j < count; j++)
          {
          out_cloud_ptr->at(class_id-1)->points.push_back(in_vector->at(col+row*in_img.cols).points[j]);
          }
          }

          }
          }

          /* @brief:濾除聚類后點云數(shù)量小于設定值的類別

          * @param [in]: in_objects_cloud-聚類后的目標點云
          * @param [out]: out_objects_cloud_ptr-濾除類別中點數(shù)量少于設定閾值的類別
          * @return NONE
          */
          void ObjectSegment::filterObjectsCloud(const std::vector::Ptr> in_objects_cloud, \
          std::vector::Ptr> *out_objects_cloud_ptr)
          {
          out_objects_cloud_ptr->clear();

          for(int i=0;i {
          if(in_objects_cloud[i]->size() < min_cluster_size_)
          {
          continue;
          }

          out_objects_cloud_ptr->push_back(in_objects_cloud[i]);

          }
          }

          }//namespace front_lidar

          1.2.4基于點云區(qū)域增長方法

          算法思想:
          首先依據(jù)點的曲率值對點進行排序,之所以排序是因為,區(qū)域生長算法是從曲率最小的點開始生長的,這個點就是初始種子點,初始種子點所在的區(qū)域即為最平滑的區(qū)域,從最平滑的區(qū)域開始生長可減少分割片段的總數(shù),提高效率,設置一空的種子點序列和空的聚類區(qū)域,選好初始種子后,將其加入到種子點序列中,并搜索鄰域點,對每一個鄰域點,比較鄰域點的法線與當前種子點的法線之間的夾角,小于平滑閥值的將當前點加入到當前區(qū)域,然后檢測每一個鄰域點的曲率值,小于曲率閥值的加入到種子點序列中,刪除當前的種子點,循環(huán)執(zhí)行以上步驟,直到種子序列為空。
          算法步驟:
          1.種子周圍的臨近點和種子點云相比較
          2.法線的方向是否足夠相近
          3.曲率是否足夠小
          3.如果滿足1,2則該點可用做種子點
          4.如果只滿足1,則歸類而不做種
          5.從某個種子出發(fā),其“子種子”不再出現(xiàn)則一類聚集完成
          6.類的規(guī)模既不能太大也不能太小
          上述算法是針對小曲率變化面設計的。尤其適合對連續(xù)階梯平面進行分割:比如SLAM算法所獲得的建筑走廊。
          算法源碼:
          #include
          #include
          #include
          #include
          #include
          #include
          #include
          #include
          #include
          #include

          int
          main (int argc, char** argv)
          {
          //點云的類型
          pcl::PointCloud::Ptr cloud (new pcl::PointCloud);
          //打開點云
          if ( pcl::io::loadPCDFile ("region_growing_tutorial.pcd", *cloud) == -1)
          {
          std::cout << "Cloud reading failed." << std::endl;
          return (-1);
          }
          //設置搜索的方式或者說是結構
          pcl::search::Search::Ptr tree = boost::shared_ptr > (new pcl::search::KdTree);
          //求法線
          pcl::PointCloud ::Ptr normals (new pcl::PointCloud );
          pcl::NormalEstimation normal_estimator;
          normal_estimator.setSearchMethod (tree);
          normal_estimator.setInputCloud (cloud);
          normal_estimator.setKSearch (50);
          normal_estimator.compute (*normals);
          //直通濾波在Z軸的0到1米之間
          pcl::IndicesPtr indices (new std::vector );
          pcl::PassThrough pass;
          pass.setInputCloud (cloud);
          pass.setFilterFieldName ("z");
          pass.setFilterLimits (0.0, 1.0);
          pass.filter (*indices);
          //聚類對象<點,法線>
          pcl::RegionGrowing reg;
          reg.setMinClusterSize (50); //最小的聚類的點數(shù)
          reg.setMaxClusterSize (1000000); //最大的
          reg.setSearchMethod (tree); //搜索方式
          reg.setNumberOfNeighbours (30); //設置搜索的鄰域點的個數(shù)
          reg.setInputCloud (cloud); //輸入點
          //reg.setIndices (indices);
          reg.setInputNormals (normals); //輸入的法線
          reg.setSmoothnessThreshold (3.0 / 180.0 * M_PI); //設置平滑度
          reg.setCurvatureThreshold (1.0); //設置曲率的閥值

          std::vector clusters;
          reg.extract (clusters);

          std::cout << "Number of clusters is equal to " << clusters.size () << std::endl;
          std::cout << "First cluster has " << clusters[0].indices.size () << " points." << endl;
          std::cout << "These are the indices of the points of the initial" <<
          std::endl << "cloud that belong to the first cluster:" << std::endl;

          int counter = 0;
          while (counter < clusters[0].indices.size ())
          {
          std::cout << clusters[0].indices[counter] << ", ";
          counter++;
          if (counter % 10 == 0)
          std::cout << std::endl;
          }
          std::cout << std::endl;

          //可視化聚類的結果
          pcl::PointCloud ::Ptr colored_cloud = reg.getColoredCloud ();
          pcl::visualization::CloudViewer viewer ("Cluster viewer");
          viewer.showCloud(colored_cloud);
          while (!viewer.wasStopped ())
          {
          }

          return (0);
          }

          1.2.5基于顏色區(qū)域增長方法

          基于顏色的區(qū)域生長分割原理上和基于曲率,法線的分割方法是一致的。只不過比較目標換成了顏色,去掉了點云規(guī)模上 限的限制。可以認為,同一個顏色且挨得近,是一類的可能性很大,不需要上限來限制。所以這種方式比較適合用于室內場景分割。
          算法步驟:
          算法分為兩步:
          (1)分割,當前種子點和領域點之間色差小于色差閥值的視為一個聚類
          (2)合并,聚類之間的色差小于色差閥值和并為一個聚類,且當前聚類中點的數(shù)量小于聚類點數(shù)量的與最近的聚類合并在一起。
          pcl中的函數(shù)調用:
          #include
          #include
          #include
          #include
          #include
          #include
          #include
          #include
          #include

          int
          main (int argc, char** argv)
          {
          pcl::search::Search ::Ptr tree = boost::shared_ptr > (new pcl::search::KdTree);

          pcl::PointCloud ::Ptr cloud (new pcl::PointCloud );
          if ( pcl::io::loadPCDFile ("region_growing_rgb_tutorial.pcd", *cloud) == -1 )
          {
          std::cout << "Cloud reading failed." << std::endl;
          return (-1);
          }
          //存儲點云的容器
          pcl::IndicesPtr indices (new std::vector );
          //濾波
          pcl::PassThrough pass;
          pass.setInputCloud (cloud);
          pass.setFilterFieldName ("z");
          pass.setFilterLimits (0.0, 1.0);
          pass.filter (*indices);

          //基于顏色的區(qū)域生成的對象
          pcl::RegionGrowingRGB reg;
          reg.setInputCloud (cloud);
          reg.setIndices (indices); //點云的索引
          reg.setSearchMethod (tree);
          reg.setDistanceThreshold (10); //距離的閥值
          reg.setPointColorThreshold (6); //點與點之間顏色容差
          reg.setRegionColorThreshold (5); //區(qū)域之間容差
          reg.setMinClusterSize (600); //設置聚類的大小

          std::vector clusters;
          reg.extract (clusters);

          pcl::PointCloud ::Ptr colored_cloud = reg.getColoredCloud ();
          pcl::visualization::CloudViewer viewer ("Cluster viewer");
          viewer.showCloud (colored_cloud);
          while (!viewer.wasStopped ())
          {
          boost::this_thread::sleep (boost::posix_time::microseconds (100));
          }

          return (0);
          }

          1.3基于屬性的分割方法

          該方法是基于點云數(shù)據(jù)的屬性的一種魯棒性較好的分割方法,這種方法一般包括了兩個單獨的步驟:
          第一步,基于屬性的計算。
          第二步,將根據(jù)計算點的屬性進行聚類,這種聚類方法一般能適應空間關系和點云的各種屬性,最終將不同的屬性的點云分割出來,但是這種方法局限性在于他們高度依賴派生屬性的質量所以要求第一步能夠精確的計算點云數(shù)據(jù)的屬性,這樣才會在第二步中根據(jù)屬性的類別分割出最佳的效果。
          論文[5]則是這種方法實現(xiàn)的,提出了一種基于特征空間聚類分析方法,在該方法中,使用一種自適應斜率的鄰域系統(tǒng)導出法向量,使用點云數(shù)據(jù)的屬性,例如距離,點密度,點在水平或者垂直方向的分布,來定義測量點之間的領域,然后將每個方向上的法向量的斜率和點鄰域的數(shù)據(jù)之差作為聚類的屬性,這種方法可以消除異常值和噪聲的影響,基于屬性的方法是將點云分割相同屬性區(qū)域的高效方法,并且分割的結果靈活而準確。然而,這些方法依賴于點之間鄰域的定義和點云數(shù)據(jù)的點密度。當處理大量輸入點的多維屬性時,這種方法的另一個限制是比較耗時。
          論文[5]Segmentation of airborne data using a slope adaptive filter未能找到原文。
          目前使用的關于屬性的分割方法有歐式聚類密度聚類

          1.3.1歐式聚類

          https://www.cnblogs.com/li-yao7758258/p/6602342.html
          對于歐式聚類來說,距離判斷準則為歐氏距離。對于空間某點P,通過KD-Tree近鄰搜索算法找到k個離p點最近的點,這些點中距離小于設定閾值的便聚類到集合Q中。如果Q中元素的數(shù)目不在增加,整個聚類過程便結束;否則須在集合Q中選取p點以外的點,重復上述過程,直到Q中元素的數(shù)目不再增加為止。
          在pcl中,歐式聚類的代碼如下:
          實現(xiàn)原理:
          pcl::extractEuclideanClusters (const PointCloud &cloud,
          const typename search::Search::Ptr &tree,
          float tolerance, std::vector &clusters,
          unsigned int min_pts_per_cluster,
          unsigned int max_pts_per_cluster)
          {
          if (tree->getInputCloud ()->points.size () != cloud.points.size ()) // 點數(shù)量檢查
          {
          PCL_ERROR ("[pcl::extractEuclideanClusters] Tree built for a different point cloud dataset (%lu) than the input cloud (%lu)!\n", tree->getInputCloud ()->points.size (), cloud.points.size ());
          return;
          }
          // Check if the tree is sorted -- if it is we don't need to check the first element
          int nn_start_idx = tree->getSortedResults () ? 1 : 0;
          // Create a bool vector of processed point indices, and initialize it to false
          std::vector processed (cloud.points.size (), false);
          std::vector nn_indices;
          std::vector nn_distances; // 定義需要的變量


          // Process all points in the indices vector
          for (int i = 0; i < static_cast (cloud.points.size ()); ++i) //遍歷點云中的每一個點
          {
          if (processed[i]) //如果該點已經(jīng)處理則跳過
          continue;

          std::vector seed_queue; //定義一個種子隊列

          int sq_idx = 0;
          seed_queue.push_back (i); //加入一個種子


          processed[i] = true;

          while (sq_idx < static_cast (seed_queue.size ())) //遍歷每一個種子
          {
          // Search for sq_idx kdtree 樹的近鄰搜索
          if (!tree->radiusSearch (seed_queue[sq_idx], tolerance, nn_indices, nn_distances))
          {
          sq_idx++;
          continue; //沒找到近鄰點就繼續(xù)
          }

          for (size_t j = nn_start_idx; j < nn_indices.size (); ++j) // can't assume sorted (default isn't!)
          {
          if (nn_indices[j] == -1 || processed[nn_indices[j]]) // Has this point been processed before ?
          continue; // 種子點的近鄰點中如果已經(jīng)處理就跳出此次循環(huán)繼續(xù)

          // Perform a simple Euclidean clustering
          seed_queue.push_back (nn_indices[j]); //將此種子點的臨近點作為新的種子點。入隊操作
          processed[nn_indices[j]] = true; // 該點已經(jīng)處理,打標簽
          }

          sq_idx++;
          }

          // If this queue is satisfactory, add to the clusters 最大點數(shù)和最小點數(shù)的類過濾
          if (seed_queue.size () >= min_pts_per_cluster && seed_queue.size () <= max_pts_per_cluster)
          {
          pcl::PointIndices r;
          r.indices.resize (seed_queue.size ());
          for (size_t j = 0; j < seed_queue.size (); ++j)
          r.indices[j] = seed_queue[j];

          // These two lines should not be needed: (can anyone confirm?) -FF
          std::sort (r.indices.begin (), r.indices.end ());
          r.indices.erase (std::unique (r.indices.begin (), r.indices.end ()), r.indices.end ());

          r.header = cloud.header;
          clusters.push_back (r); // We could avoid a copy by working directly in the vector
          }
          }
          }
          調用方式及可視化:
          // Creating the KdTree object for the search method of the extraction
          pcl::search::KdTree::Ptr tree (new pcl::search::KdTree);
          tree->setInputCloud (cloud_filtered);

          std::vector cluster_indices;
          pcl::EuclideanClusterExtraction ec;
          ec.setClusterTolerance (0.02); //設置近鄰搜索的搜索半徑為2cm
          ec.setMinClusterSize (100); //設置一個聚類需要的最少點數(shù)目為100
          ec.setMaxClusterSize (25000); //設置一個聚類需要的最大點數(shù)目為25000
          ec.setSearchMethod (tree); //設置點云的搜索機制
          ec.setInputCloud (cloud_filtered); //設置原始點云
          ec.extract (cluster_indices); //從點云中提取聚類

          // 可視化部分
          pcl::visualization::PCLVisualizer viewer("segmention");
          // 我們將要使用的顏色
          float bckgr_gray_level = 0.0; // 黑色
          float txt_gray_lvl = 1.0 - bckgr_gray_level;
          int num = cluster_indices.size();

          int j = 0;
          for (std::vector::const_iterator it = cluster_indices.begin (); it != cluster_indices.end (); ++it)
          {
          pcl::PointCloud::Ptr cloud_cluster (new pcl::PointCloud);
          for (std::vector::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 (ss.str (), *cloud_cluster, false); //*

          pcl::visualization::PointCloudColorHandlerCustom cloud_in_color_h(cloud,
          color_bar[j][0],
          color_bar[j][1],
          color_bar[j][2]);//賦予顯示點云的顏色
          viewer.addPointCloud(cloud_cluster, cloud_in_color_h, std::to_string(j));

          j++;
          }

          1.3.2條件歐式聚類

          條件歐幾里德分割的工作方式與(1)所示的標準的歐幾里德分割方法基本一樣,條件分割除了要距離檢查,點還需要滿足一個特殊的,可以自定義的要求的限制,這樣它們被添加到一個集群。此條件是用戶指定的。它歸結為如下:對于每一對點(第一個點,作為種子點,第二個點,作為候選點,是一個臨近點的選擇與計算比較等操作)將會有自定義函數(shù)。此函數(shù)具有一定的特性:這兩個點具有副本,以便我們可以執(zhí)行我們自己的選擇計算的平方距離函數(shù),并返回布爾值。如果值為true,則可以將候選添加到群集。如果假,它不會被添加,即使它通過距離檢查。
          其主要的缺點:該算法沒有初始化種子系統(tǒng),沒有過度分割或者分割不足的控制,還有就是從主循環(huán)運算中調用條件函數(shù)時,效率比較低。
          pcl中提供的函數(shù)調用方式:
          #include
          #include

          #include

          // 如果這個函數(shù)返回的是真,這這個候選點將會被加入聚類中
          bool
          customCondition(const pcl::PointXYZ& seedPoint, const pcl::PointXYZ& candidatePoint, float squaredDistance)
          {
          // Do whatever you want here.做你想做的條件的篩選
          if (candidatePoint.y < seedPoint.y) //如果候選點的Y的值小于種子點的Y值(就是之前被選擇為聚類的點),則不滿足條件,返回假
          return false;

          return true;
          }

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

          if (pcl::io::loadPCDFile(argv[1], *cloud) != 0)
          {
          return -1;
          }

          pcl::ConditionalEuclideanClustering clustering;
          clustering.setClusterTolerance(0.02);
          clustering.setMinClusterSize(100);
          clustering.setMaxClusterSize(25000);
          clustering.setInputCloud(cloud);
          //設置每次檢測一對點云時的函數(shù)
          clustering.setConditionFunction(&customCondition);
          std::vector clusters;
          clustering.segment(clusters);

          int currentClusterNum = 1;
          for (std::vector::const_iterator i = clusters.begin(); i != clusters.end(); ++i)
          {
          pcl::PointCloud::Ptr cluster(new pcl::PointCloud);
          for (std::vector::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() <= 0)
          break;
          std::cout << "Cluster " << currentClusterNum << " has " << cluster->points.size() << " points." << std::endl;
          std::string fileName = "cluster" + boost::to_string(currentClusterNum) + ".pcd";
          pcl::io::savePCDFileASCII(fileName, *cluster);

          currentClusterNum++;
          }
          }

          1.3.3密度聚類

          https://blog.csdn.net/jjmjim/article/details/107064271
          相關概念:
          DBSCAN中為了增加抗噪聲的能力,引入了核心對象等概念。
          ε:參數(shù),鄰域距離。
          minPts:參數(shù),核心點領域內最少點數(shù)。
          核心點:在 ? \epsilon??鄰域內有至少 m i n P t s minPtsminPt**s?個鄰域點的點為核心點。
          直接密度可達:對于樣本集合 D DD,如果樣本點 q qq?在 p pp?的 ? \epsilon??鄰域內,并且 p pp?為核心對象,那么對象 q qq?從對象 p pp?直接密度可達。
          密度可達:對于樣本集合 D DD,給定一串樣本點p 1 p_1p1, p 2 p_2p2, …, p n p_np**n,p = p 1 p = p_1p=p1, q = p n q = p_nq=p**n, 假如對象 p i p_ip**i?從 p i ? 1 p_{i-1}p**i?1 直接密度可達,那么對象 q qq?從對象 p pp?密度可達。
          密度相連:存在樣本集合 D DD?中的一點 o oo?,如果對象 o oo?到對象 p pp?和對象 q qq?都是密度可達的,那么 p pp?和 q qq?密度相聯(lián)。
          DBSCAN 算法核心是找到密度相連對象的最大集合。
          算法:
          逐點遍歷,如果該點非核心點,則認為是噪聲點并忽視(噪聲點可能在后續(xù)被核心點歸入聚類中),若為核心點則新建聚類,并將所有鄰域點加入聚類。對于鄰域點中的核心點,還要遞歸地把其鄰域點加入聚類。依此類推直到無點可加入該聚類,并開始考慮新的點,建立新的聚類。利用kdtree查詢鄰域點。
          實現(xiàn)代碼:
          #ifndef DBSCAN_H
          #define DBSCAN_H

          #include

          #define UN_PROCESSED 0
          #define PROCESSING 1
          #define PROCESSED 2

          inline bool comparePointClusters (const pcl::PointIndices &a, const pcl::PointIndices &b) {
          return (a.indices.size () < b.indices.size ());
          }

          template
          class DBSCANSimpleCluster {
          public:
          typedef typename pcl::PointCloud::Ptr PointCloudPtr;
          typedef typename pcl::search::KdTree::Ptr KdTreePtr;

          virtual void setInputCloud(PointCloudPtr cloud) {
          input_cloud_ = cloud;
          }

          void setSearchMethod(KdTreePtr tree) {
          search_method_ = tree;
          }

          void extract(std::vector& cluster_indices) {
          std::vector nn_indices;
          std::vector nn_distances;
          std::vector is_noise(input_cloud_->points.size(), false);
          std::vector types(input_cloud_->points.size(), UN_PROCESSED);
          for (int i = 0; i < input_cloud_->points.size(); i++) {
          if (types[i] == PROCESSED) {
          continue;
          }
          int nn_size = radiusSearch(i, eps_, nn_indices, nn_distances);
          if (nn_size < minPts_) {
          is_noise[i] = true;
          continue;
          }
          std::vector seed_queue;
          seed_queue.push_back(i);
          types[i] = PROCESSED;
          for (int j = 0; j < nn_size; j++) {
          if (nn_indices[j] != i) {
          seed_queue.push_back(nn_indices[j]);
          types[nn_indices[j]] = PROCESSING;
          }
          } // for every point near the chosen core point.
          int sq_idx = 1;
          while (sq_idx < seed_queue.size()) {
          int cloud_index = seed_queue[sq_idx];
          if (is_noise[cloud_index] || types[cloud_index] == PROCESSED) {
          // seed_queue.push_back(cloud_index);
          types[cloud_index] = PROCESSED;
          sq_idx++;
          continue; // no need to check neighbors.
          }
          nn_size = radiusSearch(cloud_index, eps_, nn_indices, nn_distances);
          if (nn_size >= minPts_) {
          for (int j = 0; j < nn_size; j++) {
          if (types[nn_indices[j]] == UN_PROCESSED) {

          seed_queue.push_back(nn_indices[j]);
          types[nn_indices[j]] = PROCESSING;
          }
          }
          }
          types[cloud_index] = PROCESSED;
          sq_idx++;
          }
          if (seed_queue.size() >= min_pts_per_cluster_ && seed_queue.size () <= max_pts_per_cluster_) {
          pcl::PointIndices r;
          r.indices.resize(seed_queue.size());
          for (int j = 0; j < seed_queue.size(); ++j) {
          r.indices[j] = seed_queue[j];
          }
          // These two lines should not be needed: (can anyone confirm?) -FF
          std::sort (r.indices.begin (), r.indices.end ());
          r.indices.erase (std::unique (r.indices.begin (), r.indices.end ()), r.indices.end ());

          r.header = input_cloud_->header;
          cluster_indices.push_back (r); // We could avoid a copy by working directly in the vector
          }
          } // for every point in input cloud
          std::sort (cluster_indices.rbegin (), cluster_indices.rend (), comparePointClusters);
          }

          void setClusterTolerance(double tolerance) {
          eps_ = tolerance;
          }

          void setMinClusterSize (int min_cluster_size) {
          min_pts_per_cluster_ = min_cluster_size;
          }

          void setMaxClusterSize (int max_cluster_size) {
          max_pts_per_cluster_ = max_cluster_size;
          }

          void setCorePointMinPts(int core_point_min_pts) {
          minPts_ = core_point_min_pts;
          }

          protected:
          PointCloudPtr input_cloud_;
          double eps_ {0.0};
          int minPts_ {1}; // not including the point itself.
          int min_pts_per_cluster_ {1};
          int max_pts_per_cluster_ {std::numeric_limits::max()};
          KdTreePtr search_method_;

          virtual int radiusSearch(
          int index, double radius, std::vector &k_indices,
          std::vector &k_sqr_distances) const
          {
          return this->search_method_->radiusSearch(index, radius, k_indices, k_sqr_distances);
          }
          }; // class DBSCANCluster

          #endif // DBSCAN_H
          使用示例:
          pcl::search::KdTree::Ptr tree(new pcl::search::KdTree);
          tree->setInputCloud(point_cloud_input);
          std::vector cluster_indices;

          DBSCANKdtreeCluster ec;
          ec.setCorePointMinPts(20);
          ec.setClusterTolerance(0.05);
          ec.setMinClusterSize(100);
          ec.setMaxClusterSize(25000);
          ec.setSearchMethod(tree);
          ec.setInputCloud(point_cloud_input);
          ec.extract(cluster_indices);

          1.4基于模型的分割方法

          該方法時基于幾何的形狀比如球形,圓錐,平面和圓柱形來對點云進行分組,那么根據(jù)這些幾個形狀,具有相同的數(shù)學表示的點將會被分割為同一組點,論文[6]中引入了一種眾所周知的算法RANSAC(RANdom SAmple Consensus),RANSAC是強大的模型,用于檢測直線,圓等數(shù)學特征,這種應用極為廣泛且可以認為是模型擬合的最先進技術,在3D點云的分割中需要改進的方法都是繼承了這種方法?;谀P偷姆椒ň哂屑兇獾臄?shù)學原理,快速且強大,具有異值性,這種方法的主要局限性在于處理不同點云是的不準確性。這種方法在點云庫中已經(jīng)實現(xiàn)了基于線,平面,圓等各種模型。
          論文[6]在ACM官網(wǎng)上,名為Random sample consensus: a paradigm for model fitting with applications to image analysis and automated cartography Communications of the ACM
          pcl中提供的關于ransac進行分割的方法可以參考4.2。

          1.5基于圖割的分割方法

          圖優(yōu)化的方法在機器人的應用中十分流行,眾所周知的方法是FH算法[7],該方法簡單且高效,并且像Kruskal算法一樣用于在圖中查找最小生成樹。許多基于圖的方法的工作被投入到概率推理模型中,例如條件隨機場(CRF),使用CRF標記具有不同幾何表面基元的點的方法。基于圖優(yōu)化的方法在復雜的城市環(huán)境中成功地分割點云,具有接近實時的性能。為了與其他方法進行比較,基于圖形的方法可以對點云數(shù)據(jù)中的復雜場景進但是,這些方法通常無法實時運行。其中一些可能需要離線訓練等步驟。
          [7]Efficient Graph-Based Image Segmentation——一種快速的基于圖的圖像分割方法

          1.6關于pcl中提供的關于點云分割的模塊

          1.6.1最小分割算法

          https://blog.csdn.net/lemonxiaoxiao/article/details/106211294
          原理:
          最小割算法是圖論中的一個概念,其作用是以某種方式,將兩個點分開,當然這兩個點中間可能是通過無數(shù)的點再相連的。如下圖所示:

          如果要分開最左邊的點和最右邊的點,紅綠兩種割法都是可行的,但是紅線跨過了三條線,綠線只跨過了兩條。單從跨線數(shù)量上來論可以得出綠線這種切割方法更優(yōu)的結論。但假設線上有不同的權值,那么最優(yōu)切割則和權值有關了。它到底是怎么找到那條綠線的暫且不論??偠灾褪怯心敲匆粋€算法,當你給出了點之間的 “圖” (廣義的),以及連線的權值時,最小割算法就能按照你的要求把圖分開。
          顯而易見,切割有兩個非常重要的因素,第一個是獲得點與點之間的拓撲關系,也就是生成一張“圖”。第二個是給圖中的連線賦予合適的權值。只要這兩個要素合適,最小割算法就會辦好剩下的事情。
          算法流程:
          連接算法如下:1.找到每個點最近的n個點;
          1.將這n個點和父點連接;
          2.找到距離最小的兩個塊(A塊中某點與B塊中某點距離最?。?,并連接;
          3.重復3,直至只剩一個塊。
          可以用點與點之間的歐式距離來構造權值。所有線的權值可映射為線長的函數(shù):
          目標需要人為指定(center),尺寸需要提前給出(radius)。
          讓除此對象之外的物體被保護起來,不受到打擊。保護的方法就是人為加重目標范圍之外的權值(罰函數(shù)):
          pcl中對最小分割算法的實現(xiàn):
          //生成分割器
          pcl::MinCutSegmentation seg;
          //分割輸入分割目標
          seg.setInputCloud(cloud);
          //指定打擊目標(目標點)
          pcl::PointCloud::Ptr foreground_points(new pcl::PointCloud ());
          pcl::PointXYZ point;
          point.x = 68.97;
          point.y = -18.55;
          point.z = 0.57;
          foreground_points->points.push_back(point);
          seg.setForegroundPoints(foreground_points);
          //指定權函數(shù)sigma
          seg.setSigma(0.25);
          //物體大概范圍
          seg.setRadius(3.0433856);
          //用多少生成圖
          seg.setNumberOfNeighbours(14);
          //和目標點相連點的權值(至少有14個)
          seg.setSourceWeight(0.8);
          //分割結果
          std::vector clusters;
          seg.extract(clusters);

          1.6.2超體聚類

          https://www.cnblogs.com/ironstark/p/5013968.html
          原理:
          超體聚類實際上是一種特殊的區(qū)域生長算法,和無限制的生長不同,超體聚類首先需要規(guī)律的布置區(qū)域生長“晶核”。晶核在空間中實際上是均勻分布的,并指定晶核距離(Rseed)。再指定粒子距離(Rvoxel)。再指定最小晶粒(MOV),過小的晶粒需要融入最近的大晶粒。超體聚類之前,必須以八叉樹對點云進行劃分,獲得不同點團之間的鄰接關系。

          pcl中的超體聚類調用方式及可視化
          #include
          #include
          #include
          #include
          #include
          #include

          //VTK include needed for drawing graph lines
          #include

          // 數(shù)據(jù)類型
          typedef pcl::PointXYZRGBA PointT;
          typedef pcl::PointCloud PointCloudT;
          typedef pcl::PointNormal PointNT;
          typedef pcl::PointCloud PointNCloudT;
          typedef pcl::PointXYZL PointLT;
          typedef pcl::PointCloud PointLCloudT;

          //可視化
          void addSupervoxelConnectionsToViewer (PointT &supervoxel_center,
          PointCloudT &adjacent_supervoxel_centers,
          std::string supervoxel_name,
          boost::shared_ptr & viewer);


          int
          main (int argc, char ** argv)
          {
          //解析命令行
          if (argc < 2)
          {
          pcl::console::print_error ("Syntax is: %s \n "
          "--NT Dsables the single cloud transform \n"
          "-v \n-s \n"
          "-c \n-z \n"
          "-n \n", argv[0]);
          return (1);
          }

          //打開點云
          PointCloudT::Ptr cloud = boost::shared_ptr (new PointCloudT ());
          pcl::console::print_highlight ("Loading point cloud...\n");
          if (pcl::io::loadPCDFile (argv[1], *cloud))
          {
          pcl::console::print_error ("Error loading cloud file!\n");
          return (1);
          }


          bool disable_transform = pcl::console::find_switch (argc, argv, "--NT");

          float voxel_resolution = 0.008f; //分辨率
          bool voxel_res_specified = pcl::console::find_switch (argc, argv, "-v");
          if (voxel_res_specified)
          pcl::console::parse (argc, argv, "-v", voxel_resolution);

          float seed_resolution = 0.1f;
          bool seed_res_specified = pcl::console::find_switch (argc, argv, "-s");
          if (seed_res_specified)
          pcl::console::parse (argc, argv, "-s", seed_resolution);

          float color_importance = 0.2f;
          if (pcl::console::find_switch (argc, argv, "-c"))
          pcl::console::parse (argc, argv, "-c", color_importance);

          float spatial_importance = 0.4f;
          if (pcl::console::find_switch (argc, argv, "-z"))
          pcl::console::parse (argc, argv, "-z", spatial_importance);

          float normal_importance = 1.0f;
          if (pcl::console::find_switch (argc, argv, "-n"))
          pcl::console::parse (argc, argv, "-n", normal_importance);

          //如何使用SupervoxelClustering函數(shù)
          pcl::SupervoxelClustering super (voxel_resolution, seed_resolution);
          if (disable_transform)//如果設置的是參數(shù)--NT 就用默認的參數(shù)
          super.setUseSingleCameraTransform (false);
          super.setInputCloud (cloud);
          super.setColorImportance (color_importance); //0.2f
          super.setSpatialImportance (spatial_importance); //0.4f
          super.setNormalImportance (normal_importance); //1.0f

          std::map ::Ptr > supervoxel_clusters;

          pcl::console::print_highlight ("Extracting supervoxels!\n");
          super.extract (supervoxel_clusters);
          pcl::console::print_info ("Found %d supervoxels\n", supervoxel_clusters.size ());

          boost::shared_ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
          viewer->setBackgroundColor (0, 0, 0);

          PointCloudT::Ptr voxel_centroid_cloud = super.getVoxelCentroidCloud ();//獲得體素中心的點云
          viewer->addPointCloud (voxel_centroid_cloud, "voxel centroids");
          viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE,2.0, "voxel centroids"); //渲染點云
          viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_OPACITY,0.95, "voxel centroids");

          PointLCloudT::Ptr labeled_voxel_cloud = super.getLabeledVoxelCloud ();
          viewer->addPointCloud (labeled_voxel_cloud, "labeled voxels");
          viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_OPACITY,0.8, "labeled voxels");

          PointNCloudT::Ptr sv_normal_cloud = super.makeSupervoxelNormalCloud (supervoxel_clusters);

          //We have this disabled so graph is easy to see, uncomment to see supervoxel normals
          //viewer->addPointCloudNormals (sv_normal_cloud,1,0.05f, "supervoxel_normals");

          pcl::console::print_highlight ("Getting supervoxel adjacency\n");

          std::multimap supervoxel_adjacency;
          super.getSupervoxelAdjacency (supervoxel_adjacency);
          //To make a graph of the supervoxel adjacency, we need to iterate through the supervoxel adjacency multimap
          //為了使整個超體形成衣服圖,我們需要遍歷超體的每個臨近的個體
          std::multimap::iterator label_itr = supervoxel_adjacency.begin ();
          for ( ; label_itr != supervoxel_adjacency.end (); )
          {
          //First get the label
          uint32_t supervoxel_label = label_itr->first;
          //Now get the supervoxel corresponding to the label
          pcl::Supervoxel::Ptr supervoxel = supervoxel_clusters.at (supervoxel_label);

          //Now we need to iterate through the adjacent supervoxels and make a point cloud of them
          PointCloudT adjacent_supervoxel_centers;
          std::multimap::iterator adjacent_itr = supervoxel_adjacency.equal_range (supervoxel_label).first;
          for ( ; adjacent_itr!=supervoxel_adjacency.equal_range (supervoxel_label).second; ++adjacent_itr)
          {
          pcl::Supervoxel::Ptr neighbor_supervoxel = supervoxel_clusters.at (adjacent_itr->second);
          adjacent_supervoxel_centers.push_back (neighbor_supervoxel->centroid_);
          }
          //Now we make a name for this polygon
          std::stringstream ss;
          ss << "supervoxel_" << supervoxel_label;
          //This function is shown below, but is beyond the scope of this tutorial - basically it just generates a "star" polygon mesh from the points given
          //從給定的點云中生成一個星型的多邊形,
          addSupervoxelConnectionsToViewer (supervoxel->centroid_, adjacent_supervoxel_centers, ss.str (), viewer);
          //Move iterator forward to next label
          label_itr = supervoxel_adjacency.upper_bound (supervoxel_label);
          }

          while (!viewer->wasStopped ())
          {
          viewer->spinOnce (100);
          }
          return (0);
          }

          //VTK可視化構成的聚類圖
          void
          addSupervoxelConnectionsToViewer (PointT &supervoxel_center,
          PointCloudT &adjacent_supervoxel_centers,
          std::string supervoxel_name,
          boost::shared_ptr & viewer)
          {
          vtkSmartPointer points = vtkSmartPointer::New ();
          vtkSmartPointer cells = vtkSmartPointer::New ();
          vtkSmartPointer polyLine = vtkSmartPointer::New ();

          //Iterate through all adjacent points, and add a center point to adjacent point pair
          PointCloudT::iterator adjacent_itr = adjacent_supervoxel_centers.begin ();
          for ( ; adjacent_itr != adjacent_supervoxel_centers.end (); ++adjacent_itr)
          {
          points->InsertNextPoint (supervoxel_center.data);
          points->InsertNextPoint (adjacent_itr->data);
          }
          // Create a polydata to store everything in
          vtkSmartPointer polyData = vtkSmartPointer::New ();
          // Add the points to the dataset
          polyData->SetPoints (points);
          polyLine->GetPointIds ()->SetNumberOfIds(points->GetNumberOfPoints ());
          for(unsigned int i = 0; i < points->GetNumberOfPoints (); i++)
          polyLine->GetPointIds ()->SetId (i,i);
          cells->InsertNextCell (polyLine);
          // Add the lines to the dataset
          polyData->SetLines (cells);
          viewer->addModelFromPolyData (polyData,supervoxel_name);
          }

          1.6.3基于凹凸性的點云分割

          https://www.cnblogs.com/ironstark/p/5027269.html

          1.6.3.1lccp方法

          算法原理:
          超體聚類存在明顯的過分割現(xiàn)象。點云完成超體聚類之后,對于過分割的點云需要計算不同的塊之間凹凸關系。凹凸關系通過?CC(Extended Convexity Criterion)?和?SC (Sanity criterion)判據(jù)來進行判斷。其中 CC 利用相鄰兩片中心連線向量與法向量夾角來判斷兩片是凹是凸。顯然,如果圖中a1>a2則為凹,反之則為凸。
          考慮到測量噪聲等因素,需要在實際使用過程中引入門限值(a1需要比a2大出一定量)來濾出較小的凹凸誤判。此外,為去除一些小噪聲引起的誤判,還需要引入“第三方驗證”,如果某塊和相鄰兩塊都相交,則其凹凸關系必相同。CC 判據(jù)最終如CCe:
          如果相鄰兩面中,有一個面是單獨的,cc判據(jù)是無法將其分開的。舉個簡單的例子,兩本厚度不同的書并排放置,視覺算法應該將兩本書分割開。如果是臺階,則視覺算法應該將臺階作為一個整體。本質上就是因為厚度不同的書存在surface-singularities。為此需要引入SC判據(jù),來對此進行區(qū)分。
          如圖所示,相鄰兩面是否真正聯(lián)通,是否存在單獨面,與θ角有關,θ角越大,則兩面真的形成凸關系的可能性就越大。據(jù)此,可以設計SC判據(jù):
          其中S(向量)為兩平面法向量的叉積。
            最終,兩相鄰面之間凸邊判據(jù)為:
          在標記完各個小區(qū)域的凹凸關系后,則采用區(qū)域增長算法將小區(qū)域聚類成較大的物體。此區(qū)域增長算法受到小區(qū)域凹凸性限制,即只允許區(qū)域跨越凸邊增長。
          //生成LCCP分割器
          pcl::LCCPSegmentation::LCCPSegmentation LCCPseg;
          //輸入超體聚類結果
          seg.setInputSupervoxels(supervoxel_clusters,supervoxel_adjacency);
          //CC效驗beta值
          seg.setConcavityToleranceThreshold (concavity_tolerance_threshold);
          //CC效驗的k鄰點
          seg.setKFactor (k_factor_arg)
          //
          seg.setSmoothnessCheck (bool_use_smoothness_check_arg,voxel_res_arg,seed_res_arg,smoothness_threshold_arg = 0.1);
          //SC效驗
          seg.setSanityCheck (bool_use_sanity_criterion_arg);
          //最小分割尺寸
          seg.setMinSegmentSize (min_segment_size_arg)

          seg.segment();
          seg.relabelCloud (pcl::PointCloud &labeled_cloud_arg);

          1.6.3.2cpc方法

          出自論文:*Constrained Planar Cuts - Object Partitioning for Point Clouds*?。
          算法原理:
          和其他基于凹凸性的方法相同,本方法也需要先進行超體聚類。在完成超體聚類之后,采用和LCCP相同的凹凸性判據(jù)獲得各個塊之間的凹凸關系。在獲得凹凸性之后,CPC方法所采取的措施是不同的。其操作稱為?半全局分割
            在分割之前,首先需要生成?EEC(Euclidean edge cloud),?EEC的想法比較神奇,因為凹凸性定義在相鄰兩個”片“上,換言之,定義在連接相鄰兩“片”的edge上。將每個edge抽象成一個點云,則得到了附帶凹凸信息的點云。如圖所示,左圖是普通點云,但附帶了鄰接和凹凸信息。右邊是EEC,對凹邊賦權值1,其他為0。
           顯而易見,某處如果藍色的點多,那么就越?,就越應該切開(所謂切開實際上是用平面劃分)。問題就轉化為利用藍點求平面了。利用點云求一個最可能的平面當然需要請出我們的老朋友?RanSaC?.?但此處引入一個評價函數(shù),用于評價此次分割的?優(yōu)良程度Sm,**Pm 是EEC中的點.
          單純的weighted RanSac算法并不夠。其會導致對某些圖形的錯誤分割,所以作者對此做了第一次“修補".錯誤的分割如下圖所示
          方法的原理很簡單,垂直于凹邊表面的點具有更高的權重,顯然,對于EEC中的凹點,只要取其少量鄰點即可估計垂直方向。
            這種修補后還有一個問題,如果這個分割面過長的情況下,有可能會誤傷。如圖所示:
          這種修補方法的原理就更加簡單粗暴了,對凹點先進行歐式分割(限制增長上限),之后再分割所得的子域里進行分割。
            在修修補補之后,CPC算法終于可以投入使用了,從測試集的結果來看,效果還是很好的。
          pcl中cpc算法的調用:
          //生成CPC分割器
          pcl::CPCSegmentation::CPCSegmentation seg;
          //輸入超體聚類結果
          seg.setInputSupervoxels(supervoxel_clusters,supervoxel_adjacency);
          //設置分割參數(shù)
          setCutting (max_cuts = 20,
          cutting_min_segments = 0,
          cutting_min_score = 0.16,
          locally_constrained = true,
          directed_cutting = true,
          clean_cutting = false);
          seg.setRANSACIterations (ransac_iterations);
          seg.segment();
          seg.relabelCloud (pcl::PointCloud &labeled_cloud_arg);

          1.6.4基于形態(tài)學的點云分割

          本方法主要用于航空測量中。
           出自論文: A Progressive Morphological Filter for Removing Nonground Measurements From Airborne LIDAR Data
          算法原理:
          對于圖像而言,形態(tài)學運算一般是針對二值圖像而言的。當然也有針對灰度的形態(tài)學運算,其原理應該和針對點云的形態(tài)學運算類似(我猜的)。形態(tài)學算子的設計實際上非常簡單,只要能設計出基礎的膨脹和腐蝕算子就可以組合得到一系列的處理。
            其中,d表示膨脹算子,e表示腐蝕算子。算子的原理有些像中值濾波,通過選取一個窗w中最高點或最低點來完成圖像的膨脹和腐蝕,其效果如下圖所示:
            在航拍圖的橫截面上可以很清楚的看出膨脹與腐蝕的效果。對于房子和樹可以用不同尺度窗(從小到大)先腐蝕至地面。但是這會導致一個巨大的問題。。。如果地面上有個土包(比如秦始皇陵),那么這個土包也會在一次次的腐蝕中被消耗。那豈不是秦始皇陵就發(fā)現(xiàn)不了?所以還有一個補償算法用于解決這個問題,稱為線性補償算法。
            建筑物和土包有一個巨大的區(qū)別,建筑物往往相對比較陡峭,而土包卻是變化比較平緩的。這個可以作為一個判據(jù),用于判斷物體是否需要被腐蝕,也作為窗收斂的判據(jù)。
            式中k稱為斜率,代表下一個窗的大小是上一個窗的2^k倍
            s是一個因子
            dh是切深判據(jù),每一次腐蝕大于切深判據(jù)才認為是有效的,小于切深判據(jù)則是土包。
          pcl中本算法的調用:
          //生成形態(tài)濾波器
          pcl::ProgressiveMorphologicalFilter pmf;
          pmf.setInputCloud (cloud);
          //設置窗的大小以及切深,斜率信息
          pmf.setMaxWindowSize (20);
          pmf.setSlope (1.0f);
          pmf.setInitialDistance (0.5f);
          pmf.setMaxDistance (3.0f);
          //提取地面
          pmf.extract (ground->indices);

          // 從標號到點云
          pcl::ExtractIndices extract;
          extract.setInputCloud (cloud);
          extract.setIndices (ground);
          extract.filter (*cloud_filtered);

          1.7總結

          一般來說,點云的分割有兩種基本方法。
          第一種方法使用純數(shù)學模型和幾何推理技術,如區(qū)域增長或模型擬合,將線性和非線性模型擬合到點云數(shù)據(jù)。這種方法允許快速運行時間能實現(xiàn)良好的結果。這種方法的局限性在于在擬合物體時難以選擇模型的大小,對噪聲敏感并且在復雜場景中不能很好地工作。
          第二種方法使用特征描述子的方法從點云數(shù)據(jù)中提取3D特征,并使用機器學習技術來學習不同類別的對象類型,然后使用結果模型對所獲取的數(shù)據(jù)進行分類。在復雜場景中,機器學習技術將優(yōu)于純粹基于幾何推理的技術。原因是由于噪聲,密度不均勻,點云數(shù)據(jù)中的遮擋,很難找到并將復雜的幾何圖元擬合到物體上。雖然機器學習技術可以提供更好的結果,但它們通常很慢并且依賴于特征提取過程的結果。

          本文僅做學術分享,如有侵權,請聯(lián)系刪文。

          —THE END—
          瀏覽 75
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产无遮挡又黄又爽又色视频 | 538在线精品 | 91午夜理论 | 色五月丁香五月 | 爽到高潮免费视频 |