1.点云切割概述
点云分割是根据空间,几何和纹理等特征对点云进行划分,使得同一划分内的点云拥有相似的特征,点云的有效分割往往是许多应用的前提,例如逆向工作,CAD领域对零件的不同扫描表面进行分割,然后才能更好的进行空洞修复曲面重建,特征描述和提取,进而进行基于3D内容的检索,组合重用等。
2.PCL中常用的点云分割方法
Plane model segmentation (平面模型分割):这个算法能够把地面等一些平面给分割出来,方便后面的物体的点云分割。
Cylinder model segmentation(圆柱模型分割):这个算法能够把一些圆柱体分割出来,方便后面的物体的点云分割。
Euclidean Cluster Extraction (欧几里德聚类提取):这个算法通俗来讲就是先从点云中找出一个点p0,然后寻找p0周围最近的n个点,如果这n个点与p0之间的距离小于预先设定的阈值,那么就把这个点取出,依次重复。
Region growing segmentation (区域蔓延分割):对于普通点云,其可由法线、曲率估计算法获得其法线和曲率值。通过法线和曲率来判断某点是否属于该类,向周边蔓延直至完成。
Progressive Morphological Filter(渐进形态过滤器):这个算法本身用于处理高空获取的激光雷达数据,把地面与非地面的物体分割,也就是可以将地面分割出来使其他物体悬空,为下一步打基础。
3.几种分割的使用实例
3.1.简单平面分割
这个算法分割点云的平面的原理就是,通过改变平面模型(ax+by+cz+d=0)的参数,找出哪一组的参数能使得这个模型一定程度上拟合最多的点。这个程度就是由distancethreshold这个参数来设置。那找到这组参数后,这些能够被拟合的点就是平面的点。总体感受就是这个算法如果理解的不好调参有点复杂。
代码:
#include <iostream>
#include <pcl/ModelCoefficients.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/sample_consensus/method_types.h> //随机参数估计方法头文件
#include <pcl/sample_consensus/model_types.h> //模型定义头文件
#include <pcl/segmentation/sac_segmentation.h> //基于采样一致性分割的类的头文件
#include <pcl/filters/extract_indices.h>
#include <pcl/visualization/cloud_viewer.h>
int
main (int argc, char** argv)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 读取一个PCD文件
if (pcl::io::loadPCDFile<pcl::PointXYZ>("d:/2.pcd", *cloud) != 0)
{
return -1;
}
//创建分割时所需要的模型系数对象,coefficients及存储内点的点索引集合对象inliers
pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
// 创建分割对象
pcl::SACSegmentation<pcl::PointXYZ> seg;
// 可选择配置,设置模型系数需要优化
seg.setOptimizeCoefficients (true);
// 必要的配置,设置分割的模型类型,所用的随机参数估计方法,距离阀值,输入点云
seg.setModelType (pcl::SACMODEL_PLANE); //设置模型类型
seg.setMethodType (pcl::SAC_RANSAC); //设置随机采样一致性方法类型
seg.setDistanceThreshold (5); //设定距离阀值,距离阀值决定了点被认为是局内点是必须满足的条件
//表示点到估计模型的距离最大值,
seg.setInputCloud (cloud);
//引发分割实现,存储分割结果到点几何inliers及存储平面模型的系数coefficients
seg.segment (*inliers, *coefficients);
if (inliers->indices.size () == 0)
{
return (-1);
}
pcl::ExtractIndices<pcl::PointXYZ> extract; //创建点云提取对象
extract.setInputCloud (cloud);
extract.setIndices (inliers);
extract.setNegative (false);
extract.filter (*cloud);
//点云可视化
pcl::visualization::CloudViewer viewer("sa");
viewer.showCloud(cloud);
while (!viewer.wasStopped()){
}
return (0);
}
处理前:
处理后:
要的就是这种效果,这样就可以将地面分割出去,使车辆悬空,然后通过后面的进一步处理将货车显示出来
3.2.圆柱模型分割
圆柱模型分割的具体原理与平面模型分割类似,如果理解了平面模型分割那么圆柱模型分割也就容易理解了,这里就不再详述。
3.3.欧几里德聚类提取
这个算法通俗来讲就是先从点云中找出一个点p0,然后寻找p0周围最近的n个点,如果这n个点与p0之间的距离小于预先设定的阈值,那么就把这个点取出,依次重复,最终分割出多个点云。
代码:
#include <pcl/io/pcd_io.h>
#include <pcl/segmentation/extract_clusters.h>
#include <QDebug>
#include <pcl/visualization/cloud_viewer.h>
#include <iostream>
int
main(int argc, char** argv)
{
// 申明存储点云的对象.
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 读取一个PCD文件
if (pcl::io::loadPCDFile<pcl::PointXYZ>("d:/2.pcd", *cloud) != 0)
{
return -1;
}
// 建立kd-tree对象用来搜索
pcl::search::KdTree<pcl::PointXYZ>::Ptr kdtree(new pcl::search::KdTree<pcl::PointXYZ>);
kdtree->setInputCloud(cloud);
// Euclidean 聚类对象.
pcl::EuclideanClusterExtraction<pcl::PointXYZ> clustering;
// 设置聚类的最小值 2cm
clustering.setClusterTolerance(0.2);
// 设置聚类的小点数和最大点云数
clustering.setMinClusterSize(1000);
//clustering.setMaxClusterSize(25000);
clustering.setSearchMethod(kdtree);
clustering.setInputCloud(cloud);
std::vector<pcl::PointIndices> clusters;
clustering.extract(clusters);
// For every cluster...
for (std::vector<pcl::PointIndices>::const_iterator i = clusters.begin(); i != clusters.end(); ++i)
{
//添加所有的点云到一个新的点云中
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>);
for (std::vector<int>::const_iterator point = i->indices.begin(); point != i->indices.end(); point++)
cluster->points.push_back(cloud->points[*point]);
cluster->width = cluster->points.size();
cluster->height = 1;
cluster->is_dense = true;
//实验预先知道了最大的点云数量,偷懒就不排序查找点云数量最大的点云了
if(cluster->points.size()>10000){
//处理后点云显示
pcl::visualization::CloudViewer viewer("PCL滤波");
viewer.showCloud(cluster);
while (!viewer.wasStopped()){
}
}
}
}
处理前:
处理后:
效果很明显了,这已经很接近想要的结果了,但是目前默认的是货车的点云数量最多,所以很多地方还不够严谨。更好的实现方法应该是与其他处理结合。
3.4.区域蔓延分割
区域生长算法直观感觉上和欧几里德算法相差不大,都是从一个点出发,最终占领整个被分割区域。欧几里德算法是通过距离远近,来判断该点是否为要找的点,而区域蔓延分割算法是通过曲率、法线方向的来判断的。对于普通点云,先按照点的曲率值对点进行排序,找到最小曲率值点,并把它添加到种子点集,从种子的位置出发,开始往四周搜索点,然后比对点于点之间的曲率和法线方向,如果差距小于阈值就视为同一个簇。如果一个簇无法再蔓延,在剩下的点云里再找曲率小的面播种,然后继续重复直到遍历完毕。
代码:
#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/passthrough.h>
#include <pcl/segmentation/region_growing.h>
int
main (int argc, char** argv)
{
//读取PCL文件
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
if ( pcl::io::loadPCDFile <pcl::PointXYZ> ("d:/2.pcd", *cloud) == -1)
{
std::cout << "Cloud reading failed." << std::endl;
return (-1);
}
//创建法线估计对象,估计点云法向量
pcl::search::Search<pcl::PointXYZ>::Ptr tree = boost::shared_ptr<pcl::search::Search<pcl::PointXYZ> > (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::PointXYZ, pcl::Normal> reg;
//设置最小点簇的点数min
reg.setMinClusterSize (50);
//reg.setMaxClusterSize (1000000);
reg.setSearchMethod (tree);
reg.setNumberOfNeighbours (30);
//输入点云
reg.setInputCloud (cloud);
reg.setInputNormals (normals);
//设置法线角度差
reg.setSmoothnessThreshold (3.0 / 180.0 * M_PI);
//设置曲率阈值
reg.setCurvatureThreshold (1.0);
std::vector <pcl::PointIndices> clusters;
//切割处理保存到clusters中
reg.extract (clusters);
pcl::PointCloud <pcl::PointXYZRGB>::Ptr colored_cloud = reg.getColoredCloud ();
pcl::visualization::CloudViewer viewer ("Cluster viewer");
viewer.showCloud(colored_cloud);
while (!viewer.wasStopped ())
{
}
return (0);
}
处理前:
处理后:
4.总结
通过滤波和分割的学习,对分离出车辆已经有了一个实际不知是否可行的思路,具体思路如下:
1.首先由于点云数据中点的数量很大,做一些处理时耗时较多,所以第一步是使用体素滤波,实现下采样,即在保留点云原有形状的基础上减少点的数量 减少点云数据,以提高后面对点云处理的速度。
2.通过随机采样一致性(前面多出用到)分割地面,将地面从点云中分离出来,这时候地面上的物体就悬空了
3.使用统计滤波取出离散点
4.物体悬空后就更好分离了,现在就构建Kdtree通过欧几里得聚类提取获取路面上不同物体,然后车辆就提取了出来。