读自动驾驶激光雷达物体检测技术(Lidar Obstacle Detection)(4):Clustering(欧式聚类)
在第(3)节中完成了地面点与障碍物的有效分离。这一部分的主要目标是实施聚类操作。而聚类则是将不同物体的点云分别组合、聚集起来的过程。通过这种方式使得你可以追踪汽车、行人以及其他多个目标。其中一种对点云数据进行分组和聚类的方法称为欧氏聚类。
欧氏聚类通过聚集具有较高相似度的点云实现数据分组。为了提高最近邻搜索效率, 建议采用KD-Tree数据结构进行组织。该数据结构通常能将查询时间从O(n)优化至O(log n)水平。其优势在于能够更有效地划分搜索空间区域。通过在KD-Tree中合理分组空间区域, 我们能够显著减少计算大量远端点距离的需求。
作者罗列了两种欧式聚类的方法。第一种方法是自行实现了欧式聚类算法与k-dtree结合使用的版本。第二种方法则是直接调用了PCL库模块中的欧式聚类实现。具体记录在下文部分以便于学习者参考。
第一种:Manual Euclidean clustering
除了上述之外,我们还可以运用KD-Tree来进行欧氏聚类。
在此基础上,我们首先阐述了KD-Tree的工作原理。
它是由一种二叉树结构构成的数据组织形式,在处理多维数据时会根据各个维度的数据值依次进行比较,并通过划分区域来实现对空间的有效划分。
例如,在处理具有三维坐标的复杂数据时,
这种方法能够在较短的时间内完成最近邻搜索任务。
为了便于理解,在二维坐标系中
我们可以先构建一个KD-Tree,
并详细讲解其在整个欧氏聚类过程中的具体实现步骤,
最终我们将探讨如何将这一方法扩展至三维情形。
在KD-Tree中进行点的插入操作(这是将点云数据输入至树结构中完成KD-Tree的构建)
第二种:PCL Euclidean clustering
基于PCL平台提供的欧氏聚类功能模块进行点云分割处理。为了获得更详细的处理流程和参数设置信息,请参考PCL官方文档中的详细说明。
欧氏聚类对象ec遵循一定的容差值,在此范围内任意接近的对象将被归为同一簇。此外该算法还提供了min和max参数来指定集群中的最小和最大点数量。如果一个集群很小则可能是噪声数据;通过min参数可以限制这种小集群的使用。而max参数允许我们更好地分解非常大的集群如果一个集群非常大则可能是许多其他簇重叠的结果此时最大容差设置有助于更有效地分割这些区域以提高目标检测精度。最后该算法的一个重要参数是Kd-Tree数据结构它用于加速聚类过程这些Kd-Tree结构都是基于原始点云数据构建而成其中原始点云数据是通过过滤和平分操作从初始扫描结果中提取障碍物区域的关键特征。
聚类结果可视化显示:




#include <iostream>
#include <pcl/io/io.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/common/common.h>
#include <unordered_set>
using namespace std;
using namespace pcl;
int main()
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PCDReader reader;
reader.read("F:\ SFND_Lidar_Obstacle_Detection\ SFND_Lidar_Obstacle_Detection\ data\ pcd\ pcd\ out_plane.pcd", *cloud);
pcl::PointCloud<pcl::PointXYZ>::Ptr obstCloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PCDReader reader2;
reader2.read("F:\ SFND_Lidar_Obstacle_Detection\ SFND_Lidar_Obstacle_Detection\ data\ pcd\ pcd\ obstCloud.pcd", *obstCloud);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
//tree->setInputCloud(cloud);
tree->setInputCloud(obstCloud);
std::vector<pcl::PointCloud<pcl::PointXYZ>> clusters;//保存分割后的所有类 每一类为一个点云( //创建点云索引向量,用于存储实际的点云信息)
// 欧式聚类对检测到的障碍物进行分组
float clusterTolerance = 0.5;
int minsize = 10;
int maxsize = 140;
std::vector<pcl::PointIndices> clusterIndices;// 创建索引类型对象
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec; // 欧式聚类对象
ec.setClusterTolerance(clusterTolerance);//设置近邻搜索半径
ec.setMinClusterSize(minsize);//设置一个类需要的最小的点数
ec.setMaxClusterSize(maxsize);//设置一个类需要的最大的点数
ec.setSearchMethod(tree);//设置搜索方法
//ec.setInputCloud(cloud); // feed point cloud
ec.setInputCloud(obstCloud);//输入的点云不同,聚类的结果不同
ec.extract(clusterIndices); // 得到所有类别的索引 clusterIndices 11类
// 将得到的所有类的索引分别在点云中找到,即每一个索引形成了一个类
for (pcl::PointIndices getIndices : clusterIndices)
{
pcl::PointCloud<pcl::PointXYZ> cloudCluster;
//PtCdtr<PointT> cloudCluster(new pcl::PointCloud<PointT>);
// For each point indice in each cluster
for (int index : getIndices.indices)
{
cloudCluster.push_back(cloud->points[index]);
//cloudCluster.push_back(obstCloud->points[index]);
}
cloudCluster.width = cloudCluster.size();
cloudCluster.height = 1;
cloudCluster.is_dense = true;
clusters.push_back(cloudCluster);
}
/*为了从点云索引向量中分割出每个聚类,必须迭代访问点云索引,每次创建一个新的点云数据集,并且将所有当前聚类的点写入到点云数据集中。*/
//迭代访问点云索引clusterIndices,直到分割出所有聚类
int j = 0;
for (std::vector<pcl::PointIndices>::const_iterator it = clusterIndices.begin(); it != clusterIndices.end(); ++it)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster(new pcl::PointCloud<pcl::PointXYZ>);
//创建新的点云数据集cloud_cluster,将所有当前聚类写入到点云数据集中
for (std::vector<int>::const_iterator pit = it->indices.begin(); pit != it->indices.end(); ++pit)
//cloud_cluster->points.push_back(cloud->points[*pit]); //查找的是定义的cloud里边对应的点云
cloud_cluster->points.push_back(obstCloud->points[*pit]); //查找的是定义的obstCloud里边对应的点云
cloud_cluster->width = cloud_cluster->points.size();
cloud_cluster->height = 1;
cloud_cluster->is_dense = true;
std::cout << "PointCloud representing the Cluster: " << cloud_cluster->points.size() << " data points." << std::endl;
std::stringstream ss;
ss << "F:\ SFND_Lidar_Obstacle_Detection\ SFND_Lidar_Obstacle_Detection\ data\ pcd\ pcd\ cluster\ " << j << ".pcd";
pcl::PCDWriter writer;//保存提取的点云文件
writer.write<pcl::PointXYZ>(ss.str(), *cloud_cluster, false); //*
j++;
}
int clusterId = 0;
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
//viewer->addPointCloud<pcl::PointXYZ>(obstCloud, "obstCLoud" );
for (pcl::PointCloud<pcl::PointXYZ> cluster : clusters)
{
//viewer->addPointCloud<pcl::PointXYZ>(cloud, "obstCLoud" + std::to_string(clusterId));//cloud可视化显示的是平面的聚类
viewer->addPointCloud<pcl::PointXYZ>(obstCloud, "obstCLoud" + std::to_string(clusterId));//obstCloud可视化显示的是车的聚类
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "obstCLoud" + std::to_string(clusterId));
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 0, 1, "obstCLoud" + std::to_string(clusterId));
++clusterId;
}
viewer->resetCamera(); //相机点重置
viewer->spin();
cout << clusters.size() << endl;
system("pause");
return 0;
}
AI助手
给聚类结果画框:

