在点云处理流程中,滤波处理作为预处理的第一步,会对后续数据处理工作影响很大,只有在滤波与处理中,将噪声点、离群点、空洞、数据压缩等按照后续处理定制,才能更好地进行配准、特征提取、曲面重建、可视化等后续应用处理。
本篇通过在PCL中应用直通滤波器对点云数据进行滤波处理,使用体素滤波器对点云进行下采样,使用统计滤波器、条件滤波器、半径滤波器移除离群点。
直通滤波器:
在点云数据中,按照坐标轴划定特定范围,保留或删除特定范围。对有一定空间特征的点云数据,比如使用线结构光扫描的方式采集点云,沿z向分布较广,但x,y向的分布处于有限范围内。此时可使用直通滤波器,确定点云在x或y方向上的范围,可较快剪除离群点,达到第一步粗处理的目的。
体素滤波器
体素化网格方法实现下采样,既减少点云的数量,并同时保持点云的形状特征。创建一个三维体素,用体素内所有点的重心近似表示体素中的其他点,这样体素内所有点就用一个重心点最终表示。
统计滤波法
统计滤波主要是去除稀疏离群的噪声点,对每个点的领域采用统计学的方法进行分析,然后去除不满足设定阈值的点,其中移除稀疏离群点的方法是基于统计和计算输入数据中点与其相邻点之间的距离分布。计算每个点与其所有相邻点之间的平均距离、方差和平均值,并用求得的所有点的距离和方差来定义这个标准范围。标准范围之外的点就是离群点,最后将这些离群点从原始点云中删除。
对于每个点,计算对应临近点的平均距离,得到的结构近似高斯分布,形状由均值和标准差决定,平均距离在标准范围(一般定为3个标准差)之外的点,定为离群点,可删除。
基本步骤如下:
1)选择一个邻域U,给定某一点云,在其领域U内,求该点全部点云的之间的平均距离的均值和方差。
均值:
方差:
其中,k表示U领域内点云的个数。
2)通过给定的阈值T,求出对应的点云坐标
其中,T的大小取决于领域内点的数目,符合条件的点云数据被保留,否则被删除。
条件滤波法
条件滤波器通过设定滤波条件进行滤波,点在范围内保留,不在范围内丢弃。
半径滤波器
以某点为中心画一个圆计算落在该圆中点的数量,当数量大于给定值时,则保留该点,数量小于给定值则剔除该点。若要求半径范围内至少存在3个点,则黄点和绿点均不满足条件,被剔除。
代码如下:
#include<iostream>
#include<pcl/point_types.h>
#include<pcl/filters/passthrough.h> //直通滤波器头文件
#include<pcl/filters/voxel_grid.h> //体素滤波器头文件
#include<pcl/filters/statistical_outlier_removal.h> //统计滤波器头文件
#include <pcl/filters/conditional_removal.h> //条件滤波器头文件
#include <pcl/filters/radius_outlier_removal.h> //半径滤波器头文件
#include<pcl/io/pcd_io.h> //pcd读写类相关头文件
#include <pcl/visualization/cloud_viewer.h> //
void viewerOneOff(pcl::visualization::PCLVisualizer& viewer) //设置背景颜色
{
viewer.setBackgroundColor(1.0f, 0.7f, 1.0f);
}
int main(int argc, char** argv)
{
///****************************************************
/*创建点云数据集。*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); //创建一个共享指针,并实例化
cloud->width = 500; //创建点云的数量
cloud->height = 1; //设置点云的高度,其为无序点云
cloud->points.resize(cloud->width*cloud->height);
std::cout << "创建原始点云数据" << std::endl;
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);
}
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;
}
std::cout << "原始点云数据点数:" << cloud->points.size() << std::endl << std::endl; //打印所有点到标准错误输出
///****************************************************
/*方法一:直通滤波器对点云进行处理。*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_PassThrough(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PassThrough<pcl::PointXYZ> passthrough; //设置滤波器对象
passthrough.setInputCloud(cloud);//输入点云
passthrough.setFilterFieldName("z");//对z轴进行操作,也可以对"x"和"y"轴进行操作
passthrough.setFilterLimits(0.0, 50.0);//设置直通滤波器操作范围
//passthrough.setFilterLimitsNegative(true);//true表示保留范围内,false表示保留范围外
passthrough.filter(*cloud_after_PassThrough);//执行滤波,过滤结果保存在 cloud_after_PassThrough
std::cout << "直通滤波后点云数据点数:" << cloud_after_PassThrough->points.size() << std::endl;
///****************************************************
///****************************************************
/*方法二:体素滤波器实现下采样*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_voxelgrid(new pcl::PointCloud<pcl::PointXYZ>);//
pcl::VoxelGrid<pcl::PointXYZ> voxelgrid; //创建滤波对象
voxelgrid.setInputCloud(cloud); //设置需要过滤的点云
voxelgrid.setLeafSize(10.0f, 10.0f, 10.0f);//长宽高
voxelgrid.filter(*cloud_after_voxelgrid); //执行滤波处理后,存储输出cloud_after_voxelgrid
std::cout << "体素化网格方法后点云数据点数:" << cloud_after_voxelgrid->points.size() << std::endl;
///****************************************************
///****************************************************
/*方法三:统计滤波器滤波*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_StatisticalRemoval(new pcl::PointCloud<pcl::PointXYZ>);//
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> Statistical; //创建滤波器对象
Statistical.setInputCloud(cloud); //设置待滤波的点云
Statistical.setMeanK(20);//取平均值的临近点数
Statistical.setStddevMulThresh(5);//设置是否为离群点的阈值:超过平均距离一个标准差以上,该点记为离群点,将其移除
Statistical.filter(*cloud_after_StatisticalRemoval); //执行滤波处理,保存内点到cloud_after_StatisticalRemoval
std::cout << "统计分析滤波后点云数据点数:" << cloud_after_StatisticalRemoval->points.size() << std::endl;
///****************************************************
///****************************************************
/*方法四:条件滤波器*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_Condition(new pcl::PointCloud<pcl::PointXYZ>);
pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_condition(new pcl::ConditionAnd<pcl::PointXYZ>());
range_condition->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new
pcl::FieldComparison<pcl::PointXYZ>("z", pcl::ComparisonOps::GT, 0.0))); //GT表示大于等于
range_condition->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new
pcl::FieldComparison<pcl::PointXYZ>("z", pcl::ComparisonOps::LT, 0.8))); //LT表示小于等于
pcl::ConditionalRemoval<pcl::PointXYZ> condition;
condition.setCondition(range_condition);
condition.setInputCloud(cloud); //输入点云
condition.setKeepOrganized(true);
condition.filter(*cloud_after_Condition);
std::cout << "条件滤波后点云数据点数:" << cloud_after_Condition->points.size() << std::endl;
///****************************************************
///****************************************************
/*方法五:半径滤波器*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_Radius(new pcl::PointCloud<pcl::PointXYZ>);
pcl::RadiusOutlierRemoval<pcl::PointXYZ> radiusoutlier; //创建滤波器
radiusoutlier.setInputCloud(cloud); //设置输入点云
radiusoutlier.setRadiusSearch(100); //设置半径为100的范围内找临近点
radiusoutlier.setMinNeighborsInRadius(2); //设置查询点的邻域点集数小于2的删除
radiusoutlier.filter(*cloud_after_Radius);
std::cout << "半径滤波后点云数据点数:" << cloud_after_Radius->points.size() << std::endl;
///****************************************************
///****************************************************
//点云可视化
pcl::visualization::CloudViewer viewer("cloud_after_PassThrough"); //showCloud 函数是同步的,在此处等待直到渲染显示为止,为窗口命名
viewer.showCloud(cloud_after_PassThrough); //该注册函数在可视化时只调用一次,可视化保存的点云文件cloud_after_PassThrough——可视化处理后的点云
viewer.runOnVisualizationThreadOnce(viewerOneOff); //该注册函数在渲染输出是每次都调用
while (!viewer.wasStopped())
{
}
}
程序运行显示:
可视化直通滤波处理后的点云:
当所处理的点云数据不是随机产生的,而是某一pcd文件时,利用体素化网格法压缩点云,利用半径滤波法和统计滤波法移除离群点。
#include<iostream>
#include<pcl/point_types.h>
#include<pcl/filters/passthrough.h> //直通滤波器头文件
#include<pcl/filters/voxel_grid.h> //体素滤波器头文件
#include<pcl/filters/statistical_outlier_removal.h> //统计滤波器头文件
#include <pcl/filters/conditional_removal.h> //条件滤波器头文件
#include <pcl/filters/radius_outlier_removal.h> //半径滤波器头文件
#include<pcl/io/pcd_io.h> //pcd读写类相关头文件
#include <pcl/visualization/cloud_viewer.h> //
void viewerOneOff(pcl::visualization::PCLVisualizer& viewer) //设置背景颜色
{
viewer.setBackgroundColor(1.0f, 0.7f, 1.0f);
}
int main(int argc, char** argv)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
//
//*打开点云文件
//if (pcl::io::loadPCDFile<pcl::PointXYZ>("C://Users//Administrator//Desktop//test1//Project2//rabbit.pcd", *cloud) == -1)
if (pcl::io::loadPCDFile<pcl::PointXYZ>("E://PCL//readpcd//table_scene_lms400.pcd", *cloud) == -1)
//根据自己pcd文件的路径进行添加
{
PCL_ERROR("Couldn't read file rabbit.pcd\n");
return(-1);
}
std::cout << "原始点云数据点数:" << cloud->points.size() << std::endl << std::endl; //打印所有点到标准错误输出
///****************************************************
/*创建点云数据集。*/
/*
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); //创建一个共享指针,并实例化
cloud->width = 500; //创建点云的数量
cloud->height = 1; //设置点云的高度,其为无序点云
cloud->points.resize(cloud->width*cloud->height);
std::cout << "创建原始点云数据" << std::endl;
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);
}
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;
}
std::cout << "原始点云数据点数:" << cloud->points.size() << std::endl << std::endl; //打印所有点到标准错误输出
*/
///****************************************************
/*方法一:直通滤波器对点云进行处理。*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_PassThrough(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PassThrough<pcl::PointXYZ> passthrough; //设置滤波器对象
passthrough.setInputCloud(cloud);//输入点云
passthrough.setFilterFieldName("z");//对z轴进行操作,也可以对"x"和"y"轴进行操作
passthrough.setFilterLimits(-1.50f, -1.20f);//设置直通滤波器操作范围
//passthrough.setFilterLimitsNegative(true);//true表示保留范围内,false表示保留范围外
passthrough.filter(*cloud_after_PassThrough);//执行滤波,过滤结果保存在 cloud_after_PassThrough
std::cout << "直通滤波后点云数据点数:" << cloud_after_PassThrough->points.size() << std::endl;
///****************************************************
///****************************************************
/*方法二:体素滤波器实现下采样*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_voxelgrid(new pcl::PointCloud<pcl::PointXYZ>);//
pcl::VoxelGrid<pcl::PointXYZ> voxelgrid; //创建滤波对象
voxelgrid.setInputCloud(cloud); //设置需要过滤的点云
voxelgrid.setLeafSize(0.010f, 0.010f, 0.010f);//长宽高
voxelgrid.filter(*cloud_after_voxelgrid); //执行滤波处理后,存储输出cloud_after_voxelgrid
std::cout << "体素化网格方法后点云数据点数:" << cloud_after_voxelgrid->points.size() << std::endl;
///****************************************************
///****************************************************
/*方法三:统计滤波器滤波*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_StatisticalRemoval(new pcl::PointCloud<pcl::PointXYZ>);//
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> Statistical; //创建滤波器对象
Statistical.setInputCloud(cloud); //设置待滤波的点云
Statistical.setMeanK(50);//取平均值的临近点数
Statistical.setStddevMulThresh(1);//设置是否为离群点的阈值:超过平均距离一个标准差以上,该点记为离群点,将其移除
Statistical.filter(*cloud_after_StatisticalRemoval); //执行滤波处理,保存内点到cloud_after_StatisticalRemoval
std::cout << "统计分析滤波后点云数据点数:" << cloud_after_StatisticalRemoval->points.size() << std::endl;
///****************************************************
///****************************************************
/*方法四:条件滤波器*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_Condition(new pcl::PointCloud<pcl::PointXYZ>);
pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_condition(new pcl::ConditionAnd<pcl::PointXYZ>());
range_condition->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new
pcl::FieldComparison<pcl::PointXYZ>("z", pcl::ComparisonOps::GT, 0.0))); //GT表示大于等于
range_condition->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new
pcl::FieldComparison<pcl::PointXYZ>("z", pcl::ComparisonOps::LT, 0.8))); //LT表示小于等于
pcl::ConditionalRemoval<pcl::PointXYZ> condition;
condition.setCondition(range_condition);
condition.setInputCloud(cloud); //输入点云
condition.setKeepOrganized(true);
condition.filter(*cloud_after_Condition);
std::cout << "条件滤波后点云数据点数:" << cloud_after_Condition->points.size() << std::endl;
///****************************************************
///****************************************************
/*方法五:半径滤波器*/
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_Radius(new pcl::PointCloud<pcl::PointXYZ>);
pcl::RadiusOutlierRemoval<pcl::PointXYZ> radiusoutlier; //创建滤波器
radiusoutlier.setInputCloud(cloud); //设置输入点云
radiusoutlier.setRadiusSearch(0.04); //设置半径为100的范围内找临近点
radiusoutlier.setMinNeighborsInRadius(5); //设置查询点的邻域点集数小于2的删除
radiusoutlier.filter(*cloud_after_Radius);
std::cout << "半径滤波后点云数据点数:" << cloud_after_Radius->points.size() << std::endl;
///****************************************************
///****************************************************
//点云可视化
pcl::visualization::CloudViewer viewer("cloud_after_Radius"); //showCloud 函数是同步的,在此处等待直到渲染显示为止,为窗口命名
viewer.showCloud(cloud_after_Radius); //该注册函数在可视化时只调用一次,可视化保存的点云文件cloud_after_PassThrough——可视化处理后的点云
viewer.runOnVisualizationThreadOnce(viewerOneOff); //该注册函数在渲染输出是每次都调用
while (!viewer.wasStopped())
{
}
}
程序运行显示:
原始点云:
体素化下采样图像:
统计滤波处理:
半径滤波处理:
体素化后的半径滤波处理:
体素化后的数据滤波: