PCL源码剖析 -- 欧式聚类

news2024/11/24 2:21:30

PCL源码剖析 – 欧式聚类

参考
1. pcl Euclidean Cluster Extraction教程
2. 欧式聚类分析
3. pcl-api源码
4. 点云欧式聚类
5. 本文完整工程地址


可视化结果

在这里插入图片描述

一. 理论

聚类方法需要将无组织的点云模型P划分为更小的部分,以便显著减少P的总体处理时间。欧式空间的简单数据聚类方法可以利用固定宽度box的3D网格划分或者一般的八叉树数据结构实现。这种特殊的表征速度快,在体素表征的占据空间。然后,更普遍使用的手段是使用最近邻来实现聚类技术,本质类似于洪水填充(flood fill)算法。

二. 伪码

1. 为点云P 创建KD-Tree的输入表征
2. 创建空的聚类列表C 和 点云的检查队列Q
3. 对于P中的每一个点Pi,执行如下操作:
4.    - 将Pi添加到当前队列Q(并标记为已处理);
5.    - while处理 Q 中的每一个Pi:
6.          - 对Pi进行近邻搜索,查找满足半径 < d 的点集合;
7.          - 检查上一步中每一个邻居点,如果标记是未被处理的点,则加入到队列Q中;
8.    - 直到Q中的所有点都被标记为已处理,将Q加入到聚类列表C中,将Q重新置为空
9. 当所有的Pi都被处理过之后结束,聚类结果为列表 C

三. 源码分析

3.1 关于主要函数extractEuclideanClusters的一些说明

  1. 功能说明
    根据最大、最小聚类点数,欧式聚类阈值,将点云进行欧式聚类。
  2. 参数说明
- cloud: 输入点云
- tree: kd-tree
- tolerance: 欧式聚类距离阈值
- clusters: 输出参数,最终聚类结果数组
- min_pts_per_cluster: 每个聚类结果最小点数
- max_pts_per_cluster: 每个聚类结果最大点数

3.2 重点函数extractEuclideanClusters的实现

已为源码添加注释

 #include <pcl/segmentation/extract_clusters.h> // 分割模块
 #include <pcl/search/organized.h> // for OrganizedNeighbor
 
 //
 template <typename PointT> void
 pcl::extractEuclideanClusters (const PointCloud<PointT> &cloud,
                                const typename search::Search<PointT>::Ptr &tree,
                                float tolerance, std::vector<PointIndices> &clusters,
                                unsigned int min_pts_per_cluster,
                                unsigned int max_pts_per_cluster)
 { 
   // 若构建的kd-tree中点数,与输入点云数据点数不同,直接错误打印,并退出
   if (tree->getInputCloud ()->size () != cloud.size ())
   {
     PCL_ERROR("[pcl::extractEuclideanClusters] Tree built for a different point cloud "
               "dataset (%zu) than the input cloud (%zu)!\n",
               static_cast<std::size_t>(tree->getInputCloud()->size()),
               static_cast<std::size_t>(cloud.size()));
     return;
   }
   // 检查kdtree是否经过排序,若结果为true,则不需要检查第一个元素;否则需要检查第一个元素,默认为0
   int nn_start_idx = tree->getSortedResults () ? 1 : 0;
   // 建立处理队列Q,数目为点数数目,每一个值为false
   std::vector<bool> processed (cloud.size (), false);
   
   // nn_indices代表某一个点knn近邻查找,满足距离阈值的点云索引
   Indices nn_indices;
   std::vector<float> nn_distances;
   // 遍历每一个点
   for (int i = 0; i < static_cast<int> (cloud.size ()); ++i)
   { 
     // 若该点云被标记为true(处理过),则跳过该点
     if (processed[i])
       continue;
     
     // 当前某一个聚类的种子队列Q
     Indices seed_queue;
     int sq_idx = 0; // 种子队列索引
     seed_queue.push_back (i); // 将当前点加入种子队列,并标记为true
     processed[i] = true; 
     
     // 循环处理种子队列Q中的每一个点,进行K近邻查找以及标记工作
     while (sq_idx < static_cast<int> (seed_queue.size ()))
     {
       // 对种子队列中索引的点进行半径距离查找,若半径内没有可查询的点,则索引+1, 跳过后续处理
       if (!tree->radiusSearch (seed_queue[sq_idx], tolerance, nn_indices, nn_distances))
       {
         sq_idx++;
         continue;
       }
       
       // 半径内有满足条件的点,点云索引保存在nn_indices。遍历这些点,判断是否处理过,若没有处理过,则加入到种子队列Q中
       for (std::size_t j = nn_start_idx; j < nn_indices.size (); ++j)             // can't assume sorted (default isn't!)
       {
         // 若该索引为0 或者标记为被处理过之后,则跳过该点
         if (nn_indices[j] == UNAVAILABLE || processed[nn_indices[j]])        // Has this point been processed before ?
           continue;
  
         // 进行简单的欧式聚类,即将其加入到种子队列中,并标记为true
         seed_queue.push_back (nn_indices[j]);
         processed[nn_indices[j]] = true;
       }
       
       // 跳到队列中下一个需要处理的种子点
       sq_idx++;
     }
  
     // 当一个聚类结果完成后,若种子队列数目在最大值与最小值之间,则需要将聚类结果加入到聚类列表中
     if (seed_queue.size () >= min_pts_per_cluster && seed_queue.size () <= max_pts_per_cluster)
     {
       pcl::PointIndices r; // PointIndices类中indices是一个vector
       r.indices.resize (seed_queue.size ());
       // 完成种子队列中索引的赋值操作
       for (std::size_t j = 0; j < seed_queue.size (); ++j)
         r.indices[j] = seed_queue[j];
  
       // 点云索引值排序和去重,源码注释中表示下面两行可以去掉,并不需要
       std::sort (r.indices.begin (), r.indices.end ());
       r.indices.erase (std::unique (r.indices.begin (), r.indices.end ()), r.indices.end ());
        
       // 头赋值和点云结果加入到聚类列表中
       r.header = cloud.header;
       clusters.push_back (r);   
     }
     else
     {
       // 打印聚类数目超限
       PCL_DEBUG("[pcl::extractEuclideanClusters] This cluster has %zu points, which is not between %u and %u points, so it is not a final cluster\n",
                 seed_queue.size (), min_pts_per_cluster, max_pts_per_cluster);
     }
   }
 }

