PCL源码剖析 -- 欧式聚类

这篇具有很好参考价值的文章主要介绍了PCL源码剖析 -- 欧式聚类。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

PCL源码剖析 – 欧式聚类

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


可视化结果

欧式聚类,PCL源码剖析,算法,人工智能,3d,聚类

一. 理论

聚类方法需要将无组织的点云模型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 下载
应用完整工程地址
分析:应用整体流程经历了下采样,使用平面模型分割桌面,过滤多个平面;对剩余点云进行欧式聚类,欧式聚类结果可视化。文章来源地址https://www.toymoban.com/news/detail-734971.html

#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);
}

到了这里,关于PCL源码剖析 -- 欧式聚类的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 层次聚类算法在人工智能医疗中的应用

    作者:禅与计算机程序设计艺术 引言 1.1. 背景介绍 随着人工智能技术的快速发展,医疗领域也逐渐迎来了信息化的春天。医疗数据的丰富性和复杂性为人工智能提供了大量的应用场景。层次聚类算法作为数据挖掘领域的一种经典算法,在医疗领域有着广泛的应用前景。本文

    2024年02月08日
    浏览(36)
  • 人工智能技术基础系列之:聚类分析

    作者:禅与计算机程序设计艺术 聚类分析(Cluster analysis)是指将相似的数据对象集合划分成若干个集群或者子集的过程,其目的在于发现数据对象的共同特征和规律性,并且对数据的分类进行有效管理、数据结构的优化、数据模型的建立、数据挖掘的应用等。聚类的应用是

    2024年02月08日
    浏览(36)
  • 人工智能 - A*算法解决迷宫问题 附源码和可视化显示

    写在最前,先附上可视化后的效果: 迷宫问题可以表述为:一个二维的网格,0 表示点可走,1 表示点 不可以走,点用(x,y)表示,寻找从某一个给定的起始单元格出发, 经由行相邻或列相邻的单元格(可以通过的),最终可以到达目标单元 格的、所走过的单元格序列。在任一

    2024年02月02日
    浏览(41)
  • 【人工智能Ⅰ】实验7:K-means聚类实验

    实验7 K-means聚类实验 一、实验目的 学习K-means算法基本原理,实现Iris数据聚类。 二、实验内容 应用K-means算法对iris数据集进行聚类。 三、实验结果及分析 0 :输出数据集的基本信息 参考代码在main函数中首先打印了数据、特征名字、目标值、目标值的名字,iris数据集的结果

    2024年02月05日
    浏览(42)
  • 人工智能时代:从基础理论到应用场景,全面剖析人工智能时代的核心内容和发展方向

    作者:禅与计算机程序设计艺术 人工智能(Artificial Intelligence,AI)是一个自20世纪50年代末到60年代初由计算机科学家与哲学家一起提出的领域,它涵盖了多种子领域,如计算理论、机器学习、模式识别、智能控制等。人工智能有三个主要目标:智能推理(Artificial General Int

    2024年02月07日
    浏览(53)
  • AI 对齐:深入剖析人工智能伦理和技术标准

    在当今快速发展的人工智能时代,AI 对齐(Artificial Intelligence Alignment)逐渐成为了研究和讨论的热点话题。AI 对齐是指确保人工智能系统的行为与人类价值观和期望保持一致,并能够在面对未明确指令时作出符合人类伦理和利益的决策。此话题不仅关乎技术本身,更触及伦理

    2024年02月21日
    浏览(45)
  • 未来10年,美国47%的工作岗位将被人工智能取代?深入剖析ChatGPT

    送走最后一车货,最后三个工人,老王吃力的关上大铁门。左手从口袋摸出一盒烟,轻轻一颠,滑落一根,右手娴熟的夹住。掏出打火机,点燃,一丝青烟腾起,萦绕在指头。 夕阳穿过玻璃,照射在伤痕累累的旧机器上,空旷而寂静。面对空无一人的厂房,老王想起父亲带他

    2023年04月18日
    浏览(34)
  • 大盘点!汇总点云分割算法,涉及RANSAC、欧式聚类、区域增长等

    作者:PCIPG-zzl | 来源:计算机视觉工坊 添加微信:dddvisiona,备注:3D点云,拉你入群。文末附行业细分群。 点云分割的目标是将点云数据中的点分成不同的组或类别,使每个组中的点都属于同一种物体或区域。根据空间,几何和纹理等特征对点云进行划分,使同一划分内的

    2024年02月04日
    浏览(31)
  • 【人工智能】— 无监督学习、K-means聚类(K-means clustering)、K-means损失函数,目标函数

    无监督学习是指在没有标签的数据上进行学习,即没有监督信号的指导下进行模型训练。在无监督学习中,我们主要关注从无标签数据中学习出数据的低维结构和隐藏的模式。 通过无标签数据,我们可以预测以下内容: 低维结构:通过无监督学习算法如主成分分析(PCA),

    2024年02月10日
    浏览(32)
  • 从 人工智能学派 视角来看 人工智能算法

    当今人工智能的算法纷繁复杂:神经网络、卷积神经网络CNN、遗传算法、进化策略、知识图谱、贝叶斯网络、支持向量机SVM、强化学习、生成对抗网络GAN,自编码器… 如果你把每个算法独立看待简直是眼花缭乱,头都是大的。这次我就带你理理这些算法,有些算法其实是可以

    2024年03月15日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包