2条件欧几里得聚类

news2024/11/17 12:23:20

条件欧几里得聚类

本教程介绍如何使用类: 一种分割算法,根据欧几里得距离和需要保持的用户可自定义条件对点进行聚类。pcl::ConditionalEuclideanClustering

此类使用与 Euclidean Cluster Extraction、Region growing segmentation 和 Color-based regional growing segmentation 中使用的相同 greedy-like /region-growing /flood-fill 方法。 与其他类相比,使用此类的优势在于,聚类的约束(纯欧几里得、平滑度、RGB)现在可以由用户自定义。 一些缺点包括:没有初始种子系统,没有过度和不足分段控制,以及从主计算循环内部调用条件函数的时间效率较低。

理论入门

Euclidean Cluster Extraction 和 Region growing segmentation 教程已经非常准确地解释了区域增长算法。 对这些解释的唯一补充是,现在可以完全自定义将邻居合并到当前集群中所需的条件。

随着聚类的增长,它将评估聚类内已有的点与附近的候选点之间的用户定义条件。 候选点(最近邻点)使用围绕聚类中每个点的欧氏半径搜索来查找。 对于生成的聚类中的每个点,条件需要至少与一个相邻要素保持,而不是与其所有相邻要素保持。

Conditional Euclidean Clustering 类还可以根据大小约束自动筛选聚类。 分类为“太小”或“太大”的集群仍可在之后检索。

代码

首先,下载数据集 Statues_4.pcd 并从存档中提取 PCD 文件。 这是一个非常大的室外环境数据集,我们的目标是将单独的对象聚集在一起,并且还希望将建筑物与地平面分开,即使它是以欧几里得意义连接的。

