【转】PCL中的区域生长分割原理(region growing segmentation)

转自:https://blog.csdn.net/aishuirenjia/article/details/80239562

https://blog.csdn.net/u011021773/article/details/78941196/

对官网代码做了一点修改

一:源代码与初步解释

在本博文中,我主要介绍如何在pcl::RegionGrowing类中调用区域增长算法。首先注意一点,这里是region growing segmentation,不是color-based region growing segmentation.


算法核心:该算法是基于点法线之间角度的比较,企图将满足平滑约束的相邻点合并在一起,以一簇点集的形式输出。每簇点集被认为是属于相同平面。


工作原理:首先需要明白,区域增长是从有最小曲率值(curvature value)的点开始的。因此,我们必须计算出所有曲率值,并对它们进行排序。这是因为曲率最小的点位于平坦区域,而从最平坦的区域增长可以减少区域的总数。现在我们来具体描述这个过程:

1.点云中有未标记点,按照点的曲率值对点进行排序,找到最小曲率值点,并把它添加到种子点集;

2.对于每个种子点,算法都会发现周边的所有近邻点。1)计算每个近邻点与当前种子点的法线角度差(reg.setSmoothnessThreshold),如果差值小于设置的阈值,则该近邻点被重点考虑,进行第二步测试;2)该近邻点通过了法线角度差检验,如果它的曲率小于我们设定的阈值(reg.setCurvatureThreshold),这个点就被添加到种子点集,即属于当前平面。

3.通过两次检验的点,被从原始点云去除。

4.设置最小点簇的点数min(reg.setMinClusterSize),最大点簇为max(reg.setMaxClusterSize)。

4.重复1-3步,算法会生成点数在min和max的所有平面,并对不同平面标记不同颜色加以区分。

5.直到算法在剩余点中生成的点簇不能满足min,算法停止工作。


算法具体的伪码表示:http://pointclouds.org/documentation/tutorials/region_growing_segmentation.php#region-growing-segmentation


我自己对官网代码做了一点修改:

#include <iostream>
#include <vector>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/search/search.h>
#include <pcl/search/kdtree.h>
#include <pcl/features/normal_3d.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/filters/passthrough.h>
#include <pcl/segmentation/region_growing.h>

int
main(int argc, char** argv)
{
	DWORD t1, t2;
	t1 = GetTickCount();//以上两句和最后return 0 之上的为计时函数。
	
	//点云的类型
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
	//打开点云
	if (pcl::io::loadPCDFile <pcl::PointXYZ>("resultFloor局部.pcd", *cloud) == -1)//改成想要输入的点云名称...*cloud就是把输入的点云记录到变量指针cloud中。
	{
		std::cout << "Cloud reading failed." << std::endl;
		return (-1);
	}
	//设置搜索的方式或者说是结构
	pcl::search::Search<pcl::PointXYZ>::Ptr tree = boost::shared_ptr<pcl::search::Search<pcl::PointXYZ> >(new pcl::search::KdTree<pcl::PointXYZ>);
	//求法线
	pcl::PointCloud <pcl::Normal>::Ptr normals(new pcl::PointCloud <pcl::Normal>);
	pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normal_estimator;
	normal_estimator.setSearchMethod(tree);
	normal_estimator.setInputCloud(cloud);
	normal_estimator.setKSearch(50);
	normal_estimator.compute(*normals);
	//直通滤波在Z轴的0到1米之间
	pcl::IndicesPtr indices(new std::vector <int>);
	pcl::PassThrough<pcl::PointXYZ> pass;
	pass.setInputCloud(cloud);
	pass.setFilterFieldName("z");
	pass.setFilterLimits(0.0, 1.0);
	pass.filter(*indices);
	//聚类对象<点,法线>
	pcl::RegionGrowing<pcl::PointXYZ, pcl::Normal> reg;
	reg.setMinClusterSize(50);  //最小的聚类的点数
	reg.setMaxClusterSize(1000000);  //最大的
	reg.setSearchMethod(tree);    //搜索方式
	reg.setNumberOfNeighbours(30);    //设置搜索的邻域点的个数
	reg.setInputCloud(cloud);         //输入点
	//reg.setIndices (indices);
	reg.setInputNormals(normals);     //输入的法线
	reg.setSmoothnessThreshold(3.0 / 180.0 * M_PI);  //设置平滑度
	reg.setCurvatureThreshold(1.0);     //设置曲率的阈值

	std::vector <pcl::PointIndices> clusters;
	reg.extract(clusters);

	std::cout << "Number of clusters is equal to " << clusters.size() << std::endl;
	std::cout << "First cluster has " << clusters[0].indices.size() << " points." << endl;
	std::cout << "These are the indices of the points of the initial" <<
		std::endl << "cloud that belong to the first cluster:" << std::endl;

	//int counter = 0;
	//while (counter < clusters[0].indices.size())
	//{
	//	std::cout << clusters[0].indices[counter] << ", ";
	//	counter++;
	//	if (counter % 10 == 0)
	//		std::cout << std::endl;
	//}
	//std::cout << std::endl;

	//可视化聚类的结果
	pcl::PointCloud <pcl::PointXYZRGB>::Ptr colored_cloud = reg.getColoredCloud();
	//pcl::visualization::CloudViewer viewer("Cluster viewer");
	//viewer.showCloud(colored_cloud);
	//while (!viewer.wasStopped())
	//{
	//}
	pcl::PCDWriter writer;//将点云写入磁盘
	writer.write("resultFloor局部_Cluster_viewer.pcd", *colored_cloud, false);//改成想要输出的点云名称

	t2 = GetTickCount();  //从这句到return 0之间的两句为计时函数
	printf("Use Time:%f\n", (t2 - t1)*1.0 / 1000);
	return (0);
}

