参考页面:https://pcl.readthedocs.io/projects/tutorials/en/master/region_growing_segmentation.html
区域增长分割(Region growing segmentation)
- 在本教程中,我们将学习如何使用在
pcl::RegionGrowing类中实现的区域增长算法。这个算法的目的是 合并在平滑度约束方面足够接近 的点。 - 因此,该算法的输出是一组簇,其中每个簇是一组 被视为同一平滑曲面的一部分 的点。该算法是基于点法线之间的角度比较。
理论基础(Theoretical Primer)
- 首先,它根据曲率值对点进行排序。这里需要这样做,是因为该区域从具有最小曲率值的点开始生长。这样做的原因是曲率最小的点位于平坦区域(从最平坦区域增长可以减少段的总数)。
- 然后我们得到了排序后的点云。直到点云中没有未标记的点为止,该算法提取曲率值最小的点并开始区域增长。此过程如下所示:
- 选取的点将添加到称为“种子”的集合中;
- 对于每一个种子点,算法会寻找它的邻接点:
- 计算每个邻接的法线与当前种子点法线之间的角度。如果角度小于阈值,则将当前点添加到当前区域;
- 之后,对每个邻居计算曲率值。如果曲率小于阈值,则该点将添加到种子。
- 当前种子将从种子集合中移除。
- 如果种子集变为空,这意味着算法已经扩大了区域,并且从一开始就重复该过程。
- 可以在下面找到上述算法的伪代码:
输 入 : 点 云 = { P } 点 法 向 量 = { N } 点 曲 率 = { c } 寻 找 邻 域 函 数 Ω ( . ) 曲 率 阈 值 c t h 角 度 阈 值 θ t h 初 始 化 : 区 域 列 表 R ← ∅ 可 获 取 点 的 列 表 { A } ← { 1 , . . . , ∣ P ∣ } 算 法 : W h i l e { A } i s n o t e m p t y d o 当 前 区 域 { R c } ← ∅ 当 前 种 子 集 合 { S c } ← ∅ { A } 中 最 小 曲 率 的 点 → P m i n { S c } ← { S c } ∪ P m i n { R c } ← { R c } ∪ P m i n { A } ← { A } ∖ P m i n f o r i = 0 t o s i z e ( { S c } ) d o 找 在 当 前 种 子 点 中 的 最 近 邻 接 点 { B c } ← Ω ( S c { i } ) f o r j = 0 t o s i z e ( { B c } ) d o 当 前 邻 接 点 P j ← B c { j } I f { A } c o n t a i n s P j a n d c o s − 1 ( ∣ ( N { S c { i } } , N { S c { j } } ) ∣ ) < θ t h t h e n { R c } ← { R c } ∪ P j { A } ← { A } ∖ P j I f c { P j } < c t h t h e n { S c } ← { S c } ∪ P j e n d i f e n d i f e n d f o r e n d f o r 将 当 前 区 域 添 加 进 全 局 分 割 列 表 { R } ← { R } ∪ { R c } e n d w h i l e R e t u r n { R } \begin{aligned} & 输入: \\ &\quad 点云 = \{P\} \\ &\quad 点法向量 = \{N\} \\ &\quad 点曲率 = \{c\} \\ &\quad 寻找邻域函数\ \Omega(.) \\ &\quad 曲率阈值\ c_{th} \\ &\quad 角度阈值\ \theta_{th} \\ & 初始化: \\ &\quad 区域列表\ {R}\leftarrow{ \emptyset } \\ &\quad 可获取点的列表\ \{A\}\leftarrow\{1,...,|P|\} \\ & 算法: \\ &\quad While\ \{A\}\ is\ not\ empty\ do \\ &\quad \quad 当前区域\ \{R_c\}\leftarrow{\emptyset} \\ &\quad \quad 当前种子集合\ \{S_c\}\leftarrow{\emptyset} \\ &\quad \quad \{A\}中最小曲率的点\rightarrow P_{min} \\ &\quad \quad \{S_c\}\leftarrow\{S_c\}\cup P_{min} \\ &\quad \quad \{R_c\}\leftarrow\{R_c\}\cup P_{min} \\ &\quad \quad \{A\}\leftarrow\{A\}\setminus P_{min} \\ &\quad \quad for\ i = 0\ to\ size\ ( \{S_c\} ) do \\ &\quad \quad \quad 找在当前种子点中的最近邻接点 \{B_c\}\leftarrow\Omega(S_c\{i\}) \\ &\quad \quad \quad for\ j = 0\ to\ size\ ( \{B_c\} ) do\ \\ &\quad \quad \quad \quad 当前邻接点\ P_j\leftarrow B_c\{j\} \\ &\quad \quad \quad \quad If\ \{A\}\ contains\ P_j\ and \ cos^{-1}(|(N\{S_c\{i\}\},N\{S_c\{j\}\})|)<\theta_{th}\ then\ \\ &\quad \quad \quad \quad \quad \{R_c\}\leftarrow\{R_c\}\cup P_j \\ &\quad \quad \quad \quad \quad \{A\}\leftarrow\{A\}\setminus P_j \\ &\quad \quad \quad \quad \quad If\ c\{P_j\}<c_{th}\ then \\ &\quad \quad \quad \quad \quad \quad \{S_c\}\leftarrow\{S_c\}\cup P_j \\ &\quad \quad \quad \quad \quad end\ if \\ &\quad \quad \quad \quad end\ if \\ &\quad \quad \quad end\ for \\ &\quad \quad end\ for \\ &\quad \quad 将当前区域添加进全局分割列表\ \{R\}\leftarrow\{R\}\cup\{R_c\} \\ &\quad end\ while \\ &\quad Return\ \{R\} \end{aligned} 输入:点云={P}点法向量={N}点曲率={c}寻找邻域函数 Ω(.)曲率阈值 cth角度阈值 θth初始化:区域列表 R←∅可获取点的列表 {A}←{1,...,∣P∣}算法:While {A} is not empty do当前区域 {Rc}←∅当前种子集合 {Sc}←∅{A}中最小曲率的点→Pmin{Sc}←{Sc}∪Pmin{Rc}←{Rc}∪Pmin{A}←{A}∖Pminfor i=0 to size ({Sc})do找在当前种子点中的最近邻接点{Bc}←Ω(Sc{i})for j=0 to size ({Bc})do 当前邻接点 Pj←Bc{j}If {A} contains Pj and cos−1(∣(N{Sc{i}},N{Sc{j}})∣)<θth then {Rc}←{Rc}∪Pj{A}←{A}∖PjIf c{Pj}<cth then{Sc}←{Sc}∪Pjend ifend ifend forend for将当前区域添加进全局分割列表 {R}←{R}∪{Rc}end whileReturn {R}
代码(The code)
算法所用的点云文件:region_growing_tutorial.pcd
需要创建一个叫region_growing_segmentation.cpp文件,在文件中填入以下代码:
#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/filter_indices.h> // for pcl::removeNaNFromPointCloud
#include <pcl/segmentation/region_growing.h>
int main ()
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
if ( pcl::io::loadPCDFile <pcl::PointXYZ> ("region_growing_tutorial.pcd", *cloud) == -1)
{
std::cout << "Cloud reading failed." << std::endl;
return (-1);
}
pcl::search::Search<pcl::PointXYZ>::Ptr tree (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);
pcl::IndicesPtr indices (new std::vector <int>);
pcl::removeNaNFromPointCloud(*cloud, *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." << 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;
std::size_t 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 ())
{
}
return (0);
}
代码解释(The explanation)
-
简单的读取点云文件
*.pcdpcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); if ( pcl::io::loadPCDFile <pcl::PointXYZ> ("region_growing_tutorial.pcd", *cloud) == -1) { std::cout << "Cloud reading failed." << std::endl; return (-1); } -
上文有提及这个算法需要计算法向量,这里的
pcl::NormalEstimation类就是用来计算这个的。- 如果想知道它是这么完成的可以去看 估算点云中的表面法向量 这个教程
pcl::search::Search<pcl::PointXYZ>::Ptr tree (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); -
因为
pcl::RegionGrowing是派生于pcl::PCLBase,所以可以使用索引。这意味着我们可以仅分割索引数组中列出的点,而不是整个点云。这里,只选择有效点进行分割。pcl::IndicesPtr indices (new std::vector <int>); pcl::removeNaNFromPointCloud(*cloud, *indices); -
接下来算是实例化
pcl::RegionGrowing的部分,这是一个模板类,有两个参数:- PointT - 要使用的点的类型(在样例中是
pcl::PointXYZ) - NormalT - 要使用的法向量的类型(在样例中是
pcl::Normal)
pcl::RegionGrowing<pcl::PointXYZ, pcl::Normal> reg; reg.setMinClusterSize (50); reg.setMaxClusterSize (1000000); - PointT - 要使用的点的类型(在样例中是
-
然后设置最小和最大簇的大小。这意味着分割完成后,所有点小于最小值(或大于最大值)的簇都将被丢弃。最小值和最大值的默认值分别为1和“尽可能多”。
-
该算法的内部结构需要K近邻搜索,因此这里提供了一种搜索方法并设置了邻接点数。
在这之后,还需接收 所需分割的云、点的索引和法线。
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); -
以下代码是面向那些不熟悉如何使用
pcl::PointIndices以及如何访问其元素的朋友。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; std::size_t 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::RegionGrowing类提供了一个方法,该方法返回上色之后的点云,其中每个簇都有自己的颜色。因此,在这部分代码中,pcl::visualization::CloudViewer被实例化,用于查看分割结果,即相同颜色的点云。可以在CloudViewer 教程中了解有关点云可视化的更多信息。pcl::PointCloud <pcl::PointXYZRGB>::Ptr colored_cloud = reg.getColoredCloud (); pcl::visualization::CloudViewer viewer ("Cluster viewer"); viewer.showCloud(colored_cloud); while (!viewer.wasStopped ()) { } return (0); }
编译与运行(Compiling and running the program)
-
下面这个是
CMakeLists.txt文件cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(region_growing_segmentation) find_package(PCL 1.5 REQUIRED) include_directories(${PCL_INCLUDE_DIRS}) link_directories(${PCL_LIBRARY_DIRS}) add_definitions(${PCL_DEFINITIONS}) add_executable (region_growing_segmentation region_growing_segmentation.cpp) target_link_libraries (region_growing_segmentation ${PCL_LIBRARIES}) -
在这之后,使它运行,将会看到以下效果:


-
你可能可以看到在上色的点云上有很多红点,如下图所示,这些红点是因为太多或太少点而被拒绝的簇。

本文详细介绍了如何使用区域增长算法对点云数据进行分割,通过曲率值和法线角度比较来合并相近点,实现点云的平滑曲面分割。算法包括从最小曲率点开始的区域生长,邻接点搜索,以及曲率和法线角度阈值判断。文章还提供了C++代码实现,并解释了代码工作原理,最后展示了编译运行效果。


&spm=1001.2101.3001.5002&articleId=125479141&d=1&t=3&u=f7e1854b022c42ca92293568b48b873f)
783

被折叠的 条评论
为什么被折叠?