现在,在您最喜欢的编辑器中创建一个文件,并将以下内容放入其中:conditional_euclidean_clustering.cpp

  1#include <pcl/point_types.h>
  2#include <pcl/io/pcd_io.h>
  3#include <pcl/console/time.h>
  4
  5#include <pcl/filters/voxel_grid.h>
  6#include <pcl/features/normal_3d.h>
  7#include <pcl/segmentation/conditional_euclidean_clustering.h>
  8
  9typedef pcl::PointXYZI PointTypeIO;
 10typedef pcl::PointXYZINormal PointTypeFull;
 11
 12bool
 13enforceIntensitySimilarity (const PointTypeFull& point_a, const PointTypeFull& point_b, float /*squared_distance*/)
 14{
 15  if (std::abs (point_a.intensity - point_b.intensity) < 5.0f)
 16    return (true);
 17  else
 18    return (false);
 19}
 20
 21bool
 22enforceNormalOrIntensitySimilarity (const PointTypeFull& point_a, const PointTypeFull& point_b, float /*squared_distance*/)
 23{
 24  Eigen::Map<const Eigen::Vector3f> point_a_normal = point_a.getNormalVector3fMap (), point_b_normal = point_b.getNormalVector3fMap ();
 25  if (std::abs (point_a.intensity - point_b.intensity) < 5.0f)
 26    return (true);
 27  if (std::abs (point_a_normal.dot (point_b_normal)) > std::cos (30.0f / 180.0f * static_cast<float> (M_PI)))
 28    return (true);
 29  return (false);
 30}
 31
 32bool
 33customRegionGrowing (const PointTypeFull& point_a, const PointTypeFull& point_b, float squared_distance)
 34{
 35  Eigen::Map<const Eigen::Vector3f> point_a_normal = point_a.getNormalVector3fMap (), point_b_normal = point_b.getNormalVector3fMap ();
 36  if (squared_distance < 10000)
 37  {
 38    if (std::abs (point_a.intensity - point_b.intensity) < 8.0f)
 39      return (true);
 40    if (std::abs (point_a_normal.dot (point_b_normal)) > std::cos (30.0f / 180.0f * static_cast<float> (M_PI)))
 41      return (true);
 42  }
 43  else
 44  {
 45    if (std::abs (point_a.intensity - point_b.intensity) < 3.0f)
 46      return (true);
 47  }
 48  return (false);
 49}
 50
 51int
 52main ()
 53{
 54  // Data containers used
 55  pcl::PointCloud<PointTypeIO>::Ptr cloud_in (new pcl::PointCloud<PointTypeIO>), cloud_out (new pcl::PointCloud<PointTypeIO>);
 56  pcl::PointCloud<PointTypeFull>::Ptr cloud_with_normals (new pcl::PointCloud<PointTypeFull>);
 57  pcl::IndicesClustersPtr clusters (new pcl::IndicesClusters), small_clusters (new pcl::IndicesClusters), large_clusters (new pcl::IndicesClusters);
 58  pcl::search::KdTree<PointTypeIO>::Ptr search_tree (new pcl::search::KdTree<PointTypeIO>);
 59  pcl::console::TicToc tt;
 60
 61  // Load the input point cloud
 62  std::cerr << "Loading...\n", tt.tic ();
 63  pcl::io::loadPCDFile ("Statues_4.pcd", *cloud_in);
 64  std::cerr << ">> Done: " << tt.toc () << " ms, " << cloud_in->size () << " points\n";
 65
 66  // Downsample the cloud using a Voxel Grid class
 67  std::cerr << "Downsampling...\n", tt.tic ();
 68  pcl::VoxelGrid<PointTypeIO> vg;
 69  vg.setInputCloud (cloud_in);
 70  vg.setLeafSize (80.0, 80.0, 80.0);
 71  vg.setDownsampleAllData (true);
 72  vg.filter (*cloud_out);
 73  std::cerr << ">> Done: " << tt.toc () << " ms, " << cloud_out->size () << " points\n";
 74
 75  // Set up a Normal Estimation class and merge data in cloud_with_normals
 76  std::cerr << "Computing normals...\n", tt.tic ();
 77  pcl::copyPointCloud (*cloud_out, *cloud_with_normals);
 78  pcl::NormalEstimation<PointTypeIO, PointTypeFull> ne;
 79  ne.setInputCloud (cloud_out);
 80  ne.setSearchMethod (search_tree);
 81  ne.setRadiusSearch (300.0);
 82  ne.compute (*cloud_with_normals);
 83  std::cerr << ">> Done: " << tt.toc () << " ms\n";
 84
 85  // Set up a Conditional Euclidean Clustering class
 86  std::cerr << "Segmenting to clusters...\n", tt.tic ();
 87  pcl::ConditionalEuclideanClustering<PointTypeFull> cec (true);
 88  cec.setInputCloud (cloud_with_normals);
 89  cec.setConditionFunction (&customRegionGrowing);
 90  cec.setClusterTolerance (500.0);
 91  cec.setMinClusterSize (cloud_with_normals->size () / 1000);
 92  cec.setMaxClusterSize (cloud_with_normals->size () / 5);
 93  cec.segment (*clusters);
 94  cec.getRemovedClusters (small_clusters, large_clusters);
 95  std::cerr << ">> Done: " << tt.toc () << " ms\n";
 96
 97  // Using the intensity channel for lazy visualization of the output
 98  for (const auto& small_cluster : (*small_clusters))
 99    for (const auto& j : small_cluster.indices)
100      (*cloud_out)[j].intensity = -2.0;
101  for (const auto& large_cluster : (*large_clusters))
102    for (const auto& j : large_cluster.indices)
103      (*cloud_out)[j].intensity = +10.0;
104  for (const auto& cluster : (*clusters))
105  {
106    int label = rand () % 8;
107    for (const auto& j : cluster.indices)
108      (*cloud_out)[j].intensity = label;
109  }
110
111  // Save the output point cloud
112  std::cerr << "Saving...\n", tt.tic ();
113  pcl::io::savePCDFile ("output.pcd", *cloud_out);
114  std::cerr << ">> Done: " << tt.toc () << " ms\n";
115
116  return (0);
117}

解释

由于 Conditional Euclidean Clustering 类适用于更高级的用户,因此我将跳过对代码中更明显的部分的解释:

  • pcl::io::loadPCDFile,用于加载和保存点云数据。pcl::io::savePCDFile

  • pcl::console::TicToc用于轻松输出计时结果。

  • 使用 VoxelGrid 过滤器对 PointCloud 进行下采样(第 66-73 行)来对云进行下采样并提供更均衡的点密度。

  • 在 PointCloud 中估计表面法线(第 75-83 行)用于估计将附加到点信息的法线; 条件欧几里得聚类类将使用 模板化,其中包含要在条件函数中使用的 x、y、z、强度、法线和曲率信息。pcl::PointXYZINormal

