在获取点云数据时,由于设备精度、操作者经验、环境因素等带来的影响,点云数据中不可避免的出现了一些噪声点(就是自己用不到的点),我们在处理数据时,需要把这些点去除掉,这个过程叫滤波。
一、pcl中的点云滤波方案
(1)点云数据密度不规则需要平滑
(2)因为遮挡等问题造成离群点需要去除
(3)大量数据需要进行下采样(Downsample)。
(4)噪声数据需要去除。
对应方法如下:
(1)按具体给定的规则限制过滤去除点。
(2)通过常用滤波算法修改点的部分属性。
(3)对数据进行下采样。
直通滤波器:对于在空间分布有一定空间特征的点云数据,比如使用线结构光扫描的方式采集点云,沿z向分布较广,但x,y向的分布处于有限范围内。此时可使用直通滤波器,确定点云在x或y方向上的范围,可较快剪除离群点,达到第一步粗处理的目的。
#include
#include
#include //直通滤波器头文件
using namespace std;
int main()
{
///////****************************************************////////////////////
/*创建点云数据集。*/
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轴进行操作
passthrough.setFilterLimits(0.0, 400.0);//设置直通滤波器操作范围
//passthrough.setFilterLimitsNegative(true);//true表示保留范围内,false表示保留范围外
passthrough.filter(*cloud_after_PassThrough);//执行滤波,过滤结果保存在 cloud_after_PassThrough
std::cout << "直通滤波后点云数据点数:" << cloud_after_PassThrough->points.size() << std::endl;
std::cerr<<"cloud afer filtering:"<<std::endl;
for(size_i=0;i<cloud_after_PassThrough->points.size();++i)
{
std::cerr<<""<<cloud_after_PassThrough->points[i].x<<" "
<<cloud_after_PassThrough->points[i].y<<" "
<<cloud_after_PassThrough->points[i].z<<" " <<std::endl;
}
}
体素的概念类似于像素,使用AABB包围盒将点云数据体素化,一般体素越密集的地方信息越多,噪音点及离群点可通过体素网格去除。另一方面如果使用高分辨率相机等设备对点云进行采集,往往点云会较为密集。过多的点云数量会对后续分割工作带来困难。体素滤波器可以达到向下采样同时不破坏点云本身几何结构的功能。
#include
#include
#include //体素滤波器头文件
using namespace std;
int main()
{
///////****************************************************////////////////////
/*创建点云数据集。*/
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_voxelgrid(new pcl::PointCloud<pcl::PointXYZ>);//
pcl::VoxelGrid<pcl::PointXYZ> voxelgrid;
voxelgrid.setInputCloud(cloud);//输入点云数据
voxelgrid.setLeafSize(10.0f, 10.0f, 10.0f);//AABB长宽高
voxelgrid.filter(*cloud_after_voxelgrid);
std::cout << "体素化网格方法后点云数据点数:" << cloud_after_voxelgrid->points.size() << std::endl;
std::cerr<<"cloud afer filtering:"<<std::endl;
for(size_i=0;i<ccloud_after_voxelgrid->points.size();++i)
{
std::cerr<<""<<cloud_after_voxelgrid->points[i].x<<" "
<<cloud_after_voxelgrid->points[i].y<<" "
<<cloud_after_voxelgrid->points[i].z<<" " <<std::endl;
}
}
使用统计分析技术,从一个点云数据中集中移除测量噪声点(也就是离群点)比如:激光扫描通常会产生密度不均匀的点云数据集,另外测量中的误差也会产生稀疏的离群点,使效果不好,估计局部点云特征(例如采样点处法向量或曲率变化率)的运算复杂,这会导致错误的数值,反过来就会导致点云配准等后期的处理失败.
解决办法:每个点的邻域进行一个统计分析,并修剪掉一些不符合一定标准的点,稀疏离群点移除方法基于在输入数据中对点到临近点的距离分布的计算,对每一个点,计算它到它的所有临近点的平均距离,,假设得到的结果是一个高斯分布,其形状是由均值和标准差决定,平均距离在标准范围之外的点,可以被定义为离群点并可从数据中去除。
补充:高斯分布又名正态分布
#include
#include
#include
#include
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>("bunny.pcd", *cloud);//读取需要的文件
std::cout << "Cloud before filtering: " << std::endl;
std::cout << *cloud << std::endl;
// 创建滤波器,对每个点分析的临近点的个数设置为50 ,并将标准差的倍数设置为1 这意味着如果一
//个点的距离超出了平均距离一个标准差以上,则该点被标记为离群点,并将它移除,存储起来
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor; //创建滤波器对象
sor.setInputCloud(cloud); //设置待滤波的点云
sor.setMeanK(50); //设置在进行统计时考虑查询点临近点数
//sor.setStddevMulThresh(1.0); //设置判断是否为离群点的阀值
sor.setStddevMulThresh(0.00034); //设置判断是否为离群点的阀值
sor.filter(*cloud_filtered); //存储
std::cerr << "Cloud after filtering: " << std::endl;
std::cerr << *cloud_filtered << std::endl;
pcl::PCDWriter writer;
writer.write<pcl::PointXYZ>("test1_inliers.pcd", *cloud_filtered, false);
sor.setNegative(true);
sor.filter(*cloud_filtered);
writer.write<pcl::PointXYZ>("test1_outliers.pcd", *cloud_filtered, false);
return (0);
}
条件滤波器通过设定滤波条件进行滤波,有点分段函数的味道,当点云在一定范围则留下,不在则舍弃。
#include
#include
#include //条件滤波器头文件
using namespace std;
int main()
{
///////****************************************************////////////////////
/*创建点云数据集。*/
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_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;
std::cerr<<"cloud afer filtering:"<<std::endl;
for(size_i=0;i<cloud_after_Condition->points.size();++i)
{
std::cerr<<""<<cloud_after_Condition->points[i].x<<" "
<<cloud_after_Condition->points[i].y<<" "
<<cloud_after_Condition->points[i].z<<" " <<std::endl;
}
}
半径滤波器与统计滤波器相比更加简单粗暴。以某点为中心画一个圆计算落在该圆中点的数量,当数量大于给定值时,则保留该点,数量小于给定值则剔除该点。此算法运行速度快,依序迭代留下的点一定是最密集的,但是圆的半径和圆内点的数目都需要人工指定。
#include
#include
#include //条件滤波器头文件
using namespace std;
int main()
{
///////****************************************************////////////////////
/*创建点云数据集。*/
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_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;
std::cerr<<"cloud afer filtering:"<<std::endl;
for(size_i=0;i<cloud_after_Radius->points.size();++i)
{
std::cerr<<""<<cloud_after_Radius->points[i].x<<" "
<<cloud_after_Radius->points[i].y<<" "
<<cloud_after_Radius->points[i].z<<" " <<std::endl;
}
}
以上的5种方法是最基本的常用的五种滤波方法,之后遇到更好的方法会补充。