二:详解

因为这个算法是根据法线来计算的,所以我先算出了法线,在

pcl::RegionGrowing<pcl::PointXYZ, pcl::Normal> reg;
以上的代码是求法线的部分,那么我们从这行开始往下讲,就都是区域增长的内容了。

首先还是先建立了一个区域增长的对象reg

然后设置平面包含的最少点数(这个参数非常重要,小于这个参数的平面会被忽略不计)

然后设置最大的点数,原理同上,但是一般我们希望的是无穷大,所以可以设大一点,当然如果你有特殊要求可以按自己需求来

然后设置搜索方法,之前我们声明了使用kd树的方法,所以直接用就可以了,这也是默认的方法。

然后设置参考的邻域点数,也就是看看周边的多少个点来决定这是一个平面(这个参数至关重要,决定了你的容错率,如果设置的很大,那么从全局角度看某一个点稍微有点歪也可以接受,如果设置的很小则通常检测到的平面都会很小)

然后输入要检测的点云cloud

然后输入点云的法线(计算法线的方法参考之前的博客,这里给个传送门 http://blog.csdn.net/u011021773/article/details/78247657)

然后设置判断的阈值,大概也就是两个法线在多大的夹角内还可以当做是共面的。

最后也是一个弯曲的阈值,这个决定了比当前考察的点和平均的法线角度,决定是否还有继续探索下去的必要。(也就是假设每个点都是平稳弯曲的,那么normal的夹角都很小,但是时间长了偏移的就大了,这个参数就是限制这个用的)


然后就可以把结果输出到一个簇里面,这个簇会自动把每个平面分成一个vector,可以打印下来看看

std::cout << "Number of clusters is equal to " << clusters.size () << std::endl;
std::cout << "First cluster has " << clusters[0].indices.size () << " points." << endl;
std::cout << "These are the indices of the points of the initial" <<
std::endl << "cloud that belong to the first cluster:" << std::endl;

也可以把检测到的每个平面涂上不同的颜色

pcl::PointCloud <pcl::PointXYZRGB>::Ptr colored_cloud = reg.getColoredCloud ();
  pcl::visualization::CloudViewer viewer ("Cluster viewer");
  viewer.showCloud(colored_cloud);
  while (!viewer.wasStopped ())
  {
  }

当然,实际效果可能没有这么好,如果有这么多平面需要分割我建议先手工把它减少一点,这个算法对单独检测一两个特定规格的平面还是很准的,但是这么多的真心不建议。

猜你喜欢

转载自blog.csdn.net/liukunrs/article/details/80482788