本教程学习使用pcl::EuclideanClusterExtraction类进行欧几里得聚类提取。
欧几里得聚类原理:
聚类方法需要将一个无组织的点云模型p划分为更小的部分,从而大大减少了p的整体处理时间。
一种简单的欧几里德意义上的数据聚类方法可以通过使用固定宽度的盒子或一般的八叉树数据结构来实现。
然而,在更一般的意义上,我们可以利用最近邻并实现本质上类似于洪水填充算法的聚类技术。
假设有一个点云,有一张桌子和上面的物体。我们希望找到并分割位于平面上的单个物体点簇。假设我们使用 Kd 树结构来寻找最近的邻居,那么算法步骤将是:
1、为输入点云数据集p创建一个 Kd 树表示;
2、建立一个空的集群列表c,以及一个需要检查q的点的队列;
3、然后,对于p中的每一点,执行以下步骤:
4、当p中的所有点被处理完后并且都已经属于点集群c的一部分,算法停止。
源码:
创建 cluster_extraction.cpp 文件
- 1#include <pcl/ModelCoefficients.h>
- 2#include <pcl/point_types.h>
- 3#include <pcl/io/pcd_io.h>
- 4#include <pcl/filters/extract_indices.h>
- 5#include <pcl/filters/voxel_grid.h>
- 6#include <pcl/features/normal_3d.h>
- 7#include <pcl/search/kdtree.h>
- 8#include <pcl/sample_consensus/method_types.h>
- 9#include <pcl/sample_consensus/model_types.h>
- 10#include <pcl/segmentation/sac_segmentation.h>
- 11#include <pcl/segmentation/extract_clusters.h>
- 12
- 13
- 14int
- 15main ()
- 16{
- 17 // Read in the cloud data
- 18 pcl::PCDReader reader;
- 19 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>);
- 20 reader.read ("table_scene_lms400.pcd", *cloud);
- 21 std::cout << "PointCloud before filtering has: " << cloud->size () << " data points." << std::endl; //*
- 22
- 23 // Create the filtering object: downsample the dataset using a leaf size of 1cm
- 24 pcl::VoxelGrid<pcl::PointXYZ> vg;
- 25 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);
- 26 vg.setInputCloud (cloud);
- 27 vg.setLeafSize (0.01f, 0.01f, 0.01f);
- 28 vg.filter (*cloud_filtered);
- 29 std::cout << "PointCloud after filtering has: " << cloud_filtered->size () << " data points." << std::endl; //*
- 30
- 31 // Create the segmentation object for the planar model and set all the parameters
- 32 pcl::SACSegmentation<pcl::PointXYZ> seg;
- 33 pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
- 34 pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
- 35 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_plane (new pcl::PointCloud<pcl::PointXYZ> ());
- 36 pcl::PCDWriter writer;
- 37 seg.setOptimizeCoefficients (true);
- 38 seg.setModelType (pcl::SACMODEL_PLANE);
- 39 seg.setMethodType (pcl::SAC_RANSAC);
- 40 seg.setMaxIterations (100);
- 41 seg.setDistanceThreshold (0.02);
- 42
- 43 int nr_points = (int) cloud_filtered->size ();
- 44 while (cloud_filtered->size () > 0.3 * nr_points)
- 45 {
- 46 // Segment the largest planar component from the remaining cloud
- 47 seg.setInputCloud (cloud_filtered);
- 48 seg.segment (*inliers, *coefficients);
- 49 if (inliers->indices.size () == 0)
- 50 {
- 51 std::cout << "Could not estimate a planar model for the given dataset." << std::endl;
- 52 break;
- 53 }
- 54
- 55 // Extract the planar inliers from the input cloud
- 56 pcl::ExtractIndices<pcl::PointXYZ> extract;
- 57 extract.setInputCloud (cloud_filtered);
- 58 extract.setIndices (inliers);
- 59 extract.setNegative (false);
- 60
- 61 // Get the points associated with the planar surface
- 62 extract.filter (*cloud_plane);
- 63 std::cout << "PointCloud representing the planar component: " << cloud_plane->size () << " data points." << std::endl;
- 64
- 65 // Remove the planar inliers, extract the rest
- 66 extract.setNegative (true);
- 67 extract.filter (*cloud_f);
- 68 *cloud_filtered = *cloud_f;
- 69 }
- 70
- 71 // Creating the KdTree object for the search method of the extraction
- 72 pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>);
- 73 tree->setInputCloud (cloud_filtered);
- 74
- 75 std::vector<pcl::PointIndices> cluster_indices;
- 76 pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
- 77 ec.setClusterTolerance (0.02); // 2cm
- 78 ec.setMinClusterSize (100);
- 79 ec.setMaxClusterSize (25000);
- 80 ec.setSearchMethod (tree);
- 81 ec.setInputCloud (cloud_filtered);
- 82 ec.extract (cluster_indices);
- 83
- 84 int j = 0;
- 85 for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin (); it != cluster_indices.end (); ++it)
- 86 {
- 87 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster (new pcl::PointCloud<pcl::PointXYZ>);
- 88 for (const auto& idx : it->indices)
- 89 cloud_cluster->push_back ((*cloud_filtered)[idx]); //*
- 90 cloud_cluster->width = cloud_cluster->size ();
- 91 cloud_cluster->height = 1;
- 92 cloud_cluster->is_dense = true;
- 93
- 94 std::cout << "PointCloud representing the Cluster: " << cloud_cluster->size () << " data points." << std::endl;
- 95 std::stringstream ss;
- 96 ss << "cloud_cluster_" << j << ".pcd";
- 97 writer.write<pcl::PointXYZ> (ss.str (), *cloud_cluster, false); //*
- 98 j++;
- 99 }
- 100
- 101 return (0);
- 102}
说明:
1、相关头文件
- 1#include <pcl/ModelCoefficients.h>
- 2#include <pcl/point_types.h>
- 3#include <pcl/io/pcd_io.h>
- 4#include <pcl/filters/extract_indices.h>
- 5#include <pcl/filters/voxel_grid.h>
- 6#include <pcl/features/normal_3d.h>
- 7#include <pcl/search/kdtree.h>
- 8#include <pcl/sample_consensus/method_types.h>
- 9#include <pcl/sample_consensus/model_types.h>
- 10#include <pcl/segmentation/sac_segmentation.h>
- 11#include <pcl/segmentation/extract_clusters.h>
2、创建读取对象,读取点云数据,输出点云信息
- 17 // Read in the cloud data
- 18 pcl::PCDReader reader;
- 19 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>);
- 20 reader.read ("table_scene_lms400.pcd", *cloud);
- 21 std::cout << "PointCloud before filtering has: " << cloud->size () << " data points." << std::endl; //*
3、创建体素滤波对象,对点云数据进行下采样
- 23 // Create the filtering object: downsample the dataset using a leaf size of 1cm
- 24 pcl::VoxelGrid<pcl::PointXYZ> vg;
- 25 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);
- 26 vg.setInputCloud (cloud);
- 27 vg.setLeafSize (0.01f, 0.01f, 0.01f);
- 28 vg.filter (*cloud_filtered);
- 29 std::cout << "PointCloud after filtering has: " << cloud_filtered->size () << " data points." << std::endl; //*
4、为平面模型创建分割对象并设置所有参数
- 31 // Create the segmentation object for the planar model and set all the parameters
- 32 pcl::SACSegmentation<pcl::PointXYZ> seg;
- 33 pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
- 34 pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
- 35 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_plane (new pcl::PointCloud<pcl::PointXYZ> ());
- 36 pcl::PCDWriter writer;
- 37 seg.setOptimizeCoefficients (true);
- 38 seg.setModelType (pcl::SACMODEL_PLANE);
- 39 seg.setMethodType (pcl::SAC_RANSAC);
- 40 seg.setMaxIterations (100);
- 41 seg.setDistanceThreshold (0.02);
5、获取平面分割点云
- 43 int nr_points = (int) cloud_filtered->size ();
- 44 while (cloud_filtered->size () > 0.3 * nr_points)
- 45 {
- 46 // Segment the largest planar component from the remaining cloud
- 47 seg.setInputCloud (cloud_filtered);
- 48 seg.segment (*inliers, *coefficients);
- 49 if (inliers->indices.size () == 0)
- 50 {
- 51 std::cout << "Could not estimate a planar model for the given dataset." << std::endl;
- 52 break;
- 53 }
- 54
- 55 // Extract the planar inliers from the input cloud
- 56 pcl::ExtractIndices<pcl::PointXYZ> extract;
- 57 extract.setInputCloud (cloud_filtered);
- 58 extract.setIndices (inliers);
- 59 extract.setNegative (false);
- 60
- 61 // Get the points associated with the planar surface
- 62 extract.filter (*cloud_plane);
- 63 std::cout << "PointCloud representing the planar component: " << cloud_plane->size () << " data points." << std::endl;
- 64
- 65 // Remove the planar inliers, extract the rest
- 66 extract.setNegative (true);
- 67 extract.filter (*cloud_f);
- 68 *cloud_filtered = *cloud_f;
- 69 }
6、在这里,我们为提取算法的搜索方法创建了一个 KdTree 对象。
- 71 // Creating the KdTree object for the search method of the extraction
- 72 pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>);
- 73 tree->setInputCloud (cloud_filtered);
7、在这里,我们正在创建一个PointIndices 数组,其中包含vector<int>中的实际索引信息。每个检测到的集群的索引都保存在这里 - 请注意cluster_indices是一个数组,其中包含每个检测到的集群的一个 PointIndices 实例。所以 cluster_indices[0]包含我们点云中第一个簇的所有索引。
std::vector<pcl::PointIndices> cluster_indices;
8、因为我们的点云属于 PointXYZ 类型,我们创建了一个点类型为 PointXYZ 的 EuclideanClusterExtraction 对象。我们还设置了提取的参数和变量。小心为setClusterTolerance()设置正确的值。如果您采用非常小的值,则可能会将实际对象视为多个集群。另一方面,如果您将值设置得太高,则可能会发生多个对象 被视为一个集群的情况。因此,我们的建议是测试并尝试哪个值适合您的数据集。
我们要求找到的集群必须至少有setMinClusterSize()点和最大setMaxClusterSize()点。
- 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);
9、现在,我们从点云中提取聚类,并将索引保存在 cluster _ index 中。为了将每个聚类从vector< PointIndices > 中分离出来,我们必须遍历 cluster _ index,为每个条目创建一个新的 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;
编译并且运行:
1、将以下行添加到 CMakeLists.txt
- 1cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
- 2
- 3project(cluster_extraction)
- 4
- 5find_package(PCL 1.2 REQUIRED)
- 6
- 7include_directories(${PCL_INCLUDE_DIRS})
- 8link_directories(${PCL_LIBRARY_DIRS})
- 9add_definitions(${PCL_DEFINITIONS})
- 10
- 11add_executable (cluster_extraction cluster_extraction.cpp)
- 12target_link_libraries (cluster_extraction ${PCL_LIBRARIES})
2、终端运行
$ ./cluster_extraction
3、输出
- PointCloud before filtering has: 460400 data points.
- PointCloud after filtering has: 41049 data points.
- [SACSegmentation::initSACModel] Using a model of type: SACMODEL_PLANE
- [SACSegmentation::initSAC] Using a method of type: SAC_RANSAC with a model threshold of 0.020000
- [SACSegmentation::initSAC] Setting the maximum number of iterations to 100
- PointCloud representing the planar component: 20522 data points.
- [SACSegmentation::initSACModel] Using a model of type: SACMODEL_PLANE
- [SACSegmentation::initSAC] Using a method of type: SAC_RANSAC with a model threshold of 0.020000
- [SACSegmentation::initSAC] Setting the maximum number of iterations to 100
- PointCloud representing the planar component: 12429 data points.
- PointCloud representing the Cluster: 4883 data points.
- PointCloud representing the Cluster: 1386 data points.
- PointCloud representing the Cluster: 320 data points.
- PointCloud representing the Cluster: 290 data points.
- PointCloud representing the Cluster: 120 data points.