四. 应用

测试数据table_scene_lms400.pcd 下载
应用完整工程地址
分析:应用整体流程经历了下采样,使用平面模型分割桌面,过滤多个平面;对剩余点云进行欧式聚类,欧式聚类结果可视化。

#include <pcl/ModelCoefficients.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/normal_3d.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/segmentation/extract_clusters.h>
#include<pcl/visualization/pcl_visualizer.h>

bool isPushSpace = false;

//键盘事件
void keyboard_event_occurred(const pcl::visualization::KeyboardEvent& event, void * nothing)
{
	if (event.getKeySym() == "space" && event.keyDown())
	{
		isPushSpace = true;
	}
}

int main(int argc, char** argv)
{
	// 从PCD文件中读取点云数据
	pcl::PCDReader reader;
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>), cloud_f(new pcl::PointCloud<pcl::PointXYZ>);
	reader.read("../../../data/table_scene_lms400.pcd", *cloud);
	std::cout << "PointCloud before filtering has: " << cloud->points.size() << " data points." << std::endl; //*

	pcl::visualization::PCLVisualizer viewer("Cluster Extraction");

    // 注册键盘事件
	viewer.registerKeyboardCallback(&keyboard_event_occurred, (void*)NULL);
	int v1(1);
	int v2(2);
	viewer.createViewPort(0, 0, 0.5, 1, v1);
	viewer.createViewPort(0.5, 0, 1, 1, v2);

	// 使用下采样,分辨率 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->points.size() << " data points." << std::endl; //*

	viewer.addPointCloud(cloud, "cloud1", v1);
	viewer.addPointCloud(cloud_filtered, "cloud2", v2);
	//渲染10秒再继续
	viewer.spinOnce(10000);

	// 创建平面分割对象
	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);

	// 把点云中所有的平面全部过滤掉,重复过滤,直到点云数量小于原来的0.3倍
	int i = 0, nr_points = (int)cloud_filtered->points.size();
	while (cloud_filtered->points.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);
 
		// Write the planar inliers to disk
		extract.filter(*cloud_plane);
		std::cout << "PointCloud representing the planar component: " << cloud_plane->points.size() << " data points." << std::endl;
 
		// Remove the planar inliers, extract the rest
		extract.setNegative(true);
		extract.filter(*cloud_f);
 
		//更新显示点云
		viewer.updatePointCloud(cloud_filtered, "cloud1");
		viewer.updatePointCloud(cloud_f, "cloud2");
		//渲染3秒再继续
		viewer.spinOnce(3000);
 
		cloud_filtered = cloud_f;
 
	}

	viewer.removePointCloud("cloud2", v2);

	/// 创建KdTree对象作为搜索方法
	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->points.push_back(cloud_filtered->points[*pit]); //*

		//设置点云属性
		cloud_cluster->width = cloud_cluster->points.size();
		cloud_cluster->height = 1;
		cloud_cluster->is_dense = true;

		std::cout << "当前聚类 "<<j<<" 包含的点云数量: " << 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++;

		//显示,随机设置不同颜色,以区分不同的聚类
		pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> cluster_color(cloud_cluster, rand()*100 + j * 80, rand() * 50 + j * 90, rand() * 200 + j * 100);
		viewer.addPointCloud(cloud_cluster,cluster_color, ss.str(), v2);
		viewer.spinOnce(5000);
	}
	while (!viewer.wasStopped())
	{
		viewer.spinOnce();
	}
	return (0);
}

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

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

