1. 为什么要滤波?
通常我们获取的点云数据中包含噪声,噪声会影响点云的特征提取、配准和语义处理。
点云需要处理的主要情况包括:
-
数据量过大,不易于处理,需要进行下采样
-
通常由遮挡引起的离群点,需要去除
-
点云数据的密度不均匀需要平滑
-
噪声数据需要去除
在获取点云数据时,由于设备精度、操作者经验、环境因素等带来的影响,点云数据中将不可避免地出现一些噪声点。这便需要我们对点云进行后处理。
在点云的处理流程中,滤波处理作为预处理的第一步,往往对后续处理管道影响最大,只有在滤波预处理中将噪声点、离群点、空洞等按照后续处理定制,才能更好地进行配准、特征提取、曲面重建、可视化等。
PCL中的点云处理模块提供了很多灵活实用的滤波处理算法,例如双边滤波、高斯滤波、条件滤波、直通滤波、基于随机采样一致性滤波等。
PCL中总结了几种需要进行点云滤波处理的情况,这几种情况如下:
-
(1)点云数据密度不规则需要平滑;
-
(2)因为遮挡等问题造成离群点需要去除;
-
(3)大量数据需要进行下采样;
-
(4)噪音数据需要去除。
对应的方法主要如下:
-
(a) 按具体给定的规则限制过滤去除点。
-
(b)通过常用滤波算法修改点的部分属性。
-
©对数据进行下采样。
PCL中对常规的滤波手段进行了良好地封装,主要的滤波器有直通滤波、体素法滤波、统计滤波、条件滤波等。组合使用完成任务,效果更佳。
1、如果是线结构光的采集方式得到的点云,则沿z向的分布较广,但沿x、y方向的分布则处于有限的范围内。此时,可采用直通滤波,确定x或者y方向的范围,快速裁剪离群点。
2、如果使用高分辨率相机等设备对点云进行采集,则点云往往较为密集。过多的点云数据对后续的分割工作带来困难。体素法滤波可以达到下采样的同时不破坏点云本身几何结构的功能。
3、统计滤波器用于去除明显的离群点(离群点往往由噪声引入)。噪声信息属于无用信息,信息量较小。所以离群点表达的信息可以忽略不计。考虑到离群点的特征,则可以定义某处点云小于某个密度,既点云无效。计算每个点到其最近的k个点平均距离。则点云中所有点的距离应构成高斯分布。给定均值与方差,可剔除3∑之外的点。
4、半径滤波器与统计滤波器相比更加简单粗暴。以某点为中心画一个圆计算落在该圆中点的数量,当数量大于给定值时,则保留该点,数量小于给定值则剔除该点。此算法运行速度快,依序迭代留下的点一定是最密集的,但是圆的半径和圆内点的数目都需要人工指定。
接下来,以demo的形式简单介绍一下PCL中关于直通滤波和体素法滤波的功能及函数使用方法。
2. 直通滤波
直通滤波功能:指定字段,指定坐标范围进行剪裁,可以选择保留范围内的点或者范围外的点。
//相关的头文件声明
#include <iostream> //标准C++库中输入输出类相关头文件
#include <pcl/io/pcd_io.h> //pcd读写类相关头文件
#include "pcl/point_cloud.h"
#include <pcl/point_types.h> //PCL中支持的点类型头文件
#include<pcl/common/common.h>
#include <pcl/filters/passthrough.h>
int main()
{
//创建一个PointCloud<PointXYZ> boost 共享指针并进行实例化
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
//滤波后的点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
//pcd文件路径
std::string pcd_in = "../rops_cloud.pcd";
std::string pcd_out = "../rops_cloud_passthroughed.pcd";
if (pcl::io::loadPCDFile<pcl::PointXYZ>(pcd_in,*cloud)==-1)
//打开点云文件
{
PCL_ERROR("Couldn't read file test_pcd.pcd\n");
return(-1);
}
pcl::PointXYZ minPt, maxPt;
pcl::getMinMax3D(*cloud,minPt,maxPt);
// 创建滤波器对象
pcl::PassThrough<pcl::PointXYZ> pass; //设置滤波器对象
pass.setInputCloud(cloud);//设置输入点云
pass.setFilterFieldName("z"); //设置过滤时所需要的点云类型的z字段
pass.setFilterLimits(minPt.z+0.2, maxPt.z - 0.1); //设置在过滤字段上的范围
pass.setFilterLimitsNegative(false); //设置保留范围内的还是过滤掉范围内的
pass.filter(*cloud_filtered); //执行滤波
//将数据存储到磁盘
pcl::io::savePCDFileASCII(pcd_out,*cloud_filtered);
return (0);
}
直通滤波的演示效果图,如下图所示(gif 原图,详见:https://zhuanlan.zhihu.com/p/64226544?utm_source=wechat_session )。
3. 体素法滤波
体素法滤波,即减少点的数量,减少点云数据,并同时保持点云的形状特征,在提高配准、曲面重建、形状识别等算法速度中非常实用。 PCL实现的VoxelGrid类通过输入的点云数据创建一个三维体素栅格(可把体素栅格想象为微小的空间三维立方体的集合),然后在每个体素(即三维立方体)内,用体素中所有点的重心来近似显示体素中其他点,这样该体素内所有点就用一个重心点最终表示,对于所有体素处理后得到过滤后的点云。 优缺点:这种方法比用体素中心来逼近的方法更慢,但它对于采样点对应曲面的表示更为准确。
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
int main (int argc, char** argv)
{
pcl::PCLPointCloud2::Ptr
cloud(new pcl::PCLPointCloud2());
pcl::PCLPointCloud2::Ptr
cloud_filtered(new pcl::PCLPointCloud2());
// 填入点云数据
pcl::PCDReader reader; //点云读取对象
// 把路径改为自己存放文件的路径,或者将该文件与生成的可执行文件放在同一目录下
reader.read ("../table_scene_lms400.pcd", *cloud); // 读取点云文件中的数据到cloud对象
std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height << " data points (" << pcl::getFieldsList(*cloud) << ")." <<std::endl;
pcl::VoxelGrid<pcl::PCLPointCloud2> sor; // 创建滤波器对象
sor.setInputCloud (cloud); //给滤波器对象设置需要过滤的点云
sor.setLeafSize (0.01f, 0.01f, 0.01f); //设置滤波时创建的体素大小为1cm立方体
sor.filter (*cloud_filtered); //执行滤波处理
std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height << " data points (" << pcl::getFieldsList (*cloud_filtered) << ").";
//将数据写入磁盘
pcl::PCDWriter writer;
writer.write ("../table_scene_lms400_downsampled.pcd", *cloud_filtered, Eigen::Vector4f::Zero (), Eigen::Quaternionf::Identity (), false);
return (0);
}
以上代码运行结果如下图1. 将点云结果放在CloudCampare中对比显示如图2.