条件欧几里得聚类 pcl::ConditionalEuclideanClustering

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”将切换到强度通道可视化。太小的簇将被标记为红色,太大的簇将被标记为蓝色,并且实际的感兴趣的簇/对象将在黄色和青色之间随机着色。

posted @ 2020-02-23 11:03  ID是菜鸟  阅读(3892)  评论(1编辑  收藏  举报