一、简介
区域生长分割算法的输出是一个聚类集合,每个聚类集合被认为是同一光滑表面的一部分。该算法思想:首先依据点的曲率值对点进行排序,之所以排序,是因为区域生长算法是从曲率最小的点开始生长的,这个点就是初始种子点,初始种子点所在的区域即为最平滑的区域,一般场景中平面区域较大,这样从最平滑的区域开始生长可减少分割区域的总数,提高效率。
算法的流程:设置一空的种子点序列和空的聚类数组,选好初始种子点后,将其加入到种子点序列中,并搜索邻域点。对每一个邻域点,比较邻域点的法线与当前种子点的法线之间的夹角,小于平滑阈值的邻域点加入到当前区域。然后检查每一个邻域点的曲率值,小于曲率阈值的邻域点加入到种子点序列中。在进行种子点邻域判断后,删除当前种子点,利用新加入的种子点继续生长,重负进行以上生长过程,知道种子点序列被清空。一个区域生长完成,将其加入聚类数组。最后,利用曲率值从小到大排序,顺序选择输入点集的点作为种子点加入到种子点序列中,重复以上生长步骤,这样就通过区域生长实现了点云的分割。
二、代码分析
1)利用pcl::NormalEstimation类,计算法线:
pcl::NormalEstimation normal_estimator;//创建法线估计对象
normal_estimator.setSearchMethod (tree);//设置搜索方法
normal_estimator.setInputCloud (cloud);//设置法线估计对象输入点集
normal_estimator.setKSearch (KN_normal);// 设置用于法向量估计的k近邻数目
normal_estimator.compute (*normals);//计算并输出法向量
2)对类pcl::RegionGrowing进行实例化,该模板类具有两个参数:PointT是所用点云的类型,NormalT是所用法线的类型:
pcl::RegionGrowing reg;//创建区域生长分割对象
3)设置最大和最小聚类大小,这样分割后点的数量小于最小聚类值(或大于最大聚类值)的都将被舍弃:
reg.setMinClusterSize (50);//设置一个聚类需要的最小点数
reg.setMaxClusterSize (1000000);//设置一个聚类需要的最大点数
4)该方法需要用K近邻搜索遍历近邻,一下几行提供搜索方法和设置近邻大小阈值,并输入点云,点云索引和点云对应的法向量:
reg.setSearchMethod (tree);//设置搜索方法
reg.setNumberOfNeighbours (30);//设置搜索的临近点数目
reg.setInputCloud (cloud);//设置输入点云
if(Bool_Cuting)reg.setIndices (indices);//通过输入参数设置,确定是否输入点云索引
reg.setInputNormals (normals);//设置输入点云的法向量
5)setSmoothnessThreshold成员函数用于设置平滑阈值,也就是两点法线偏差的允许范围。如果两点法线间的偏差小于算法设置的平滑阈值,那么这两点被认为是同一个聚类;setCurvatureThreshold成员函数用于设置曲率阈值,即两点曲率偏差的允许范围,如果两点的法向量偏差较小,那么将继续检测它们曲率之间的差异,如果这个值小于曲率阈值,那么算法会采用新增加的点迭代进行区域生长:
reg.setSmoothnessThreshold (SmoothnessThreshold / 180.0 * M_PI);//设置平滑阈值
reg.setCurvatureThreshold (CurvatureThreshold);//设置曲率阈值
6)启动分割算法,返回聚类向量:
std::vector clusters;
reg.extract (clusters);//获取聚类的结果,分割结果保存在点云索引的向量中。
end = time(0);
diff[2] = difftime (end, start)-diff[0]-diff[1];
PCL_INFO ("\Region growing takes(seconds): %d\n", diff[2]);
std::cout << "Number of clusters is equal to " << clusters.size () << std::endl;//输出聚类的数量
std::cout << "First cluster has " << clusters[0].indices.size () << " points." << endl;//输出第一个聚类的数量
7)整体代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void PrintMemoryInfo() //输出内存中的报错信息
{
HANDLE hProcess;
PROCESS_MEMORY_COUNTERS pmc;
hProcess = GetCurrentProcess();
printf("\nProcess ID: %u\n", hProcess);
if (NULL == hProcess)
return;
if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc)))
{
printf("\t PageFaultCount: 0x%08X\n", pmc.PageFaultCount);
printf("\t PeakWorkingSetSize: 0x%08X\n", pmc.PeakWorkingSetSize);
printf("\t WorkingSetSize: 0x%08X\n", pmc.WorkingSetSize);
printf("\t QuotaPeakPagedPoolUsage: 0x%08X\n", pmc.QuotaPeakPagedPoolUsage);
printf("\t QuotaPagedPoolUsage: 0x%08X\n", pmc.QuotaPagedPoolUsage);
printf("\t QuotaPeakNonPagedPoolUsage: 0x%08X\n", pmc.QuotaPeakNonPagedPoolUsage);
printf("\t QuotaNonPagedPoolUsge: 0x%08X\n", pmc.QuotaNonPagedPoolUsage);
printf("\t PagefileUsge: 0x%08X\n", pmc.PagefileUsage);
printf("\t peakPagefileUsage: 0x%08X\n", pmc.PeakPagefileUsage);
}
CloseHandle(hProcess);
}
using namespace pcl::console;
int main(int argc, char** argv)
{
if (argc < 2)
{
std::cerr << ".exe xx.pcd -kn 50 -bc 0 -fc 10.0 -nc 0 -st 30 -ct 0.05" << std::endl; //输出参考信息
return (0);
}
time_t start, end, diff[5], option; //用于保存运行的相应时间等
start = time(0); //记录当前的时间
int KN_normal = 50;
bool Bool_cutting = false;
float far_cutting = 10, near_cutting = 0, SmoothnessThreshold = 30.0, CurvatureThreshold = 0.05;
parse_argument(argc, argv, "-kn", KN_normal); //设置k近邻点的个数
parse_argument(argc, argv, "-bc", Bool_cutting); //设置是否进行直通滤波的标志位
parse_argument(argc, argv, "-fc", far_cutting); //设置直通滤波的上限
parse_argument(argc, argv, "-nc", near_cutting); //设置直通滤波的下限
parse_argument(argc, argv, "-st", SmoothnessThreshold); //设置平滑阈值
parse_argument(argc, argv, "-ct", CurvatureThreshold); //设置曲率阈值
pcl::PointCloud::Ptr cloud(new pcl::PointCloud); //设置输入点云的对象,用于加载点云数据
if (pcl::io::loadPCDFile(argv[1], *cloud) == -1)
{
std::cout << "Cloud reading failed." << std::endl;
return (-1);
}
end = time(0); //记录加载完点云时的时间
diff[0] = difftime(end, start); //记载加载所用的时间
PCL_INFO("\Loading pcd file takes(seconds):%d\n", diff[0]);
pcl::search::Search::Ptr tree = pcl::shared_ptr< pcl::search::Search > (new pcl::search::KdTree);
pcl::PointCloud::Ptr normals(new pcl::PointCloud); //智能指针声明kd tree与法线
pcl::NormalEstimation normal_estimator; //实例化一个法线估计对象
normal_estimator.setSearchMethod(tree); //设置搜索方式
normal_estimator.setInputCloud(cloud); //设置输入点云
normal_estimator.setKSearch(KN_normal); //设置k近邻点的个数
normal_estimator.compute(*normals); //执行法线估计
end = time(0);
diff[1] = difftime(end, start) - diff[0]; //记录法线估计所用的时间
PCL_INFO("\Estimationg normal takes : %d seconds\n", diff[1]);
pcl::IndicesPtr indices(new std::vector); //声明一个索引指针
if (Bool_cutting) //判断是否需要进行直通滤波
{
pcl::PassThrough pass; //设置直通滤波器对象
pass.setInputCloud(cloud); //设置输入点云
pass.setFilterFieldName("z"); //指定进行过滤的字段
pass.setFilterLimits(near_cutting, far_cutting); //设置进行过滤的范围
pass.filter(*indices); //执行滤波,并保存滤波结果
}
pcl::RegionGrowingreg; //创建区域生长的分割对象
reg.setMinClusterSize(50); //设置聚类所需要的最小点数
reg.setMaxClusterSize(1000000); //设置一个聚类的最大点数
reg.setSearchMethod(tree); //设置搜索方式
reg.setNumberOfNeighbours(30); //设置近邻点的个数
reg.setInputCloud(cloud); //设置输入点云
if (Bool_cutting)reg.setIndices(indices); //通过输入参数设置,确定是否输入点云的索引
reg.setInputNormals(normals); //输入点云的法向量
reg.setSmoothnessThreshold(SmoothnessThreshold / 180.0 * M_PI); //设置平滑阈值
reg.setCurvatureThreshold(CurvatureThreshold); //设置曲率阈值
std::vector clusters; //用动态数组保存聚类的结果
reg.extract(clusters);
end = time(0); //计算聚类所需的时间
diff[2] = difftime(end, start) - diff[0] - diff[1];
PCL_INFO("\Region growing takes %d seconds\n", diff[2]);
std::cout << "Number of clusters is equal to " << clusters.size() << std::endl;
std::cout << "First cluster has " << clusters[0].indices.size() << " points." << std::endl;
std::cout << "These are the indices of the points of the initial" << std::endl << "cloud that belong to the first cluster:" << std::endl;
PrintMemoryInfo();
pcl::PointCloud::Ptr colored_cloud = reg.getColoredCloud();
pcl::visualization::CloudViewer viewer("区域生长分割方法");
viewer.showCloud(colored_cloud);
while (!viewer.wasStopped())
{
}
return (0);
}
三、编译结果
分割后的图像存在部分红色点,说明这部分点的数量小于或大于用户设定的阈值。基于区域生长的分割算法,对输入参数有一啦,通过修改相关输入参数,可以获得更好的分割效果: