描述

这篇文章继续介绍一些利用PCL库的工程代码,主要目的是为了解决一些场景的地面分割任务。

这篇文章介绍的方法包括:

  1. RANSAC
  2. 形态学分割
  3. 欧式聚类分割

在最后也介绍了一些小工具代码:点云颜色显示,以及上述算法的效果显示代码

1. RANSAC分割

ransac的原理不过多介绍,大体就是这样一个思路:
从原始数据集中选出种子点,去按照指定模型来拟合(二维数据找直线,三维数据找平面)。根据拟合结果,来看数据中有多少点可以被涵盖。迭代N次,找到最好的拟合结果。

用RANSAC可以从原始点云中,分割出平面、球、圆柱体等基本形状
不足:收敛慢,容易局部最优,无法适应一些情况等等。缺点很多,针对一些特殊任务很多开发者有更优秀的改进方法。

头文件

#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/segmentation/sac_segmentation.h>

代码

pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients); //创建一个模型参数对象,用于记录结果
pcl::PointIndices::Ptr inliers (new pcl::PointIndices); //inliers记录满足分割的点序号
pcl::SACSegmentation<pcl::PointXYZ> seg;
seg.setOptimizeCoefficients (true);
seg.setModelType (pcl::SACMODEL_PLANE); // Mandatory-设置目标几何形状
seg.setMethodType (pcl::SAC_RANSAC); //平面分割方法:RANSAC
seg.setDistanceThreshold (1); //设置模型误差阈值
seg.setInputCloud (cloud_total); //输入点云
seg.segment (*inliers, *coefficients); //分割点云

// 地面方程 ax + by + cz + d = 0
// coefficients: a, b, c, d
std::cerr << "Model coefficients: " << coefficients->values[0] << " " 
    <<coefficients->values[1] << " "<<coefficients->values[2] << " " 
    <<coefficients->values[3] <<std::endl;
std::cerr << "Model inliers point size: " << inliers->indices.size () << std::endl;
std::cerr << cloud_total->points[inliers->indices[10]].x // 举例取其中的一个点的x值

setModelType的参数为设置的模型,模型的枚举类型定义在pcl/sample_consensus/include/pcl/sample_consensus/model_types.h之中

amespace pcl
{
  enum SacModel
  {
    SACMODEL_PLANE,
    SACMODEL_LINE,
    SACMODEL_CIRCLE2D,
    SACMODEL_CIRCLE3D,
    SACMODEL_SPHERE,
    SACMODEL_CYLINDER,
    SACMODEL_CONE,
    SACMODEL_TORUS,
    SACMODEL_PARALLEL_LINE,
    SACMODEL_PERPENDICULAR_PLANE,
    SACMODEL_PARALLEL_LINES,
    SACMODEL_NORMAL_PLANE,
    SACMODEL_NORMAL_SPHERE,
    SACMODEL_REGISTRATION,
    SACMODEL_REGISTRATION_2D,
    SACMODEL_PARALLEL_PLANE,
    SACMODEL_NORMAL_PARALLEL_PLANE,
    SACMODEL_STICK
  };
}

setMethodType的参数为设置的方法,其枚举类型定义在pcl/sample_consensus/include/pcl/sample_consensus/method_types.h之中

namespace pcl
{
  const static int SAC_RANSAC  = 0;
  const static int SAC_LMEDS   = 1;
  const static int SAC_MSAC    = 2;
  const static int SAC_RRANSAC = 3;
  const static int SAC_RMSAC   = 4;
  const static int SAC_MLESAC  = 5;
  const static int SAC_PROSAC  = 6;
}

2. 形态学分割

算法利用了点云的z值信息,可以理解为三维点云上完成类似二维图像的膨胀和腐蚀操作。简单来说,就是保留指定窗口大小内的最大最小值,来寻找地面。

论文:A progressive morphological filter for removing nonground measurements from airborne LIDAR data
该方法不建议使用