结果展示:
#include <iostream>
#include <pcl/io/io.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/common/common.h>
#include <unordered_set>
using namespace std;
using namespace pcl;
struct Box
{
float x_min;
float y_min;
float z_min;
float x_max;
float y_max;
float z_max;
};
int main()
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PCDReader reader;
reader.read("D:\ SFND_Lidar_Obstacle_Detection\ SFND_Lidar_Obstacle_Detection代码分解\ out_plane.pcd", *cloud);
pcl::PointCloud<pcl::PointXYZ>::Ptr all_cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PCDReader reader2;
reader2.read("D:\ SFND_Lidar_Obstacle_Detection\ SFND_Lidar_Obstacle_Detection代码分解\ cloudRegion.pcd", *all_cloud);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(cloud);
std::vector<pcl::PointCloud<pcl::PointXYZ>> clusters;//保存分割后的所有类 每一类为一个点云
// 欧式聚类对检测到的障碍物进行分组
float clusterTolerance = 0.5;
int minsize = 10;
int maxsize = 140;
std::vector<pcl::PointIndices> clusterIndices;// 创建索引类型对象
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec; // 欧式聚类对象
ec.setClusterTolerance(clusterTolerance);//设置近邻搜索半径
ec.setMinClusterSize(minsize);//设置一个类需要的最小的点数
ec.setMaxClusterSize(maxsize);//设置一个类需要的最大的点数
ec.setSearchMethod(tree);//设置搜索方法
ec.setInputCloud(cloud); // feed point cloud
ec.extract(clusterIndices); // 得到所有类别的索引 clusterIndices 11类
// 将得到的所有类的索引分别在点云中找到,即每一个索引形成了一个类
for (pcl::PointIndices getIndices : clusterIndices)
{
pcl::PointCloud<pcl::PointXYZ> cloudCluster;
//PtCdtr<PointT> cloudCluster(new pcl::PointCloud<PointT>);
// For each point indice in each cluster
for (int index : getIndices.indices)
{
cloudCluster.push_back(cloud->points[index]);
}
cloudCluster.width = cloudCluster.size();
cloudCluster.height = 1;
cloudCluster.is_dense = true;
clusters.push_back(cloudCluster);
}
int clusterId = 0;
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
//viewer->addPointCloud<pcl::PointXYZ>(all_cloud, "obstCLoud" ); //整个点云
for (pcl::PointCloud<pcl::PointXYZ> cluster : clusters)
{
viewer->addPointCloud<pcl::PointXYZ>(cloud, "obstCLoud" + std::to_string(clusterId));
viewer->setPointCloudRenderingProperties( pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "obstCLoud" + std::to_string(clusterId) );
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 0, 1, "obstCLoud" + std::to_string(clusterId));
pcl::PointXYZ minPoint, maxPoint;
pcl::getMinMax3D(cluster, minPoint, maxPoint);//想得到它x,y,z三个轴上的最大值和最小值
Box box;
box.x_min = minPoint.x;
box.y_min = minPoint.y;
box.z_min = minPoint.z;
box.x_max = maxPoint.x;
box.y_max = maxPoint.y;
box.z_max = maxPoint.z;
std::string cube = "box" + std::to_string(clusterId);
viewer->addCube(box.x_min, box.x_max, box.y_min, box.y_max, box.z_min, box.z_max, 1, 0, 0, cube);
viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_REPRESENTATION, pcl::visualization::PCL_VISUALIZER_REPRESENTATION_WIREFRAME, cube); //只显示线框
++clusterId;
}
viewer->resetCamera(); //相机点重置
viewer->spin();
cout << clusters.size() << endl;
system("pause");
return 0;
}
AI助手
