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 ())
{
}
当然,实际效果可能没有这么好,如果有这么多平面需要分割我建议先手工把它减少一点,这个算法对单独检测一两个特定规格的平面还是很准的,但是这么多的真心不建议。
上一篇: 树状数组进阶(区间修改+单点查询)