欧几里得聚类提取
在本教程中, 我们将学习如何使用 类提取欧几里得聚类 pcl::EuclideanclusterExtraction。为 了不使教程复杂化, 其中的某些元素, 例如平面分割算法, 将不在这里进行解释。请查看平面 模型分割 教程以获取更多信息。
理论入门
聚类方法需要将无组织的点云模型 P P P 分成更小的部分,从而 P P P 显着减少整体处理时间。欧几里 得意义上的简单数据聚类方法可以通过使用固定宽度框或更普遍地使用八叉树数据结构的空间 的 3D 网格细分来实现。这种特定表示的构建速度非常快, 并且对于需要占用空间的体积表示 或每个生成的 3D 框(或八叉叶)中的数据可以用不同的结构近似的情况很有用。然而, 在更 一般的意义上,我们可以利用最近的邻居并实现一种本质上类似于洪水填充算法的聚类技术。
假设我们有一个点云, 上面有一个表格和对象。我们想要找到并分割位于平面上的单个对象点 簇。假设我们使用 K d \mathrm{Kd} Kd-tree 结构来寻找最近的邻居, 那么算法步骤将是(来自 [RusuDissertation]):
- 为输入点云数据集创建 K d K d Kd-tree 表示 P P P;
- 建立一个空的簇列表 C C C 和一个需要检查的点的队列 Q Q Q;
- 然后对于每个点 p i ∈ P \boldsymbol{p}_{i} \in P pi∈P, 执行以下步骤:
- 添加 p i \boldsymbol{p}_{i} pi 到当前队列 Q Q Q;
- 对于每一点 p i ∈ Q \boldsymbol{p}_{i} \in Q pi∈Q 做:
- 在半径为 的球体中搜索 P i k P_{i}^{k} Pik 点邻居 的集合; p i r < d t h \boldsymbol{p}_{i} r<d_{t h} pir<dth
- 对于每个邻居 p i k ∈ P i k \boldsymbol{p}_{i}^{k} \in P_{i}^{k} pik∈Pik, 检查该点是否已经被处理, 如果没有, 则将其 添加到 Q Q Q;
- 处理完所有点的列表后 Q Q Q, 添加 Q Q Q 到集群列表中 C C C, 并重置 Q Q Q 为空列表 4. 当所有点 p i ∈ P \boldsymbol{p}_{i} \in P pi∈P 都被处理并且现在是点簇列表的一部分时, 算法终止 C C C
Coding
逐段分解代码
// 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);
//创建:pcl:`SACSegmentation <pcl::SACSegmentation>`对象并设置模型和方法类型。
//这也是我们指定“距离阈值”的地方,它决定了一个点必须离模型多近才能被视为内点
//我们将使用 RANSAC 方法 (pcl::SAC_RANSAC) 作为选择的稳健估计器。
int 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);
在那里,我们为我们的提取算法的搜索方法创建了一个 KdTree 对象。
std::vector<pcl::PointIndices> cluster_indices;
在这里,我们创建了一个PointIndices向量,其中包含vector<int>
中的实际索引信息。每个检测到的集群的索引都保存在这里 - 请注意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 (const auto& idx : it->indices)
cloud_cluster->push_back ((*cloud_filtered)[idx]); //*
cloud_cluster->width = cloud_cluster->size ();
cloud_cluster->height = 1;
cloud_cluster->is_dense = true;
打印分割结果到pcd文件
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++;
}
运行结果
aruchid@aruchidpc:~/code/Point-Cloud-Processing-example-master/第十二章/3 cluster_extraction_no/source$ pcl_viewer cloud_cluster_0.pcd cloud_cluster_1.pcd cloud_cluster_2.pcd cloud_cluster_3.pcd cloud_cluster_4.pcd
The viewer window provides interactive commands; for help, press 'h' or 'H' from within the window.
> Loading cloud_cluster_0.pcd [PCLVisualizer::setUseVbos] Has no effect when OpenGL version is ≥ 2
[done, 108.36 ms : 4857 points]
Available dimensions: x y z
> Loading cloud_cluster_1.pcd [done, 0.759765 ms : 1386 points]
Available dimensions: x y z
> Loading cloud_cluster_2.pcd [done, 0.189316 ms : 321 points]
Available dimensions: x y z
> Loading cloud_cluster_3.pcd [done, 0.164894 ms : 291 points]
Available dimensions: x y z
> Loading cloud_cluster_4.pcd [done, 0.094636 ms : 123 points]
Available dimensions: x y z
通过bash查看输出
pcl_viewer cloud_cluster_0.pcd cloud_cluster_1.pcd cloud_cluster_2.pcd cloud_cluster_3.pcd cloud_cluster_4.pcd