第 85-95 行设置了要使用的 Conditional Euclidean Clustering 类:

  // 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->size () / 1000);
  cec.setMaxClusterSize (cloud_with_normals->size () / 5);
  cec.segment (*clusters);
  cec.getRemovedClusters (small_clusters, large_clusters);
  std::cerr << ">> Done: " << tt.toc () << " ms\n";

对不同代码行的更详细描述:

  • 该类使用 TRUE 进行初始化。 这将允许提取太小或太大的集群。 如果在没有 this 的情况下初始化类,它可以节省一些计算时间和内存。

  • 可以使用从类派生的方法指定类的输入数据,即: 和 .PCLBasesetInputCloudsetIndices

  • 随着集群的增长,它将评估集群内已有的点与附近的候选点之间的用户定义条件。 有关 condition 函数的更多信息可以在下面进一步阅读。

  • 拓扑容差是 k-NN 搜索的半径,用于查找候选点。

  • 占云总点数 0.1% 的集群被认为太小。

  • 占云总点数 20% 以上的集群被视为太大。

  • 生成的集群以索引数组的格式存储,该格式是索引数组,即输入点云的索引点。pcl::IndicesClusters

  • 太小或太大的集群不会传递到主输出,而是可以在单独的数据容器中检索,但前提是类是使用 TRUE 初始化的。pcl::IndicesClusters

第 12-49 行显示了条件函数的一些示例:

bool
enforceIntensitySimilarity (const PointTypeFull& point_a, const PointTypeFull& point_b, float /*squared_distance*/)
{
  if (std::abs (point_a.intensity - point_b.intensity) < 5.0f)
    return (true);
  else
    return (false);
}

bool
enforceNormalOrIntensitySimilarity (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 (std::abs (point_a.intensity - point_b.intensity) < 5.0f)
    return (true);
  if (std::abs (point_a_normal.dot (point_b_normal)) > std::cos (30.0f / 180.0f * static_cast<float> (M_PI)))
    return (true);
  return (false);
}

bool
customRegionGrowing (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 (std::abs (point_a.intensity - point_b.intensity) < 8.0f)
      return (true);
    if (std::abs (point_a_normal.dot (point_b_normal)) > std::cos (30.0f / 180.0f * static_cast<float> (M_PI)))
      return (true);
  }
  else
  {
    if (std::abs (point_a.intensity - point_b.intensity) < 3.0f)
      return (true);
  }
  return (false);
}

条件函数的格式是固定的:

  • 前两个输入参数的类型必须与 Conditional Euclidean Clustering 类中使用的模板类型相同。 这些参数将传递当前种子点(第一个参数)和当前候选点(第二个参数)的点信息。

  • 第三个 input 参数需要是 float。 此参数将传递种子点和候选点之间的平方距离。 虽然这个信息也可以使用前两个参数进行计算,但它已经由底层的最近邻搜索提供,并且可以很容易地用于制作一个与距离相关的条件函数。

  • output 参数需要是布尔值。 返回 TRUE 会将候选点合并到种子点的集群中。 返回 FALSE 不会通过此特定点对合并候选点,但是,这两个点仍有可能通过不同的点对关系最终位于同一集群中。

这些示例条件函数只是为了指示如何使用它们。 例如,只要集群在表面法线方向上相似或强度值相似,第二个条件函数就会增加集群。 这应该希望将纹理相似的建筑物群集为一个群集,但不会将它们与相邻对象合并到同一群集中。 这将计算出强度是否与附近的对象足够不同,并且附近的对象没有共享具有相同法线的附近表面。 第三个条件函数与第二个条件函数类似,但根据点之间的距离具有不同的约束。

第 97-109 行包含一段代码,该代码是一个快速而肮脏的修复,用于可视化结果:

  // Using the intensity channel for lazy visualization of the output
  for (const auto& small_cluster : (*small_clusters))
    for (const auto& j : small_cluster.indices)
      (*cloud_out)[j].intensity = -2.0;
  for (const auto& large_cluster : (*large_clusters))
    for (const auto& j : large_cluster.indices)
      (*cloud_out)[j].intensity = +10.0;
  for (const auto& cluster : (*clusters))
  {
    int label = rand () % 8;
    for (const auto& j : cluster.indices)
      (*cloud_out)[j].intensity = label;
  }

当使用 PCL 的标准 PCD 查看器打开输出点云时,按“5”将切换到强度通道可视化。 太小的群集将变为红色,太大的群集将变为蓝色,实际感兴趣的群集/对象将在黄色和青色之间随机着色。

