欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

pcl小知识(四)——区域生长分割原理(region growing segmentation)

程序员文章站 2022-07-13 21:28:42
...

转自: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 ())
  {
  }

pcl小知识(四)——区域生长分割原理(region growing segmentation)

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

相关标签: PCL