无人驾驶传感器融合系列(二)——激光雷达点云的聚类原理及实现

  • Post author:
  • Post category:其他




无人驾驶传感器融合系列(二)——激光雷达点云的聚类原理及实现

本章摘要:在上一章,我们采用RANSAC算法分割出了地面点云,非地面点云。我们通常会对非地面点云进行进一步的分割,也就是对地面以上的障碍物的点云进行聚类,通过聚类,我们可以检测出障碍物的边缘,然后使用3维的Bounding Box将障碍物从三维点云中框出来。本章将讲解Euclidean 聚类算法、PCL实现,并对其所利用的基本的数据结构kd-tree进行讲解。



Euclidean 聚类效果展示:

欧几里德聚类(Euclidean Clustering),本质上是找到最近点,只要点与点之间的距离,在一定半径范围内就认为这些点属于一个聚类。那么上一章中,非地面点云聚类之后的效果如下:

在这里插入图片描述



通过代码来直观理解Euclidean聚类算法实现过程:



伪代码如下:

clusthelp(point,cluster):
    mark point as processed
    add point to cluster
    nearby points = tree(point)
    Iterate through each nearby point
        If point has not been processed
            clusthelp(cluster)

EuclideanCluster():
    list of clusters 
    Iterate through each point
        If point has not been processed
            Create cluster
            clusthelp(point, cluster)
            cluster add clusters
    return clusters



对应的C++代码实现如下:

//参数里面的 tree为kd-tree,里面存储了点云points.
std::vector<std::vector<int>> euclideanCluster(const std::vector<std::vector<float>>& points, KdTree* tree, float distanceTol)
{
	// TODO: Fill out this function to return list of indices for each cluster
	std::vector<std::vector<int>> clusters;
	std::vector<bool> processed (points.size(), false);

	for(int i=0; i< points.size(); i++)
	{
		if(processed[i]) continue;
		std::vector<int> cluster;
		clusthelp(i, points, tree, distanceTol, processed, cluster);
		clusters.push_back(cluster);
	}
	return clusters;
void clusthelp(int indice, const std::vector<std::vector<float>>& points, KdTree* tree, float distanceTol, std::vector<bool> &processed, std::vector<int> &cluster)
{	
	processed[indice] = true;
	cluster.push_back(indice);

	std::vector<int> rawclust = tree->search(points[indice],distanceTol); //利用kd-tree来搜索points[indice]点一定范围内(distanceTol)的点云。
	for(int k : rawclust)
	{
		if(!processed[k]) 
			clusthelp(k, points, tree, distanceTol, processed, cluster);
	}
}



Euclidean 聚类PCL实现:

template<typename PointT>
std::vector<typename pcl::PointCloud<PointT>::Ptr> ProcessPointClouds<PointT>::Clustering(typename pcl::PointCloud<PointT>::Ptr cloud, float clusterTolerance, int minSize, int maxSize)
{

    // Time clustering process
    auto startTime = std::chrono::steady_clock::now();

    std::vector<typename pcl::PointCloud<PointT>::Ptr> clusters;

    // TODO:: Fill in the function to perform euclidean clustering to group detected obstacles
    // Creating the KdTree object for the search method of the extraction
    typename pcl::search::KdTree<PointT>::Ptr tree (new pcl::search::KdTree<PointT>);
    tree->setInputCloud (cloud);

    std::vector<pcl::PointIndices> cluster_indices;
    pcl::EuclideanClusterExtraction<PointT> ec;
    ec.setClusterTolerance (clusterTolerance);   //设置聚类点与点之间的距离阈值。
    ec.setMinClusterSize (minSize);              //设置聚类点最少数目,排除噪音点的影响。
    ec.setMaxClusterSize (maxSize);              //设置聚类点最大数目。只有最小与最大数目之间的聚类才能够返回。
    ec.setSearchMethod (tree);                   //通过kd-tree的方式搜索。
    ec.setInputCloud (cloud);
    ec.extract (cluster_indices);

    for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin (); it != cluster_indices.end (); ++it)
    {
        typename pcl::PointCloud<PointT>::Ptr cloud_cluster (new pcl::PointCloud<PointT>);
        for (std::vector<int>::const_iterator pit = it->indices.begin (); pit != it->indices.end (); ++pit)
            cloud_cluster->points.push_back (cloud->points[*pit]); 

        cloud_cluster->width = cloud_cluster->points.size ();
        cloud_cluster->height = 1;
        cloud_cluster->is_dense = true;

        clusters.push_back(cloud_cluster);
    }

    auto endTime = std::chrono::steady_clock::now();
    auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
    std::cout << "clustering took " << elapsedTime.count() << " milliseconds and found " << clusters.size() << " clusters" << std::endl;

    return clusters;
}