相关文章

centos7.6部署ELK集群(三)之logstash7.7.0部署

32.5. 部署logstash7.7.0&#xff08;在主节点上操作&#xff09; 32.6.1. 下载logstash7.7.0 Logstash 官方下载地址&#xff1a;https://www.elastic.co/cn/downloads/logstash 32.6.2. 解压至安装目录 tar –xvf logstash-7.7.0.tar.gz -C /vmdata/ 32.6.3. 修改logstas…

逍遥自在学C语言 位运算符 “|“ 的5种高级用法

前言 在上一篇文章中&#xff0c;我们介绍了&运算符的高级用法&#xff0c;本篇文章&#xff0c;我们将介绍| 运算符的一些高级用法。 一、人物简介 第一位闪亮登场&#xff0c;有请今后会一直教我们C语言的老师 —— 自在。 第二位上场的是和我们一起学习的小白程序猿 —…

JAVA入坑之异常处理

目录 一、程序错误 二、异常 2.1概述 2.2产生原因 2.3Java 异常层次结构 Error Exception 2.3.1非受检异常 2.3.2受检异常 三、异常处理机制 3.1概述 ​编辑 调用栈Call Stack 3.2异常类型的实现 3.3异常处理的关键字&#xff1a; 3.3.1try-catch Block ​编辑 …

推荐系统概述

1.推荐系统的意义 随着移动互联网的飞速发展&#xff0c;人们已经处于一个信息过载的时代。在这个时代中&#xff0c;信息的生产者很难将信息呈现在对它们感兴趣的信息消费者面前&#xff0c;而对于信息消费者也很难从海量的信息中找到自己感兴趣的信息。推荐系统就是一个将信息…

git使用常见问题(提交代码,合并冲突)

文章目录Git提交代码步骤git pullgit statusgit addgit commitgit pushgit代码冲突合并问题方法一&#xff1a;放弃本地代码方法二&#xff1a;合并代码常用命令以及参数git add 将文件添加到仓库&#xff1a;git diff 比较文件异同git log 查看历史记录git reset 代码回滚版本…

Matlab-神经网络43个案例

神经网络简介 人工神经网络是在现代神经科学的基础上提出和发展起来的&#xff0c;旨在反映人脑结构及 功能的一种抽象数学模型。自 1943 年美国心理学家 W. McCulloch 和数学家 W. Pitts 提 出形式神经元的抽象数学模型—MP 模型以来&#xff0c;人工神经网络理论技术经过了 …

Java企业级开发学习笔记(2.1)MyBatis实现简单查询

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/zi0wB】 文章目录零、创建数据库与表一、基于配置文件方式使用MyBatis基本使用1.1 创建Maven项目 - MyBatisDemo1.2 在pom文件里添加相应的依赖1.3 创建与用户表对应的用户实体类 - User1.4 创建用…

数据库系统工程师——第四章 操作系统基础

文章目录&#x1f4c2; 第四章、操作系统基础 &#x1f4c1; 4.1 操作系统概述 &#x1f4d6; 4.1.1 基本概念 &#x1f4d6; 4.1.2 操作系统分类 &#x1f4d6; 4.1.3 操作系统的发展 &#x1f4c1; 4.2 进程管理 &#x1f4d6; 4.2.1 基本概念 &#x1f4d6; 4.2.2 进程的控…

电动滑板车出口欧洲CE认证EN17128标准

电动滑板车物美价廉、十分节省能源&#xff0c;充6小时电能骑20多公里&#xff0c;时速最高可达32迈&#xff0c;这对于任何年龄阶段(>12岁)的朋友来说绝对是非常适合的一种选择&#xff1b;与电动自行车相比&#xff0c;它造型美观、操作方便&#xff0c;而且因为座位重心低…

