原文链接:Euclidean Cluster Extraction
目录
理论基础
程序代码
代码解析
使用Kd-tree作为抽取算法的搜索方法
创建EuclideanClusterExtraction对象,并设置参数
实验结果
点云处理全过程
滤波
平面分割并去除
聚类抽取
打印结果
CMakeLists.txt
在本篇教程中,我们将学习使用pcl::EuclideanClusterExtraction类提取欧几里德聚类。为了使本篇教程更加精简,之前教程中提到的知识点将不再重复说明,比如平面模型分割,可参考之前的博文:PCL教程-点云分割之平面模型分割
数据集下载链接: table_scene_lms400.pcd
聚类方法,通过特征空间确定点与点之间的亲疏程度。
一个聚类方法需要将一个无序的点云模型P分割成更小的部分,使得处理P的整体时间显著减少。一种简单的欧氏聚类方法可以通过使用固定宽度的框对空间进行三维网格细分来实现,或者更普遍地说,使用八叉树数据结构。
这种特殊的表示非常快速,对于需要占用空间的体积表示,或者对每个结果3D盒子(或八叉树叶子)中的数据可以近似为不同的结构的情况非常有用。
然而,在更一般的意义上,我们可以利用最近邻并实现一种本质上类似于漫水填充算法的聚类技术。
假设我们使用的点云数据的场景为:一张桌子,以及桌子上放置的一些东西。我们要找出并分割平面上的单个目标点云族:
假设我们使用Kd-tree结构去查找最近邻,算法步骤如下:
算法流程简述:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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::Ptr cloud(new pcl::PointCloud), cloud_f(new pcl::PointCloud);
reader.read("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 vg;
pcl::PointCloud::Ptr cloud_filtered(new pcl::PointCloud);
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 seg;
pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
pcl::PointCloud::Ptr cloud_plane(new pcl::PointCloud());
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 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);
// 创建KdTreee对象作为搜索方法
pcl::search::KdTree::Ptr tree(new pcl::search::KdTree);
tree->setInputCloud(cloud_filtered);
std::vector cluster_indices;
pcl::EuclideanClusterExtraction 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::const_iterator it = cluster_indices.begin(); it != cluster_indices.end(); ++it)
{
//创建临时保存点云族的点云
pcl::PointCloud::Ptr cloud_cluster(new pcl::PointCloud);
//通过下标,逐个填充
for (std::vector::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 << "当前聚类 "<points.size() << " data points." << std::endl;
std::stringstream ss;
ss << "cloud_cluster_" << j << ".pcd";
writer.write(ss.str(), *cloud_cluster, false); //*
j++;
//显示,随机设置不同颜色,以区分不同的聚类
pcl::visualization::PointCloudColorHandlerCustom 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);
}
// Creating the KdTree object for the search method of the extraction
pcl::search::KdTree::Ptr tree (new pcl::search::KdTree);
tree->setInputCloud (cloud_filtered);
pcl::EuclideanClusterExtraction ec;
ec.setClusterTolerance (0.02); // 2cm
ec.setMinClusterSize (100);
ec.setMaxClusterSize (25000);
ec.setSearchMethod (tree);
ec.setInputCloud (cloud_filtered);
ec.extract (cluster_indices);
左图为原始点云数据,右图为滤波后的点云:
将点云中所有平面都分割出来,并剔除:
使用Kd-tree的结构,对处理后的点云进行聚类抽取,右图为抽取之后的结果,不同颜色代表不同聚类点云:
PointCloud before filtering has: 460400 data points.
PointCloud after filtering has: 41049 data points.
PointCloud representing the planar component: 20536 data points.
PointCloud representing the planar component: 12442 data points.
当前聚类 0 包含的点云数量: 4857 data points.
当前聚类 1 包含的点云数量: 1386 data points.
当前聚类 2 包含的点云数量: 321 data points.
当前聚类 3 包含的点云数量: 291 data points.
当前聚类 4 包含的点云数量: 123 data points.
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project(cluster_extraction)
find_package(PCL 1.2 REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
add_executable (cluster_extraction cluster_extraction.cpp)
target_link_libraries (cluster_extraction ${PCL_LIBRARIES})