1 .條件歐幾里得聚類
本教程描述如何使用pcl::ConditionalEuclideanClustering該類:一種分割算法,該算法基於歐幾里得距離和需要保持的用戶可自定義條件對點進行聚類。
此類使用與歐幾里得聚類提取,區域增長分割和基於顏色的區域增長分割相同的類似貪婪/區域增長/洪水填充方法。與其他類相比,使用該類的優點是,聚類的約束(純歐幾里得,平滑度,RGB)現在可以由用戶自定義。一些缺點包括:沒有初始的種子系統,沒有過度分割和不足分割控制,以及從主計算循環內部調用條件函數的時間效率較低的事實。
理論入門
2.理論入門
該歐幾里德集群提取和區域生長分割的教程已經解釋的區域非常准確地生長算法。這些說明的唯一補充是,現在可以完全定制將鄰居合並到當前集群中所需要滿足的條件。
隨着聚類的增長,它將評估聚類內已經存在的點與附近候選點之間的用戶定義條件。候選點(最近的相鄰點)是使用聚類中每個點周圍的歐式半徑搜索來找到的。對於結果集群中的每個點,條件必須與它的至少一個鄰居保持,而不是與所有鄰居保持。
條件歐幾里得聚類也可以基於大小約束自動過濾聚類。分類過小或過大的群集仍可以在以后檢索。
3.代碼
首先,下載數據集Statues_4.pcd並從檔案中提取PCD文件。這是一個非常大的室外環境數據集,我們希望將單獨的對象聚類,並且即使建築物以歐幾里得意義連接,也希望將建築物與地面分開。現在,conditional_euclidean_clustering.cpp在您喜歡的編輯器中創建一個文件,並將以下內容放入其中:
#include <pcl/point_types.h>#include <pcl/io/pcd_io.h>#include <pcl/console/time.h>
#include <pcl/filters/voxel_grid.h>#include <pcl/features/normal_3d.h>#include <pcl/segmentation/conditional_euclidean_clustering.h>
typedef pcl::PointXYZI PointTypeIO;typedef pcl::PointXYZINormal PointTypeFull;
boolenforceIntensitySimilarity (const PointTypeFull& point_a, const PointTypeFull& point_b, float squared_distance){
if (fabs (point_a.intensity - point_b.intensity) < 5.0f)
return (true);
else
return (false);}
boolenforceCurvatureOrIntensitySimilarity (const PointTypeFull& point_a, const PointTypeFull& point_b, float squared_distance){
Eigen::Map<const Eigen::Vector3f> point_a_normal = point_a.getNormalVector3fMap (), point_b_normal = point_b.getNormalVector3fMap ();
if (fabs (point_a.intensity - point_b.intensity) < 5.0f)
return (true);
if (fabs (point_a_normal.dot (point_b_normal)) < 0.05)
return (true);
return (false);}
boolcustomRegionGrowing (const PointTypeFull& point_a, const PointTypeFull& point_b, float squared_distance){
Eigen::Map<const Eigen::Vector3f> point_a_normal = point_a.getNormalVector3fMap (), point_b_normal = point_b.getNormalVector3fMap ();
if (squared_distance < 10000)
{
if (fabs (point_a.intensity - point_b.intensity) < 8.0f)
return (true);
if (fabs (point_a_normal.dot (point_b_normal)) < 0.06)
return (true);
}
else
{
if (fabs (point_a.intensity - point_b.intensity) < 3.0f)
return (true);
}
return (false);}
intmain (int argc, char** argv){
// Data containers used
pcl::PointCloud<PointTypeIO>::Ptr cloud_in (new pcl::PointCloud<PointTypeIO>), cloud_out (new pcl::PointCloud<PointTypeIO>);
pcl::PointCloud<PointTypeFull>::Ptr cloud_with_normals (new pcl::PointCloud<PointTypeFull>);
pcl::IndicesClustersPtr clusters (new pcl::IndicesClusters), small_clusters (new pcl::IndicesClusters), large_clusters (new pcl::IndicesClusters);
pcl::search::KdTree<PointTypeIO>::Ptr search_tree (new pcl::search::KdTree<PointTypeIO>);
pcl::console::TicToc tt;
// Load the input point cloud
std::cerr << "Loading...\n", tt.tic ();
pcl::io::loadPCDFile ("Statues_4.pcd", *cloud_in);
std::cerr << ">> Done: " << tt.toc () << " ms, " << cloud_in->points.size () << " points\n";
// Downsample the cloud using a Voxel Grid class
std::cerr << "Downsampling...\n", tt.tic ();
pcl::VoxelGrid<PointTypeIO> vg;
vg.setInputCloud (cloud_in);
vg.setLeafSize (80.0, 80.0, 80.0);
vg.setDownsampleAllData (true);
vg.filter (*cloud_out);
std::cerr << ">> Done: " << tt.toc () << " ms, " << cloud_out->points.size () << " points\n";
// Set up a Normal Estimation class and merge data in cloud_with_normals
std::cerr << "Computing normals...\n", tt.tic ();
pcl::copyPointCloud (*cloud_out, *cloud_with_normals);
pcl::NormalEstimation<PointTypeIO, PointTypeFull> ne;
ne.setInputCloud (cloud_out);
ne.setSearchMethod (search_tree);
ne.setRadiusSearch (300.0);
ne.compute (*cloud_with_normals);
std::cerr << ">> Done: " << tt.toc () << " ms\n";
// Set up a Conditional Euclidean Clustering class
std::cerr << "Segmenting to clusters...\n", tt.tic ();
pcl::ConditionalEuclideanClustering<PointTypeFull> cec (true);
cec.setInputCloud (cloud_with_normals);
cec.setConditionFunction (&customRegionGrowing);
cec.setClusterTolerance (500.0);
cec.setMinClusterSize (cloud_with_normals->points.size () / 1000);
cec.setMaxClusterSize (cloud_with_normals->points.size () / 5);
cec.segment (*clusters);
cec.getRemovedClusters (small_clusters, large_clusters);
std::cerr << ">> Done: " << tt.toc () << " ms\n";
// Using the intensity channel for lazy visualization of the output
for (int i = 0; i < small_clusters->size (); ++i)
for (int j = 0; j < (*small_clusters)[i].indices.size (); ++j)
cloud_out->points[(*small_clusters)[i].indices[j]].intensity = -2.0;
for (int i = 0; i < large_clusters->size (); ++i)
for (int j = 0; j < (*large_clusters)[i].indices.size (); ++j)
cloud_out->points[(*large_clusters)[i].indices[j]].intensity = +10.0;
for (int i = 0; i < clusters->size (); ++i)
{
int label = rand () % 8;
for (int j = 0; j < (*clusters)[i].indices.size (); ++j)
cloud_out->points[(*clusters)[i].indices[j]].intensity = label;
}
// Save the output point cloud
std::cerr << "Saving...\n", tt.tic ();
pcl::io::savePCDFile ("output.pcd", *cloud_out);
std::cerr << ">> Done: " << tt.toc () << " ms\n";
return (0);}
4.說明
由於條件歐幾里得群集類是針對更高級的用戶的,因此,我將跳過對代碼更明顯部分的解釋:
- pcl::io::loadPCDFile並且pcl::io::savePCDFile用於加載和保存點雲數據。
- pcl::console::TicToc 用於輕松輸出時序結果。
- 正在使用 VoxelGrid濾鏡對 PointCloud進行降采樣(第66-73行)以對雲進行降采樣並提供更均衡的點密度。估計點雲中的表面法線(第75-83行)用於估計法線,該法線將附加到點信息中。條件歐幾里德聚類類將以模板化pcl::PoitnXYZINormal,包含x,y,z,強度,法線和曲率信息以用於條件函數。
第85-95行建立了條件歐幾里得聚類類以供使用:
// Set up a Conditional Euclidean Clustering class
std::cerr << "Segmenting to clusters...\n", tt.tic ();
pcl::ConditionalEuclideanClustering<PointTypeFull> cec (true);
cec.setInputCloud (cloud_with_normals);
cec.setConditionFunction (&customRegionGrowing);
cec.setClusterTolerance (500.0);
cec.setMinClusterSize (cloud_with_normals->points.size () / 1000);
cec.setMaxClusterSize (cloud_with_normals->points.size () / 5);
cec.segment (*clusters);
cec.getRemovedClusters (small_clusters, large_clusters);
std::cerr << ">> Done: " << tt.toc () << " ms\n";
對不同代碼行的更詳細描述:
- 該類用TRUE初始化。這將允許提取太小或太大的簇。如果不使用該類進行初始化,則可以節省一些計算時間和內存。
- 可以使用從PCLBase類派生的方法(即:setInputCloud和)來指定類的輸入數據setIndices。
- 隨着聚類的增長,它將評估聚類內已存在的點與附近候選點之間的用戶定義條件。有關條件功能的更多信息,請參見下文。聚類容差是k-NN搜索的半徑,用於查找候選點。
- 占雲總點數不到0.1%的集群被認為太小。
- 占雲總點數超過20%的集群被認為太大。
- 結果簇以以下pcl::IndicesClusters格式存儲:索引數組是輸入點雲的索引點。
- 太小的簇或太大的簇不會傳遞到主輸出,而是可以在單獨的pcl::IndicesClusters數據容器中檢索,但是只有使用TRUE初始化了類。
第12-49行顯示了條件函數的一些示例:
條件函數的格式是固定的:前兩個輸入參數的類型必須與條件歐幾里德聚類中使用的模板化類型相同。這些參數將傳遞當前種子點(第一個參數)和當前候選點(第二個參數)的點信息。第三個輸入參數必須為浮點數。該參數將傳遞種子和候選點之間的平方距離。盡管此信息也可以使用前兩個自變量進行計算,但是它已經由底層的最近鄰居搜索提供,並且可以用於輕松制作距離相關的條件函數。輸出參數必須為布爾值。返回TRUE將把候選點合並到種子點的簇中。返回FALSE不會通過該特定點對合並候選點,但是,仍然有可能兩個點將通過不同的點對關系最終位於同一群集中。
這些示例條件函數僅用於指示如何使用它們。例如,只要第二條件函數在表面法線方向上相似或強度值相似,它們就會增長。希望這應該將紋理與一個群集相似的建築物群集在一起,但不要將它們合並為與相鄰對象相同的群集。如果強度與附近的物體足夠不同並且附近的物體沒有以相同的法線共享附近的表面,則可以解決此問題。第三條件函數與第二條件函數相似,但根據點之間的距離具有不同的約束。
第97-109行包含一段代碼,該代碼是一種快速而骯臟的解決方案,用於可視化結果:
/
// Using the intensity channel for lazy visualization of the output
for (int i = 0; i < small_clusters->size (); ++i)
for (int j = 0; j < (*small_clusters)[i].indices.size (); ++j)
cloud_out->points[(*small_clusters)[i].indices[j]].intensity = -2.0;
for (int i = 0; i < large_clusters->size (); ++i)
for (int j = 0; j < (*large_clusters)[i].indices.size (); ++j)
cloud_out->points[(*large_clusters)[i].indices[j]].intensity = +10.0;
for (int i = 0; i < clusters->size (); ++i)
{
int label = rand () % 8;
for (int j = 0; j < (*clusters)[i].indices.size (); ++j)
cloud_out->points[(*clusters)[i].indices[j]].intensity = label;
}
當使用PCL的標准PCD查看器打開輸出點雲時,按“ 5”將切換到強度通道可視化。太小的簇將被標記為紅色,太大的簇將被標記為藍色,並且實際的感興趣的簇/對象將在黃色和青色之間隨機着色。