描述
这篇文章继续介绍一些利用PCL库的工程代码,主要目的是为了解决一些场景的地面分割任务。
这篇文章介绍的方法包括:
- RANSAC
- 形态学分割
- 欧式聚类分割
在最后也介绍了一些小工具代码:点云颜色显示,以及上述算法的效果显示代码
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算法(基于点云凹凸性)。这三种方法之前没接触过,偏向于目标点云分类。
评论(0)
您还未登录,请登录后发表或查看评论