在获取点云数据时 ,由于设备精度,操作者经验环境因素带来的影响,以及电磁波的衍射特性,被测物体表面性质变化和数据拼接配准操作过程的影响,点云数据中讲不可避免的出现一些噪声。在点云处理流程中滤波处理作为预处理的第一步,对后续的影响比较大,只有在滤波预处理中将噪声点 ,离群点,孔洞,数据压缩等按照后续处理定制,才能够更好的进行配准,特征提取,曲面重建,可视化等后续应用处理,PCL中点云滤波模块提供了很多灵活实用的滤波处理算法,例如:双边滤波,高斯滤波,条件滤波,直通滤波,基于随机采样一致性滤波。双边滤波算法是通过取临近采样点和加权平均来修正当前采样点的位置,从而达到滤波效果,同时也会有选择剔除与当前采样点“差异”太大的相邻采样点,从而保持原特征的目的。
PCL中总结了几种需要进行点云滤波处理情况,这几种情况分别如下:
(1) 点云数据密度不规则需要平滑
(2) 因为遮挡等问题造成离群点需要去除
(3) 大量数据需要下采样
(4) 噪声数据需要去除
对应的方案如下:
(1)按照给定的规则限制过滤去除点
(2) 通过常用滤波算法修改点的部分属性
(3)对数据进行下采样
下面将分别举例:
(1)在PCL 中使用直通滤波器对点云进行滤波处理
#include "pch.h"
#include
#include
#include
int
main(int argc, char** argv)
{
pcl::PointCloud::Ptr cloud(new pcl::PointCloud);
pcl::PointCloud::Ptr cloud_filtered(new pcl::PointCloud);
//生成并填充点云
cloud->width = 50;
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 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;
/************************************************************************************
创建直通滤波器的对象,设立参数,滤波字段名被设置为Z轴方向,可接受的范围为(0.0,1.0)
即将点云中所有点的Z轴坐标不在该范围内的点过滤掉或保留,这里是过滤掉,由函数setFilterLimitsNegative设定
***********************************************************************************/
// 设置滤波器对象
pcl::PassThrough pass;
pass.setInputCloud(cloud); //设置输入点云
pass.setFilterFieldName("z"); //设置过滤时所需要点云类型的Z字段
pass.setFilterLimits(0.0, 1000.0); //设置在过滤字段的范围
//pass.setFilterLimitsNegative (true); //设置保留范围内还是过滤掉范围内
pass.filter(*cloud_filtered); //执行滤波,保存过滤结果在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);
}
(2)使用VoxelGrid滤波器对点云进行下采样
使用体素化网格方法实现下采样,即减少点的数量 减少点云数据,并同时保存点云的形状特征,在提高配准,曲面重建,形状识别等算法速度中非常实用,PCL是实现的VoxelGrid类通过输入的点云数据创建一个三维体素栅格,容纳后每个体素内用体素中所有点的重心来近似显示体素中其他点,这样该体素内所有点都用一个重心点最终表示,对于所有体素处理后得到的过滤后的点云,这种方法比用体素中心逼近的方法更慢,但是对于采样点对应曲面的表示更为准确。
#include "pch.h"
#include
#include
#include
#include
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("bunny.pcd", *cloud); //读取点云到cloud中
std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height
<< " data points (" << pcl::getFieldsList(*cloud) << ").";
/******************************************************************************
创建一个叶大小为1cm的pcl::VoxelGrid滤波器,
**********************************************************************************/
pcl::VoxelGrid 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);
}
(4)使用statisticalOutlierRemoval滤波器移除离群点
使用统计分析技术,从一个点云数据中集中移除测量噪声点(也就是离群点)比如:激光扫描通常会产生密度不均匀的点云数据集,另外测量中的误差也会产生稀疏的离群点,使效果不好,估计局部点云特征(例如采样点处法向量或曲率变化率)的运算复杂,这会导致错误的数值,反过来就会导致点云配准等后期的处理失败。
解决办法:每个点的邻域进行一个统计分析,并修剪掉一些不符合一定标准的点,稀疏离群点移除方法基于在输入数据中对点到临近点的距离分布的计算,对每一个点,计算它到它的所有临近点的平均距离,,假设得到的结果是一个高斯分布,其形状是由均值和标准差决定,平均距离在标准范围之外的点,可以被定义为离群点并可从数据中去除。
#include "pch.h"
#include
#include
#include
#include
int main(int argc, char** argv)
{
pcl::PointCloud::Ptr cloud(new pcl::PointCloud);
pcl::PointCloud::Ptr cloud_filtered(new pcl::PointCloud);
// 定义读取对象
pcl::PCDReader reader;
// 读取点云文件
reader.read("bunny.pcd", *cloud);
std::cerr << "Cloud before filtering: " << std::endl;
std::cerr << *cloud << std::endl;
// 创建滤波器,对每个点分析的临近点的个数设置为50 ,并将标准差的倍数设置为1 这意味着如果一
//个点的距离超出了平均距离一个标准差以上,则该点被标记为离群点,并将它移除,存储起来
pcl::StatisticalOutlierRemoval 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("test1_inliers.pcd", *cloud_filtered, false);
sor.setNegative(true);
sor.filter(*cloud_filtered);
writer.write("test1_outliers.pcd", *cloud_filtered, false);
return (0);
}
运行结果
( 2)使用参数化模型投影点云
如何将点投影到一个参数化模型上(平面或者球体等),参数化模型通过一组参数来设定,对于平面来说使用其等式形式.在PCL中有特意存储常见模型系数的数据结构。
#include "pch.h"
#include
#include
#include
#include //模型系数头文件
#include //投影滤波类头文件
int main(int argc, char** argv)
{
pcl::PointCloud::Ptr cloud(new pcl::PointCloud);
pcl::PointCloud::Ptr cloud_projected(new pcl::PointCloud);
//创建点云并打印出来
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;
// 填充ModelCoefficients的值,使用ax+by+cz+d=0平面模型,其中 a=b=d=0,c=1 也就是X——Y平面
//定义模型系数对象,并填充对应的数据
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;
// 创建ProjectInliers对象,使用ModelCoefficients作为投影对象的模型参数
pcl::ProjectInliers proj; //创建投影滤波对象
proj.setModelType(pcl::SACMODEL_PLANE); //设置对象对应的投影模型
proj.setInputCloud(cloud); //设置输入点云
proj.setModelCoefficients(coefficients); //设置模型对应的系数
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);
}
运行时候可能会报错“Error C4996 'pcl::SAC_SAMPLE_SIZE': This map is deprecated and is kept only to prevent breaking existing user code. Starting from PCL 1.8.0 model sample size is a protected member of the SampleConsensusModel class ”
解决方法看这里:https://blog.csdn.net/daidaiisdaidai/article/details/84430154
(5)从一个点云中提取索引
本例将讲解如何使用一个,基于某一分割算法提取点云中的一个子集。
#include "pch.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
int
main(int argc, char** argv)
{
/**********************************************************************************************************
从输入的.PCD 文件载入数据后,创建一个VOxelGrid滤波器对数据进行下采样,在这里进行下才样是为了加速处理过程,
越少的点意味着分割循环中处理起来越快
**********************************************************************************************************/
pcl::PCLPointCloud2::Ptr cloud_blob(new pcl::PCLPointCloud2), cloud_filtered_blob(new pcl::PCLPointCloud2);//申明滤波前后的点云
pcl::PointCloud::Ptr cloud_filtered(new pcl::PointCloud), cloud_p(new pcl::PointCloud), cloud_f(new pcl::PointCloud);
// 读取PCD文件
pcl::PCDReader reader;
reader.read("test1.pcd", *cloud_blob);
//统计滤波前的点云个数
std::cerr << "PointCloud before filtering: " << cloud_blob->width * cloud_blob->height << " data points." << std::endl;
// 创建体素栅格下采样: 下采样的大小为1cm
pcl::VoxelGrid sor; //体素栅格下采样对象
sor.setInputCloud(cloud_blob); //原始点云
//sor.setLeafSize(0.01f, 0.01f, 0.01f); // 设置采样体素大小
sor.setLeafSize(3.0f, 3.0f, 3.0f); // 设置采样体素大小
sor.filter(*cloud_filtered_blob); //保存
// 转换为模板点云
pcl::fromPCLPointCloud2(*cloud_filtered_blob, *cloud_filtered);
std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height << " data points." << std::endl;
// 保存下采样后的点云
pcl::PCDWriter writer;
writer.write("test1_downsampled.pcd", *cloud_filtered, false);
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients());
pcl::PointIndices::Ptr inliers(new pcl::PointIndices());
pcl::SACSegmentation seg; //创建分割对象
seg.setOptimizeCoefficients(true); //设置对估计模型参数进行优化处理
seg.setModelType(pcl::SACMODEL_PLANE); //设置分割模型类别
seg.setMethodType(pcl::SAC_RANSAC); //设置用哪个随机参数估计方法
seg.setMaxIterations(1000); //设置最大迭代次数
seg.setDistanceThreshold(0.01); //判断是否为模型内点的距离阀值
// 设置ExtractIndices的实际参数
pcl::ExtractIndices extract; //创建点云提取对象
int i = 0, nr_points = (int)cloud_filtered->points.size();
// While 30% of the original cloud is still there
while (cloud_filtered->points.size() > 0.3 * nr_points)
{
// 为了处理点云包含的多个模型,在一个循环中执行该过程并在每次模型被提取后,保存剩余的点进行迭代
seg.setInputCloud(cloud_filtered);
seg.segment(*inliers, *coefficients);
if (inliers->indices.size() == 0)
{
std::cerr << "Could not estimate a planar model for the given dataset." << std::endl;
break;
}
// Extract the inliers
extract.setInputCloud(cloud_filtered);
extract.setIndices(inliers);
extract.setNegative(false);
extract.filter(*cloud_p);
std::cerr << "PointCloud representing the planar component: " << cloud_p->width * cloud_p->height << " data points." << std::endl;
std::stringstream ss;
ss << "test1_plane_" << i << ".pcd";
writer.write(ss.str(), *cloud_p, false);
// Create the filtering object
extract.setNegative(true);
extract.filter(*cloud_f);
cloud_filtered.swap(cloud_f);
i++;
}
return (0);
}
运行结果
(6)使用ConditionalRemoval 或RadiusOutlinerRemoval移除离群点
如何在滤波模块使用几种不同的方法移除离群点,对于ConditionalRemoval滤波器,可以一次删除满足对输入的点云设定的一个或多个条件指标的所有的数据点,RadiusOutlinerRemoval滤波器,它可以删除在输入点云一定范围内没有至少达到足够多近邻的所有数据点。
关于RadiusOutlinerRemoval的理解,在点云数据中,设定每个点一定范围内周围至少有足够多的近邻,不满足就会被删除
关于ConditionalRemoval 这个滤波器删除点云中不符合用户指定的一个或者多个条件的数据点
#include "pch.h"
#include
#include
#include
#include
int
main (int argc, char** argv)
{
if (argc != 2) //确保输入的参数
{
std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
exit(0);
}
pcl::PointCloud::Ptr cloud (new pcl::PointCloud);
pcl::PointCloud::Ptr cloud_filtered (new pcl::PointCloud);
//填充点云
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);
}
if (strcmp(argv[1], "-r") == 0){
pcl::RadiusOutlierRemoval outrem; //创建滤波器
outrem.setInputCloud(cloud); //设置输入点云
outrem.setRadiusSearch(0.8); //设置半径为0.8的范围内找临近点
outrem.setMinNeighborsInRadius (2); //设置查询点的邻域点集数小于2的删除
// apply filter
outrem.filter (*cloud_filtered); //执行条件滤波 在半径为0.8 在此半径内必须要有两个邻居点,此点才会保存
}
else if (strcmp(argv[1], "-c") == 0){
//创建条件限定的下的滤波器
pcl::ConditionAnd::Ptr range_cond (new
pcl::ConditionAnd ()); //创建条件定义对象
//为条件定义对象添加比较算子
range_cond->addComparison (pcl::FieldComparison::ConstPtr (new
pcl::FieldComparison ("z", pcl::ComparisonOps::GT, 0.0))); //添加在Z字段上大于0的比较算子
range_cond->addComparison (pcl::FieldComparison::ConstPtr (new
pcl::FieldComparison ("z", pcl::ComparisonOps::LT, 0.8))); //添加在Z字段上小于0.8的比较算子
// 创建滤波器并用条件定义对象初始化
pcl::ConditionalRemoval condrem;
condrem.setCondition (range_cond);
condrem.setInputCloud (cloud); //输入点云
condrem.setKeepOrganized(true); //设置保持点云的结构
// 执行滤波
condrem.filter (*cloud_filtered); //大于0.0小于0.8这两个条件用于建立滤波器
}
else{
std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
exit(0);
}
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;
// display pointcloud after filtering
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);
}
从中可以看出ConditionalRemoval 或RadiusOutlinerRemoval的区别
RadiusOutlinerRemoval比较适合去除单个的离群点 ConditionalRemoval 比较灵活,可以根据用户设置的条件灵活过滤
参考博文地址:https://www.cnblogs.com/li-yao7758258/p/6445831.html