Euclidean Cluster Extraction
在本教程中,我们将学习如何使用pcl :: EuclideanClusterExtraction类提取欧几里得群集。 为了不使本教程复杂,此处将不解释其中的某些元素,例如平面分割算法。 请查看“平面模型细分”教程以获取更多信息。
Theoretical Primer
聚类方法需要将无组织的点云模型P划分为较小的部分,以便显着减少P的总处理时间。可以通过使用固定宽度的框(或更普遍地是八叉树数据结构)利用空间的3D网格细分来实现欧几里得意义上的简单数据聚类方法。此特定表示形式的构建非常快,并且对于需要占用空间的体积表示形式或每个结果3D框(或八叉树叶子)中的数据可以采用不同结构进行近似的情况很有用。但是,从更一般的意义上讲,我们可以利用最邻近的邻居并实现与泛洪填充算法基本相似的聚类技术。
假设我们得到一个点云,上面有一个桌子和一个对象。我们想要找到并分割位于平面上的各个对象点簇。假设我们使用Kd-tree结构查找最近的邻居,则其算法步骤为(摘自[RusuDissertation]):
1.为输入点云数据集P创建Kd树表示;
2.设置一个空的聚类列表C,以及一个需要检查的点队列Q;
3.然后对于P中的每个\ boldsymbol {p} _i \ i \执行以下步骤:
将dd \ boldsymbol {p} _i添加到当前队列Q;
对于Q中的每个点\ boldsymbol {p} i \ i,请执行以下操作:
在半径r
对于P ^ k_i中的每个邻居\ boldsymbol {p} ^ k_i \,检查该点是否已被处理,如果尚未处理,则将其添加到Q;
处理完Q中所有点的列表后,将Q添加到群集C的列表中,并将Q重置为空列表
4.当所有点\ boldsymbol {p} _i \ in P已被处理并且现在属于点簇C的列表时,算法终止
The Code
首先,下载数据集table_scene_lms400.pcd并将其保存到磁盘中。
然后,在您喜欢的编辑器中创建一个文件cluster_extraction.cpp,并将以下内容放入其中:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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->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->size () << " data points." << std::endl; //*
// Create the segmentation object for the planar model and set all the parameters
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->size ();
while (cloud_filtered->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;
}
// Extract the planar inliers from the input cloud
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->size () << " data points." << std::endl;
// Remove the planar inliers, extract the rest
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);
ec.setMaxClusterSize (25000);
ec.setSearchMethod (tree);
ec.setInputCloud (cloud_filtered);
ec.extract (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->push_back ((*cloud_filtered)[*pit]); //*
cloud_cluster->width = cloud_cluster->size ();
cloud_cluster->height = 1;
cloud_cluster->is_dense = true;
std::cout << "PointCloud representing the Cluster: " << cloud_cluster->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);
}
现在,让我们逐段分解代码,跳过显而易见的部分。
// 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->size () << " data points." << std::endl;
.
.
.
while (cloud_filtered->size () > 0.3 * nr_points)
{
.
.
.
// Remove the plane inliers, extract the rest
extract.setNegative (true);
extract.filter (*cloud_f);
cloud_filtered = cloud_f;
}
上面的代码已经在其他教程中进行了介绍,因此您可以在此处阅读说明(特别是平面模型分割和从PointCloud提取索引)。
// 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);
在那里,我们为提取算法的搜索方法创建了一个KdTree对象。
std::vector<pcl::PointIndices> cluster_indices;
在这里,我们创建一个PointIndices的向量,该向量在vector 中包含实际的索引信息。 每个检测到的簇的索引都保存在这里-请注意,cluster_indices是一个向量,其中包含每个检测到的簇的PointIndices的一个实例。 因此,cluster_indices [0]包含我们点云中第一个集群的所有索引。
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance (0.02); // 2cm
ec.setMinClusterSize (100);
ec.setMaxClusterSize (25000);
ec.setSearchMethod (tree);
ec.setInputCloud (cloud_filtered);
ec.extract (cluster_indices);
在这里,由于点云的类型为PointXYZ,所以我们将创建一个EuclideanClusterExtraction对象,其点类型为PointXYZ。 我们还在设置提取的参数和变量。 请小心为**setClusterTolerance()**设置正确的值。 如果取很小的值,则可能会发生一个实际对象被视为多个群集的情况。 另一方面,如果将该值设置得太高,则可能会将多个对象视为一个群集。 因此,我们的建议是仅测试并尝试哪个值适合您的数据集。
我们强加发现的集群必须至少具有**setMinClusterSize()点和最大setMaxClusterSize()**点。
现在,我们从点云中提取聚类,并将索引保存在cluster_indices中。 为了将每个群集从vector 中分离出来,我们必须遍历cluster_indices,为每个条目创建一个新的PointCloud,并将当前群集的所有点写入PointCloud。
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->push_back ((*cloud_filtered)[*pit]); //*
cloud_cluster->width = cloud_cluster->size ();
cloud_cluster->height = 1;
cloud_cluster->is_dense = true;
编译并运行程序
将以下行添加到您的CMakeLists.txt
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project(cluster_extraction)
find_package(PCL 1.2 REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
add_executable (cluster_extraction cluster_extraction.cpp)
target_link_libraries (cluster_extraction ${PCL_LIBRARIES})
生成可执行文件后,即可运行它。 只需做:
./cluster_extraction
您将看到类似于以下内容:
PointCloud before filtering has: 460400 data points.
PointCloud after filtering has: 41049 data points.
[SACSegmentation::initSACModel] Using a model of type: SACMODEL_PLANE
[SACSegmentation::initSAC] Using a method of type: SAC_RANSAC with a model threshold of 0.020000
[SACSegmentation::initSAC] Setting the maximum number of iterations to 100
PointCloud representing the planar component: 20522 data points.
[SACSegmentation::initSACModel] Using a model of type: SACMODEL_PLANE
[SACSegmentation::initSAC] Using a method of type: SAC_RANSAC with a model threshold of 0.020000
[SACSegmentation::initSAC] Setting the maximum number of iterations to 100
PointCloud representing the planar component: 12429 data points.
PointCloud representing the Cluster: 4883 data points.
PointCloud representing the Cluster: 1386 data points.
PointCloud representing the Cluster: 320 data points.
PointCloud representing the Cluster: 290 data points.
PointCloud representing the Cluster: 120 data points.
您还可以查看输出cloud_cluster_0.pcd,cloud_cluster_1.pcd,cloud_cluster_2.pcd,cloud_cluster_3.pcd和cloud_cluster_4.pcd:
$ ./pcl_viewer cloud_cluster_0.pcd cloud_cluster_1.pcd cloud_cluster_2.pcd cloud_cluster_3.pcd cloud_custer_4.pcd
现在,您可以在一个查看器中查看不同的群集。 您应该看到类似以下内容: