PCL點雲分割(1)
阿新 • • 發佈:2019-01-26
點雲分割是根據空間,幾何和紋理等特徵對點雲進行劃分,使得同一劃分內的點雲擁有相似的特徵,點雲的有效分割往往是許多應用的前提,例如逆向工作,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); }
執行結果:
不再一一檢視視覺化的結果
為了更切合實際的應用我會在這些基本的程式的基礎上,進行與實際結合的例項,因為這些都是官方給的例項,我是首先學習一下,至少過一面,這樣在後期結合實際應用的過程中會更加容易一點。(因為我也是一邊學習,然後回頭再在基礎上進行更修)
同時有很多在我的微信公眾號上的同學後臺與我交流,有時候不能即時回覆敬請諒解,(之前,就有一個不知道哪個學校的關注後就一直問我問題,告訴它基本的案例,還要我告訴他怎麼實現,本人不才,我也是入門者阿,)
請掃面二維碼關注