kd-tree讲解

上面聚类算法中,用到了数据结构kd-tree,采用kd-tree主要是为了加快数据的搜索查找速度。关于kd-tree的

基础原理讲解见这里。

这里主要通过c++代码来直观的理解kd-tree如何实现二维数据的

插入、搜索



数据的插入

void inserthelper(Node** node, uint depth, std::vector<float> point, int id)
	{
		if(*node==NULL)
			*node = new Node(point, id);
		else
		{
			uint cd = depth % 2;
			if(point[cd] < ((*node)->point[cd]))
				inserthelper(&((*node)->left), depth+1, point, id);
			else
				inserthelper(&((*node)->right), depth+1, point, id);	
		}		
	}

	void insert(std::vector<float> point, int id)
	{
		inserthelper(&root, 0, point, id);
	}
struct Node
{
	std::vector<float> point;
	int id;
	Node* left;
	Node* right;

	Node(std::vector<float> arr, int setId)
	:	point(arr), id(setId), left(NULL), right(NULL)
	{}
};



搜索目标点一点范围内的所有点。

从树根开始搜索,看点是否在目标点distanceTol所占的范围内,如果在范围内就将其加入到ids,如果不在则舍弃一半,然后接着往下搜索,采用同样的方式迭代,知道搜索完所有的点。

在这里插入图片描述

void searchhelp(std::vector<float> target, Node* node, int depth, float distanceTol, std::vector<int> &ids)
	{
		if(node!=NULL)
		{
			if( (node->point[0]>=(target[0]-distanceTol)) && (node->point[0]<=(target[0]+distanceTol)) \
				&& (node->point[1]>=(target[1]-distanceTol)) && (node->point[1]<=(target[1]+distanceTol)))
				{
					float dist = sqrt(pow((node->point[0]-target[0]),2) + pow((node->point[1]-target[1]),2));
					if(dist<=distanceTol)
						ids.push_back(node->id);
				}
			if((target[depth%2]-distanceTol) < node->point[depth%2])
				searchhelp(target, node->left, depth+1, distanceTol, ids);
			if((target[depth%2]+distanceTol) > node->point[depth%2])
				searchhelp(target, node->right, depth+1, distanceTol, ids);
		}
	}
	// return a list of point ids in the tree that are within distance of target
	std::vector<int> search(std::vector<float> target, float distanceTol)
	{
		std::vector<int> ids;
		searchhelp(target, root, 0, distanceTol, ids);
		return ids;
	}



给聚类添加Bounding Box框

对每一聚类,确定一个能够将点云簇包裹的三维Bounding Box,将其视为一个物体处理。这里采用,聚类点云中所有点的最小x, y, z值,最大x, y, z值来确定Bounding Box的外框边界。

在这里插入图片描述

template<typename PointT>
Box ProcessPointClouds<PointT>::BoundingBox(typename pcl::PointCloud<PointT>::Ptr cluster)
{
    // Find bounding box for one of the clusters
    PointT minPoint, maxPoint;
    pcl::getMinMax3D(*cluster, minPoint, maxPoint);

    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;

    return box;
}
struct Box
{
	float x_min;
	float y_min;
	float z_min;
	float x_max;
	float y_max;
	float z_max;
};

输出BoundingBox,在

environment.cpp/ simpleHighway

下做如下设置。

Box box = pointProcessor->BoundingBox(cluster);
renderBox(viewer,box,clusterId);



完整代码见:

github

显示不同的输出结果见

sensors/ environment.cpp/ simpleHighway

, 可以根据注释自己调整输出。



版权声明:本文为weixin_40215443原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。