编译和运行程序

将以下行添加到您的CMakeLists.txt

 1cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
 2
 3project(conditional_euclidean_clustering)
 4
 5find_package(PCL 1.7 REQUIRED)
 6
 7include_directories(${PCL_INCLUDE_DIRS})
 8link_directories(${PCL_LIBRARY_DIRS})
 9add_definitions(${PCL_DEFINITIONS})
10
11add_executable (conditional_euclidean_clustering conditional_euclidean_clustering.cpp)
12target_link_libraries (conditional_euclidean_clustering ${PCL_LIBRARIES})

创建可执行文件后,您可以运行它。只需:

$ ./conditional_euclidean_clustering

生成的输出点云可以按如下方式打开:

$ ./pcl_viewer output.pcd

您应该会看到类似于以下内容的内容:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2175776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

以太网交换安全:端口隔离

一、端口隔离 以太交换网络中为了实现报文之间的二层广播域的隔离&#xff0c;用户通常将不同的端口加人不同的 VLAN大型网络中&#xff0c;业务需求种类繁多&#xff0c;只通过 VLAN实现报文的二层隔离&#xff0c;会浪费有限的VLAN资源。而采用端口隔离功能&#xff0c;则可…

【Verilog学习日常】—牛客网刷题—Verilog企业真题—VL66

超前进位加法器 描述 题目描述&#xff1a; 求两个四位的数据编写一个四位的超前进位加法器&#xff0c;建议使用子模块 提示&#xff1a;超前进位加法器的位公式如下 这里‘’ ‘’符号不是‘加’和‘乘’&#xff0c;是‘或’和 ‘与’ 波形示意图&#xff1a; 输入…

履带机器人运动模型分析

履带机器人运动学模型分析 滑动转向 履带机器人单侧履带可等效视为“无穷多个小轮子”&#xff0c;且这单侧的“无穷多个小轮子”的“转速”是一致的。所以&#xff0c;履带式机器人的转向方式和四轮驱动机器人(SSMR)的是一致的&#xff0c;也是滑动转向。 因此&#xff0c;下…

供应链 | 顶刊POMS论文精读:交易成本经济学(TCE)——供应链效率理论

编者按 供应链效率提升指南&#xff1a;不可不知的TCE理论视角 本文为Production and Operations Management 期刊论文&#xff0c;原文信息&#xff1a; Ketokivi, M., & Mahoney, J. T. (2020). Transaction cost economics as a theory of supply chain efficiency. …

CentOS 替换 yum源 经验分享

视频教程在bilibili:CentOS 替换 yum源 经验分享_哔哩哔哩_bilibili问题原因 解决方法 1. 进入镜像目录 [rootlocalhost ~]# cd /etc/yum.repos.d/ 2.备份文件 [rootlocalhost yum.repos.d]# rename repo bak * 3.寻找阿里镜像源复制 https://developer.aliyun.com/mirror/ …

文件操作Tips,一次搞懂Python中open与with open的差异

前几天在群里看到&#xff0c;有小伙伴在问&#xff0c;为什么代码中打开文件有的时候用open&#xff0c;有的时候用with open&#xff0c;一会close&#xff0c;一会又没有close&#xff0c;真让人摸不着头脑。我们来看一看&#xff0c;这究竟是怎么回事。 在Python中&#xf…

前端测试最强教程 - 实现 fake http 和 fake db

因为想要写出完美的测试&#xff0c;我们就需要模拟各种接口状态&#xff0c;各种动态数据&#xff0c;要求再高点甚至需要模拟鉴权&#xff0c;那这就不是简单的覆盖方法可以实现的。 所以我们需要一些第三方依赖&#xff0c;下面介绍一下实现步骤。 实现步骤 step 1: 新增…

JSP 的 request 内置对象

文章目录 前言一、request 内置对象是什么&#xff1f;二、request 对象 1.访问请求参数2.在作用域中管理属性3.获取 cookie4.解决中文乱码5.获取客户端信息6.显示国际化信息总结 前言 我们在学习 JavaScript 的时候有了解过常用对象&#xff0c;这些对象可以在这门脚本语言中全…

