一、滤波
1.1 滤波的意义和目的
(a)滤波的概述
在获取点云数据时,由于设备精度、操作者经验、环境因素等带来的影响,以及电磁波衍射特性、被测物体表面性质变化和数据拼接配准操作过程的影响,点云
数据中将不可避免地出现一些噪声点。实际应用中除了这些测量随机误差产生的噪声点之外,由于受到外界干扰如视线遮挡,障碍物等因素的影响,点云数据中
往往存在着一 些离主体点云即被测物体点云较远的离散点,即离群点,不同的获取设备点云噪声结构也有不同,其他可以通过滤波重采样完成的工作,可以是
孔洞修复、信息损失最小海址点云数据压缩处理等。
(b) 滤波处理的目的
在点云处理流程中滤波处理作为预处理的第一步,往往对后续处理管道影响很大,只有在滤波预处理中将噪声点、离群点、孔洞、数据压缩等按照后续处理定制,才能够更好地进行配准、特征提取、曲面重建、可视化等后续应用处理。
**目的**
1、消除图像中混入的噪声;
2、为图像识别抽取出图像特征。
**要求**
1、不能损坏图像轮廓及边缘;
2、图像视觉效果应当更好。
1.2 多种滤波方式的介绍
1.3 PCL滤波的实例
PCL中点云滤波模块提供了很多灵活实用的滤波处理算法,例如双边滤波、高斯滤波、条件滤波、直通滤波、基于随机采样一致性滤波等,滤波模块是作为PCL 的一个处理成员模块,
其在应用中非常方便与其他点云处理流程集成。
(1)passthrough滤波方法
这个方法只要指定过滤的名字,和限制条件就可以完成简单滤波
类PassThrough实现对用户给定点云某个字段的限定下,对点云进行简单的基本过滤,例如限制过滤掉点云中所有X字段不在某个范围内的点,该类的使用比较灵活
但完全取决千用户的限定字段和对应条件。
关键成员函数:
void setFilterFieldName (const std: :string &field_name)
设置限定字段的名称字符串 field_name, 例如,,z"等。
void setFilterLimits (const double &!imit_min, const double &limit_max)
设置滤波限制条件,包括最小值 limit_min 和最大值 limit_max。该函数与 setFilterFieldName( )一起使用,点云中所有点的 setFilterFieldName( )设置的字段
的值未在用户所设定区间范围外的点将被删除。 参数 limit_min 为允许的区间范围的最小值,默认为 DBL _ MIN, limit _ max 为允许的区间范围的最大值,默认为DB L_MAX。
setFilterLimitsNegative (bool b); // 默认为false设置返回滤波限制条件外的点是否是内部点,
// 如果为true,则返回过滤内的点,否则,返回过滤后的值。
// passtrough.cpp是一种简单的过滤方法,指定一个filterName,设置限制的范围大小进行过滤
#include <iostream>
#include <ctime>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
int
main01(int argc, char** argv)
{
srand(time(0));
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
//填入点云数据
cloud->width = 5;
cloud->height = 1;
cloud->points.resize(cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size(); ++i)
{
cloud->points[i].x = rand() / (RAND_MAX + 1.0f) - 0.5;
cloud->points[i].y = rand() / (RAND_MAX + 1.0f) - 0.5;
cloud->points[i].z = rand() / (RAND_MAX + 1.0f) - 0.5;
}
std::cerr << "Cloud before filtering: " << std::endl;
for (size_t i = 0; i < cloud->points.size(); ++i)
std::cerr << " " << cloud->points[i].x << " "
<< cloud->points[i].y << " "
<< cloud->points[i].z << std::endl;
// 创建滤波器对象
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud); // 传入点云数据
pass.setFilterFieldName("z"); // 设置限定字段的名称
pass.setFilterLimits(0.0, 1.0); // 设置过滤的范围大小
pass.setFilterLimitsNegative (true); // 设置返回滤波限制条件外的点是否是内部点,
// 如果为true,则返回过滤内的点,否则,返回过滤后的值。
pass.filter(*cloud_filtered); // 调用过滤函数
std::cerr << "Cloud after filtering: " << std::endl;
for (size_t i = 0; i < cloud_filtered->points.size(); ++i)
std::cerr << " " << cloud_filtered->points[i].x << " "
<< cloud_filtered->points[i].y << " "
<< cloud_filtered->points[i].z << std::endl;
return (0);
}
(b) VoxelGrid过滤方法
这种过滤方法其实是在预处理阶段,把一个三维模型数据的大小大大降低易于程序运行。
class pcl:: VoxelGrid< PointT >:类VoxelGrid根据给定的点云构造一个三维体素栅格并进行下采样达到滤波的效果。 具体说来类VoxelGrid通过输入的点云数据
创建一个三维体素栅格,然后将每个体素内所有的点都用该体素内的点集的重心来近似,这样就大大减少了数据量,所以该类常用千对大 数据量的下采样处理,
特别是在配准、曲面重建等工作 之前作为预处理,可以很好地提高程序的速度。
关键成员函数:
void set Leaf Size (const Eigen: : Vector4f &. leaf_size) :
通过向量设置体素栅格 leaf_size 。
void setLeafSize (float lx, float ly, float lz)
通过lx、ly、lz 分别设置体素栅格在XYZ3个方向上的尺寸。庙en::Vector3i getGridCoordinates (float x, float y, float z): 返回在某(x,y,z)坐标点对应的体素(i,j, k)栅格坐标。
void setDownsampleAllData (bool downsample)
#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::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>());
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>());
// 填入点云数据
pcl::PCDReader reader;
// 把路径改为自己存放文件的路径
reader.read("table_scene_lms400.pcd", *cloud); // 记住要事先下载这个数据集!
std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height
<< " data points (" << pcl::getFieldsList(*cloud) << ")."<<std::endl;
// 创建滤波器对象
pcl::VoxelGrid<pcl::PointXYZ> sor; // PointCLoud2类适合更低版本的点云文件,仅在PCL1.x中适用。
sor.setInputCloud(cloud);
sor.setLeafSize(0.01f, 0.01f, 0.01f);
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.writeASCII("table_scene_lms400_downsampled.pcd", *cloud_filtered);
return (0);
}
运行结果如下:可以发现经过VoxelGrid之后的pcd文件从16.6M降低到了1.32M,观察一下可视化后的两个模型,也可以发现模型样式是没有发生变化的,
但是经过VoxelGrid之后的点云数据明显比原先的稀疏
(c)StatisticalOutlierRemoval滤波器移除离群点
背景知识:
激光扫描通常会产生密度不均匀的点云数据集。 另外,测盘中的误差会产生稀疏的离群点,使效果更糟。 估计局部点云特征(例如采样点处法向量或曲率变化率)
的运算很复杂,这会导致错误的数值,反过来有可能导致点云的配准等后期处理失败。
解决方法
对每个点的邻域进行一个统计分析,并修剪掉那些不符合一定标准的点,稀疏离群点移除方法基于在输入数据中对点到掉那些不符合一定标准的点。 我们的稀疏离
群点移除方法基于在输人数据中对点到临近点的距离分布的计算。 对每个点,我们计算它到它的所有临近点的平均距
离。 假设得到的结果是一个高斯分布,其形状由均值和标准差决定,平均距离在标准范围(由全局距离平均值和方差定义)之外的点,可被定义为离群点并可从数据集中除去。
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/statistical_outlier_removal.h>
int
main(int argc, char** argv)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
// 填入点云数据
pcl::PCDReader reader;
// 把路径改为自己存放文件的路径
reader.read<pcl::PointXYZ>("table_scene_lms400.pcd", *cloud);
std::cerr << "Cloud before filtering: " << std::endl;
std::cerr << *cloud << std::endl;
// 创建滤波器对象
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50); // 设置50个领域点来计算平均距离
sor.setStddevMulThresh(1.0); // 设置标准差阈值
sor.filter(*cloud_filtered);
std::cerr << "Cloud after filtering: " << std::endl;
std::cerr << *cloud_filtered << std::endl;
pcl::PCDWriter writer;
writer.write<pcl::PointXYZ>("table_scene_lms400_inliers.pcd", *cloud_filtered, false);
sor.setNegative(true); // 设置取外点,获得离群点
sor.filter(*cloud_filtered);
writer.write<pcl::PointXYZ>("table_scene_lms400_outliers.pcd", *cloud_filtered, false);
return (0);
}
运行结果如下,可以发现点云数据从460400减少到了451410;
在可视化中,左边的是原始三维模型,右上角的是红色点是离群点,右下角的绿色是过滤后的点云数据
通过下面的可视化代码,显示table_scene_lms400.pcd、table_scene_lms400_inliers.pcd、table_scene_lms400_outliers.pcd的三维模型。
可视化代码
#include<iostream>
#include<thread>
#include<pcl/visualization/pcl_visualizer.h>
#include<pcl/point_types.h>
#include<pcl/point_cloud.h>
#include<pcl/io/pcd_io.h>
using namespace std;
int
main() {
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud1(new pcl::PointCloud<pcl::PointXYZRGB>);
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud2(new pcl::PointCloud<pcl::PointXYZRGB>);
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud3(new pcl::PointCloud<pcl::PointXYZRGB>);
// 读取pcd文件
pcl::io::loadPCDFile("pcd/table_scene_lms400.pcd", *cloud1);
pcl::io::loadPCDFile("pcd/table_scene_lms400_inliers.pcd", *cloud2);
pcl::io::loadPCDFile("pcd/table_scene_lms400_outliers.pcd", *cloud3);
// 创建3D窗口并添加显示点云
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
viewer->initCameraParameters();
int v1(0);
viewer->createViewPort(0.0, 0.0, 0.5, 1.0, v1);
viewer->setBackgroundColor(1, 1, 1, v1);
viewer->addText("table_scene_lms400.pcd", 10, 10, "v1 text", v1);
pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(cloud1);
viewer->addPointCloud<pcl::PointXYZRGB>(cloud1, rgb, "sample cloud1", v1);
int v2(0);
viewer->createViewPort(0.5, 0.0, 1.0, 0.5, v2);
viewer->setBackgroundColor(0.3, 0.3, 0.3, v2);
viewer->addText("table_scene_lms400_inliers.pcd", 10, 10, "v2 text", v2);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> single_color(cloud2, 0, 255, 0);
viewer->addPointCloud<pcl::PointXYZRGB>(cloud2, single_color, "sample cloud2", v2);
int v3(0);
viewer->createViewPort(0.5, 0.5, 1.0, 1.0, v3);
viewer->setBackgroundColor(0.3, 0.3, 0.3, v3);
viewer->addText("离群点", 10, 10, "v3 text", v3);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> single_color2(cloud3, 255, 0, 0);
viewer->addPointCloud<pcl::PointXYZRGB>(cloud3, single_color2, "sample cloud3", v3);
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud1");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud2");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud3");
viewer->addCoordinateSystem(1.0);
while (!viewer->wasStopped())
{
viewer->spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(100000));
}
}
(d) 使用参数化模型投影点云
将点投影到一个参数化模型上(例如平面或球等)。
class pcl : : Projectlnliers< PointT >
类Projectlnliers使用一个模型和一组的内点的索引,将内点投影到模型形成新的一个独立点云。
关键成员函数
void setModelType (int model):通过用户给定 的参数设置使用的模型类型,参数Model为模型类型(见model_ types. h)有圆球、平面、直线等。
void setModelCoefficients (const ModelCoefficientsConstPtr &model)设定 指向模型系数的指针,model为指向模型系数的指针,
void setCopyAllData (bool val)设置返回所有点还是仅返回投影内点后得到的点云 ,参数val 设置为true, 则返回所有点,val 设姓为false,则仅返回投影的内点。
void filter (PointCloud &output) 获取滤波处理后的点云 output。
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/ModelCoefficients.h>
#include <pcl/filters/project_inliers.h>
int
main04(int argc, char** argv)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_projected(new pcl::PointCloud<pcl::PointXYZ>);
// 填入点云数据
cloud->width = 5;
cloud->height = 1;
cloud->points.resize(cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size(); ++i)
{
cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
std::cerr << "Cloud before projection: " << std::endl;
for (size_t i = 0; i < cloud->points.size(); ++i)
std::cerr << " " << cloud->points[i].x << " "
<< cloud->points[i].y << " "
<< cloud->points[i].z << std::endl;
// 创建一个系数为X=Y=0,Z=1的平面
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients());
coefficients->values.resize(4);
coefficients->values[0] = coefficients->values[1] = 0;
coefficients->values[2] = 1.0;
coefficients->values[3] = 0;
// 创建滤波器对象(投影的原理)
pcl::ProjectInliers<pcl::PointXYZ> proj;
proj.setModelType(pcl::SACMODEL_PLANE); // 设置模板,将来把所有点云投影到这个model上
proj.setInputCloud(cloud);
proj.setModelCoefficients(coefficients); // 可以看作投影PLANE的具体参数
proj.filter(*cloud_projected);
std::cerr << "Cloud after projection: " << std::endl;
for (size_t i = 0; i < cloud_projected->points.size(); ++i)
std::cerr << " " << cloud_projected->points[i].x << " "
<< cloud_projected->points[i].y << " "
<< cloud_projected->points[i].z << std::endl;
return (0);
}
运行结果:把点云数据投影到Z=1的平面上,所以将来点云数据的z值全为0,其他平移即可,所以x,y值不变。
二、总结
简单的了解了一下过滤的基本知识,代码来源于PCL官网和PCL点云库学习教程。因为关键点提取的知识点比较多,下周单独放在一个博客中。