MaxHub智能电视使用123

开机和关机电视正下方&#xff0c;中央有一个圆形按钮。开机时&#xff0c;轻按此按钮1下&#xff0c;智能电视开始启动。启动后会显示MAXHUB。 如果使用中想让智能电视进入休眠状态&#xff0c;轻按此按钮1下即可。此时按钮变为红色。唤醒时&#xff0c;轻按此按钮1下&#x…

智慧物流仓储人员定位系统解决方案,提升物流仓储安全管理效率

随着近年我国制造业的崛起&#xff0c;物流业也得到了迅猛的发展&#xff0c;仓储越来越得到人们的重视。然而&#xff0c;传统仓储业存在着效率低、利用率不高、作业条件差等问题&#xff0c;以及部分仓储企业的业务模式较为基础和单一&#xff0c;导致仓储行业整体盈利水平较…

加密市场喜忧参半 DeFi与CeFi、监管之间有矛盾吗?

目前&#xff0c;身处其中的加密从业者大多喜忧参半。一方面&#xff0c;美国监管机构对中心化交易所的持续打压冲击着市场信心&#xff1b;另一方面&#xff0c;以BTC、ETH为代表的加密资产在二级市场表现相当不俗。 除价格因素外&#xff0c;加密领域内的多个信号正在显现出强…

详细介绍性能测试的方法(含文档)

性能测试是软件测试中的一个重要环节&#xff0c;其目的是评估系统在不同负荷下的性能表现&#xff0c;包括响应时间、吞吐量、并发数等指标。通常可以通过以下几种方法进行性能测试&#xff1a; 1、负载测试 负载测试是模拟多用户同时访问系统&#xff0c;测试系统在高并发、…

SaaS电子病历系统源码

SaaS电子病历系统&#xff1a;现代医疗的新趋势 SaaS电子病历系统是一种基于云计算技术的电子病历服务平台&#xff0c;它将传统的病历记录存储在云端&#xff0c;使用者可以通过互联网随时随地访问和查询病历信息。 相比于传统的医疗信息系统&#xff0c;SaaS电子病历系统具…

每日学术速递4.15

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Zip-NeRF: Anti-Aliased Grid-Based Neural Radiance Fields 标题&#xff1a;Zip-NeRF&#xff1a;基于网格的抗锯齿神经辐射场 作者&#xff1a;Xueyan Zou, Jianwei Yang, Hao …

在 Ubuntu 使用SQL Server创建 Go 应用程序

在 Ubuntu 使用SQL Server创建 Go 应用程序一、设置环境1.1、安装 SQL Server1.2、安装 GoLang1.3、安装 ODBC 驱动程序和 SQL 命令行实用工具 SQL 服务器二、使用 SQL 服务器创建 Go 应用程序2.1、创建连接到 SQL Server 并执行查询的 Go 应用2.2、创建一个使用 GORM 连接到 S…

macOS 13.4Beta 2(22F5037d)发布

系统介绍 4 月 12 日消息&#xff0c;苹果今日向 Mac 电脑用户推送了 macOS 13.4 开发者预览版 Beta 2 更新&#xff08;内部版本号&#xff1a;22F5037d&#xff09;&#xff0c;本次更新距离上次发布隔了 14 天。 macOS Ventura 带来了台前调度、连续互通相机、FaceTime 通…

Python爬虫之Scrapy框架系列(19)——实战下载某度猫咪图片【媒体管道类】

目录&#xff1a;1.引入&#xff1a;1.1 不使用管道&#xff0c;直接存储本地&#xff1a;①创建scrapy项目及爬虫文件②编写爬虫文件&#xff1a;③效果&#xff1a;1.2 使用管道&#xff0c;进行本地存储&#xff1a;①编写爬虫文件&#xff1a;②在items.py文件中创建相应的…

前缀和算法【一维、二维】

算法推导 首先这种算法适合于求从 x 到 y 的和。 一维情况 一维代码十分简单&#xff0c;我们只需要每个都记录前面所有的和即可&#xff0c;注意细节 下标从1开始 for(int i 1 ; i < n ; i ){cin >> temp;a[i] a[i - 1] temp; }这里我们就看两种情况&#xff…

接口优化的常见方案实战总结

一、背景 针对老项目&#xff0c;去年做了许多降本增效的事情&#xff0c;其中发现最多的就是接口耗时过长的问题&#xff0c;就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案。 &#xfeff; &#xfeff; &#xfeff;&#xfeff; 二、接口优化…