基於歐式距離的分割和基於區域生長的分割本質上都是用區分鄰里關系遠近來完成的。由於點雲數據提供了更高維度的數據,故有很多信息可以提取獲得。歐幾里得算法使用鄰居之間距離作為判定標准,而區域生長算法則利用了法線,曲率,顏色等信息來判斷點雲是否應該聚成一類。
(1)歐幾里德算法
具體的實現方法大致是:
- 找到空間中某點p10,有kdTree找到離他最近的n個點,判斷這n個點到p的距離。將距離小於閾值r的點p12,p13,p14....放在類Q里
- 在 Q\p10 里找到一點p12,重復1
- 在 Q\p10,p12 找到一點,重復1,找到p22,p23,p24....全部放進Q里
- 當 Q 再也不能有新點加入了,則完成搜索了
因為點雲總是連成片的,很少有什么東西會浮在空中來區分。但是如果結合此算法可以應用很多東東。比如
- 半徑濾波刪除離群點
- 采樣一致找到桌面或者除去濾波
當然,一旦桌面被剔除,桌上的物體就自然成了一個個的浮空點雲團。就能夠直接用歐幾里德算法進行分割了,這樣就可以提取出我們想要識別的東西
在這里我們就可以使用提取平面,利用聚類的方法平面去掉再顯示剩下的所有聚類的結果,在這里也就是有關注我的微信公眾號的小伙伴向我請教,說雖然都把平面和各種非平面提取出來了,但是怎么把非平面的聚類對象可視化出來呢?
哈哈,剛開始我也以為沒有例程實現這樣的可視化,也許比較難吧,但是仔細一想,提取出來的聚類的對象都是單獨的顯示在相對與源文件不變的位置所以我們直接相加就應該可以實現阿~所以廢話沒多說我就直接寫程序,的確可視化的結果就是我想要的結果
那么我們看一下我的代碼吧
#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) { // 讀取文件 pcl::PCDReader reader; pcl::PointCloud<pcl::PointXYZ>::Ptr add_cloud(new pcl::PointCloud<pcl::PointXYZ>); 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; //* // 下采樣,體素葉子大小為0.01 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) { // 從剩余點雲中再分割出最大的平面分量 (因為我們要處理的點雲的數據是兩個平面的存在的) seg.setInputCloud (cloud_filtered); seg.segment (*inliers, *coefficients); if (inliers->indices.size () == 0) //如果內點的數量已經等於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); // 得到與平面表面相關聯的點雲數據 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; } // 創建用於提取搜索方法的kdtree樹對象 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++; *add_cloud+=*cloud_cluster; pcl::io::savePCDFileASCII("add_cloud.pcd",*add_cloud); } return (0); }
編譯生成可執行文件后結果如下
那么我們查看以下源文件可視化的結果
再可視化我們聚類后除了平面的可視化的結果,從中可以看出效果還是很明顯的。
當然總結一下,我們在實際應用的過程中可能沒那么輕松,因為我們要根據實際的點雲的大小來設置相關的參數,如果參數錯誤就不太能實現現在的效果。
所以對實際應用中參數的設置是需要經驗的吧,下一期會介紹其他的分割方法
有興趣這關注微信公眾號,加入我們與更多的人交流,同時也歡迎更多的來自網友的分享