点云的区域增长分割算法(Region Growing Segmentation for Point Cloud)

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

参考页面: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  PjBc{j}If {A} contains Pj and cos1((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

  • 简单的读取点云文件 *.pcd

    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::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);
    
  • 然后设置最小和最大簇的大小。这意味着分割完成后,所有点小于最小值(或大于最大值)的簇都将被丢弃。最小值和最大值的默认值分别为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})
    
  • 在这之后,使它运行,将会看到以下效果:
    效果图1
    效果图2

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值