缺点:实时性差,参数需要自行调整,需要对文章有很好的理解。因此效果也有待考证。

头文件

#include <pcl/segmentation/progressive_morphological_filter.h>

代码

//生成形态滤波器
pcl::ProgressiveMorphologicalFilter<pcl::PointXYZ> pmf;
pmf.setInputCloud (cloud_total);
//设置窗的大小以及切深,斜率信息
pmf.setMaxWindowSize (10);
pmf.setSlope (1.0);
pmf.setInitialDistance (0.5f);
pmf.setMaxDistance (1.5f);
pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
pmf.extract (inliers->indices);

3. 欧式聚类分割

算法原理:
利用KD数或者八叉树,依据点云的欧式距离完成聚类过程。在我看来,欧式聚类的方法是100%不如区域生长的。

不足:
聚类结果粗糙,仅仅依靠距离的分类显然是不准确的。

头文件

#include <ctime> // 用于生成随机数,完成随机颜色显示
#include <pcl/segmentation/extract_clusters.h>

代码

srand(time(0)); // 根据时间生成随机数

pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
std::vector<pcl::PointIndices> cluster_indices;
//欧式分割器
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance (0.2);
ec.setMinClusterSize (100);
ec.setMaxClusterSize (100000);
ec.setSearchMethod (tree);
ec.setInputCloud (cloud_total);
ec.extract (cluster_indices);

// 对聚类结果随机染色
std::cout<<"cluster num:"<<cluster_indices.size()<<std::endl;
for (int i = 0; i < cluster_indices.size(); i++) {
    int r = rand() % 255;
    int g = rand() % 255;
    int b = rand() % 255;
    for (int j = 0; j < cluster_indices[i].indices.size(); j++) {
        cloud_color->points[cluster_indices[i].indices[j]].r = r;
        cloud_color->points[cluster_indices[i].indices[j]].g = g;
        cloud_color->points[cluster_indices[i].indices[j]].b = b;
    }
}

结果的使用

1. 点云滤除

#include <pcl/filters/extract_indices.h>
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
// 按id号得到指定点云,cloud_filtered是cloud_total标号为inliers的点云
pcl::ExtractIndices<pcl::PointXYZ> extract;
extract.setInputCloud (cloud_total);
extract.setIndices (inliers);
extract.filter (*cloud_filtered);

2. 点云上色

pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 还需要对cloud自行录入点云
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_color(new pcl::PointCloud<pcl::PointXYZRGB>);
pcl::copyPointCloud(*cloud, *cloud_color);

for (size_t i = 0; i < cloud_total->points.size(); ++i) {
    cloud_color->points[i].r = 255;
    cloud_color->points[i].g = 0;
    cloud_color->points[i].b = 0;
}

3. 利用点云上色查看分割结果

std::cout<<inliers->indices.size()<<std::endl;
for (size_t i = 0; i < inliers->indices.size(); ++i) {
    cloud_color->points[inliers->indices[i]].r = 0;
    cloud_color->points[inliers->indices[i]].g = 255;
    cloud_color->points[inliers->indices[i]].b = 0;
}    
pcl::visualization::CloudViewer cloud_viewer_grow("RegionGrowing Cluster Result");
cloud_viewer_grow.showCloud(cloud_color);

总结

这篇文章提到的方法,RANSAC和形态学分割可以用于寻找地面,欧式聚类可以进行一定程度的分类操作。这些方法偏向demo,不适合在追求精确度的情况下使用。

埋个引子:之后有时间的话,介绍一下超体聚类、最小割算法、LCCP和CPC算法(基于点云凹凸性)。这三种方法之前没接触过,偏向于目标点云分类。

参考链接:https://www.bbsmax.com/topic/%E7%94%A8pcl%E5%AE%9E%E7%8E%B0ransac%E5%9C%B0%E9%9D%A2%E7%82%B9%E4%BA%91%E5%88%86%E5%89%B2/

写的不容易,欢迎各位朋友点赞并加关注,谢谢!