注意:本方法去除地面效果较好。但参数较复杂,需要理解原理才好调参。
目录
1.名词解释
(1)腐蚀:类似保留特征的下采样。即去除不必要的部分,简化物体的形状。可去除噪声数据。
如::一棵大树,去掉树叶等不必要数据,只保留树干,用树干这一主要特征表示树木。
(2)膨胀:类似上采样。即在物体原有的基础上,按照物体特征,增加物体的合理数据量。可修复数据空洞。
如:一棵大树,增加树叶等数据,丰富树木细节,提升数据量。
(3)开操作:先腐蚀后膨胀。即先下采样再上采样。开操作可消除细小物体、分离物体、平滑较大物体边界,突出物体主要特征数据。
(4)闭操作:先膨胀后腐蚀。即先上采样再下采样。闭操作可填充物体内细小空洞,连接邻近物体、平滑边界,突出物体主要特征数据。
2.渐进式滤波原理
(1)以最低z值2D栅格化数据:
1)加载点云。
2)将点云用2D栅格分成一块一块的2D栅格。
3)获取每个栅格中所有点云点的z值最低点,作为特征点保留。
4)生成只保留z值最低特征点的最小表面网格。
附:引入窗口概念。
窗口:即2D栅格化之后,假设获取的2D栅格如下图图1所示。
窗口为图2中包含一定NxN个2D的空间范围(红框)。其中N值即为窗口大小。如2x2窗口,4x4窗口。
图1 已获取的2D栅格
图2 窗口的示意图
(2)何为渐进式形态学滤波:
即通过不停的增大窗口的尺寸(即渐进),寻找最优的窗口大小,以实现形态学滤波(包含开、闭操作)的滤波。
例如:我们要过滤一块点云,去除建筑物留下地面。可去除所有建筑物,且不去除地面点的最优窗口大小未知。
1)我们首先设置初始窗口大小为10m,然后执行形态学滤波的开运算,结果中有些建筑物未被剔除。
2)增大窗口,设置窗口大小为12m,然后执行形态学滤波的开运算,结果中仍有些建筑物未被剔除。
3)再度增大窗口,设置窗口大小为14m,然后执行形态学滤波的开运算,结果中大多数建筑物被剔除。得到最优窗口大小14m。
以上例子中,窗口大小10m->12m->14m,执行形态学滤波的过程,即为渐进式形态学滤波。
(3)增加窗口大小的方法:
1)非指数级增加:k:迭代次数,b:初始窗口大小。(对于大型物体,计算时间过长)。
2)指数级增加:
(4)高差阈值的引入:
1)为了防止某些地面点被错误地滤除,我们将使用公式计算得到一个高差阈值。该高差阈值将用于判断每次窗口大小变化之后两次形态学滤波计算得到的高差,如果高差变化较小(即高差变化值小于高差阈值),那么保留,否则剔除。
2)公式:
c:2d栅格大小;s:地形坡度参数;:第k次滤波的窗口大小;:初始高差阈值;:最大高差阈值;:当前滤波窗口下的高差阈值。
画图表示第二行的公式:
注:PCL中,需手动设置三个参数,分别为地形坡度参数,初始高差阈值,最大高差阈值。
<1>地形坡度参数设置:城市地形坡度一般为0.1f-0.3f,山地地形坡度一般为0.7f-1.3f。该值需要实际跑数据时候找个合适值。
<2>初始高差阈值:一般设为0.5f。
<3>最大高差阈值:
在城市地区,主要的非地面物体包括汽车、树木和建筑物。单个汽车和树木的尺寸远小于建筑物的尺寸,因此大多数汽车和树木通常在前几次迭代中被移除,而大型建筑物将在最后被移除。最大高差阈值可以设置为固定高度(例如,最低建筑高度),以确保识别建筑群。
山区的非地面物体主要是植被(树木)。不需要设置固定的最大高差阈值来移除树木,通常将其设置为研究区域内最大的高差。
3.使用场景:
用于提取地面点,如果参数调好,提取地面点还算好用。
4.关键函数:
(1)最大窗口大小
segmentation.setMaxWindowSize(20);
以下三个值,均用于计算高差阈值,避免错误剔除。
(2)地形坡度参数
segmentation.setSlope(1.0f);
(3)初始高差阈值
segmentation.setInitialDistance(0.5f);
(4)最大高差阈值
segmentation.setMaxDistance(3.0f);
(5)设置初始窗口的大小
segmentation.setCellSize(3);
(6)设置是否以指数方式增加窗口大小
segmentation.setExponential(true);
(7)设置计算渐进窗口大小时使用的基数
segmentation.setBase(3);
5.完整代码
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/common/time.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/segmentation/progressive_morphological_filter.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>
int main()
{
/****************渐进式形态学滤波********************/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); // 源点云
pcl::io::loadPCDFile("D:/code/csdn/data/samp11-TIN.pcd", *cloud);
pcl::PointCloud<pcl::PointXYZ>::Ptr groundCloud(new pcl::PointCloud<pcl::PointXYZ>);
// 渐进式形态学滤波
pcl::ProgressiveMorphologicalFilter<pcl::PointXYZ> segmentation;
segmentation.setInputCloud(cloud); // 待处理点云
segmentation.setMaxWindowSize(20); // 最大窗口大小
segmentation.setSlope(1.0f); // 地形坡度参数
segmentation.setInitialDistance(0.5f); // 初始高差阈值
segmentation.setMaxDistance(3.0f); // 最大高差阈值
segmentation.setCellSize(3); // 设置窗口的大小
segmentation.setBase(3); // 设置计算渐进窗口大小时使用的基数
segmentation.setExponential(true); // 设置是否以指数方式增加窗口大小
pcl::PointIndicesPtr groundIndices(new pcl::PointIndices); // 地面点的索引
segmentation.extract(groundIndices->indices);
pcl::copyPointCloud(*cloud, *groundIndices, *groundCloud);
// ---------------------------------------可视化---------------------------------------------
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("ProgressiveMorphologicalFilter"));
viewer->addPointCloud(cloud, "not ground");
viewer->addPointCloud(groundCloud, pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>(groundCloud, 0, 255, 0), "ground");
pcl::visualization::PCLVisualizer::Ptr viewer0(new pcl::visualization::PCLVisualizer("only ground"));
viewer0->addPointCloud(groundCloud, pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>(groundCloud, 0, 255, 0), "ground");
while (!viewer->wasStopped() || !viewer0->wasStopped())
{
viewer->spinOnce(100);
viewer0->spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(1000));
}
return (0);
}
6.结果可视化
绿色为地面点,白色为非地面点。
单独展示提取出的非地面点