java项目之作业管理系统设计与实现源码(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的作业管理系统设计与实现源码。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 作业管理系统…

怎么选择一款适合自己的蓝牙耳机?2024开放式耳机选购指南

在选择开放式耳机时&#xff0c;可以从以下几个要点着手&#xff1a; 佩戴舒适程度&#xff1a; 耳挂的设计&#xff1a;好的耳挂设计能让耳机稳稳地戴在耳朵上&#xff0c;且不会给耳部造成过多压迫。比如有些耳机配备了可调节的耳挂&#xff0c;能依照不同人的耳型和佩戴习…

WordPress 要求插件开发人员进行双因素身份验证

全球超过40%的网站由 WordPress 提供支持&#xff0c;其庞大的插件和主题生态系统在全球范围内提供了灵活性和定制性。然而&#xff0c;这种受欢迎程度也使其成为网络攻击的主要目标。 WordPress 将为所有插件和主题开发人员引入强制性双因素身份验证 (2FA)&#xff0c;以应对…

Vue3动态导入后端路由

动态获取文件 // modules就是一个map&#xff0c;文件路径作为key&#xff0c;文件对象作为value const modulesimport.meta.glob(../views/**/*.vue)获取到的modules对象如下&#xff1a; 添加动态路由 import {createRouter, createWebHashHistory} from vue-routerconst m…

Vue项目开发注意事项

事项一&#xff1a;项目代码放在本地怎么运行起来 1、首先确定项目对应的node和npm版本 node下载地址 Index of /dist/https://nodejs.org/dist/ node 与 npm版本对应关系 Node.js — Node.js Releases 2、node卸载的时候&#xff0c;会自动把对应的npm卸载掉 情况1&…

【源码】询比价管理系统,招投标采购管理系统

前言&#xff1a; 随着互联网和数字技术的不断发展&#xff0c;企业采购管理逐渐走向数字化和智能化。数字化采购平台作为企业采购管理的新模式&#xff0c;能够提高采购效率、降低采购成本、优化供应商合作效率&#xff0c;已成为企业实现效益提升的关键手段。源码获取本文末…

vue3 实现文本内容超过N行折叠并显示“...展开”组件

1. 实现效果 组件内文字样式取决与外侧定义 组件大小发生变化时,文本仍可以省略到指定行数 文本不超过时, 无展开,收起按钮 传入文本发生改变后, 组件展示新的文本 2. 代码 文件名TextEllipsis.vue <template><div ref"compRef" class"wq-text-ellip…

新疆交投路桥桥梁公司:向“新”求“质”,积蓄发展新势能

向“新”求“质”谋发展&#xff0c;“发展新质生产力是推动高质量发展的内在要求和重要着力点&#xff0c;必须继续做好创新这篇大文章&#xff0c;推动新质生产力加快发展”。以新质生产力塑造国企新的核心竞争力成为实现高质量发展的关键内容&#xff0c;既具有重大理论意义…

在传销案件中数据库取证的分步指南

金字塔计划的特点是分层结构&#xff0c;主要由招募新成员的机制驱动。取证部门调查这些方案时&#xff0c;往往依靠数据库记录来分析这种结构。这些记录详细描述了上级和下级之间的关系&#xff0c;使调查人员能够描绘出组织的动态。在本文中&#xff0c;我们将探讨如何利用数…

如何用好通义灵码企业知识库问答能力?

通义灵码企业版&#xff1a;通义灵码企业标准版快速入门_智能编码助手_AI编程_智能编码助手通义灵码(Lingma)-阿里云帮助中心 通义灵码提供了基于企业知识库的问答检索增强的能力&#xff0c;在开发者使用通义灵码 IDE 插件时&#xff0c;可以结合企业知识库内上传的文档、文件…

23款奔驰E300立标升级23P智能辅助驾驶案例分享

《23 款奔驰 E300 立标升级 23P 智能辅助驾驶案例》 在汽车科技不断进步的今天&#xff0c;越来越多的车主开始追求更加智能、安全的驾驶体验。今天&#xff0c;我们就为大家带来一款 23 款奔驰 E300 立标升级 23P 智能辅助驾驶的精彩案例。 这辆 23 款奔驰 E300 立标原本就散…

业务调度 -- 线路单板中继模式

OTN网络中&#xff0c;线路板可以设置为中继模式&#xff0c;作为中继提高系统传输距离。当前设备支持“双向中继”与“单向中继”两种模式&#xff0c;不同的线路板支持其中的一种模式。单向中继与双向中继模式下支持“光中继”或“电中继”。 单向中继 VS. 双向中继 单向中…