点云处理系列 - 点云滤波(二)

1. 为什么要滤波?

通常我们获取的点云数据中包含噪声,噪声会影响点云的特征提取、配准和语义处理。

点云需要处理的主要情况包括:

  • 数据量过大,不易于处理,需要进行下采样

  • 通常由遮挡引起的离群点,需要去除

  • 点云数据的密度不均匀需要平滑

  • 噪声数据需要去除

2. 概述

点云滤波是点云处理的基本步骤,也是进行 high level 三维图像处理之前必须要进行的预处理。其作用类似于信号处理中的滤波,但实现手段却和信号处理不一样。我认为原因有以下几个方面:

  • 点云不是函数,对于复杂三维外形,其x, y, z 之间并非以某种规律或某种数值关系定义。所以点云无法建立横纵坐标之间的联系。

  • 点云在空间中是离散的。和图像,信号不一样,并不定义在某个区域上,无法以某种模板的形式对其进行滤波。换言之,点云没有图像与信号那么明显的定义域。

  • 点云在空间中分布很广泛。遍历整个点云中的每个点,并建立点与点之间相互位置关系成了最大难点。不像图像与信号,可以有迹可循。

  • 点云滤波依赖于几何信息,而不是数值关系。

综上所述,点云滤波只在抽象意义上与信号,图像滤波类似。因为滤波的功能都是突出需要的信息。

实际上点云滤波的手段和传统的信号滤波与图像滤波在自动化程度,滤波效果上还有很大的差距。学者大多关注图像识别与配准算法在点云处理方面的移植,而对滤波算法关注较少。其实点云前处理对测量精度与识别速度都有很大影响。

3. 滤波算法

经典的滤波方法有:直通滤波器,体素滤波器,统计滤波器,条件滤波,半径滤波器,双边滤波,高斯滤波等。

PCL对常规滤波手段均进行了很好的封装。对点云的滤波通过调用各个滤波器对象来完成。主要的滤波器有直通滤波器,体素格滤波器,统计滤波器,半径滤波器 等。不同特性的滤波器构成了较为完整的点云前处理族,并组合使用完成任务。实际上,滤波手段的选择和采集方式是密不可分的。

  • 直通滤波: 如果使用线结构光扫描的方式采集点云,必然物体沿z向分布较广,但x,y向的分布处于有限范围内。此时可使用直通滤波器,确定点云在x或y方向上的范围,可较快剪除离群点,达到第一步粗处理的目的。

  • 体素滤波: 如果使用高分辨率相机等设备对点云进行采集,往往点云会较为密集。过多的点云数量会对后续分割工作带来困难。体素格滤波器可以达到向下采样同时不破坏点云本身几何结构的功能。点云几何结构不仅是宏观的几何外形,也包括其微观的排列方式,比如横向相似的尺寸,纵向相同的距离。随机下采样虽然效率比体素滤波器高,但会破坏点云微观结构。

  • 统计滤波: 统计滤波器用于去除明显离群点(离群点往往由测量噪声引入)。其特征是在空间中分布稀疏,可以理解为:每个点都表达一定信息量,某个区域点越密集则可能信息量越大。噪声信息属于无用信息,信息量较小。所以离群点表达的信息可以忽略不计。考虑到离群点的特征,则可以定义某处点云小于某个密度,既点云无效。计算每个点到其最近的k个点平均距离。则点云中所有点的距离应构成高斯分布。给定均值与方差,可剔除3∑之外的点。

  • 半径滤波: 半径滤波器与统计滤波器相比更加简单粗暴。以某点为中心画一个圆计算落在该圆中点的数量,当数量大于给定值时,则保留该点,数量小于给定值则剔除该点。此算法运行速度快,依序迭代留下的点一定是最密集的,但是圆的半径和圆内点的数目都需要人工指定。

  • 索引滤波: 索引滤波就是设置索引选择区域,可用于超简单的区域初步筛选,算不上真正的滤波算法。

  • 双边滤波: 通过临近采样点加权平均,剔除差异大的点,同时保留边缘信息从而使数据更加平滑。

4. 点云库对滤波算法的实现

点云库中已经包含了上述所有滤波算法。PCL滤波算法的实现是通过滤波器类来完成的,需要实现滤波功能时则新建一个滤波器对象并设置参数。从而保证可以针对不同的滤波任务,使用不同参数的滤波器对点云进行处理。

4.1. 直通滤波器(PassThrough filter):
4.1.1 示例1
// Create the filtering object
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud (cloud);
pass.setFilterFieldName ("z");  //设置过滤时所需要的点云类型的z字段
pass.setFilterLimits (0.0, 1.0);  //设置在过滤字段上的范围
//pass.setFilterLimitsNegative (true);
pass.setFilterLimitsNegative(false); //设置保留范围内的还是过滤掉范围内的
pass.filter (*cloud_filtered);

4.1.2 示例2

#include <iostream>
#include <ctime>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
int main (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);
  //建立3组随机数
  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);
  //滤波字段名被设置为X轴方向
  pass.setFilterFieldName ("x");
  //设置滤波范围
  pass.setFilterLimits (0.0, 1.0);
  //pass.setFilterLimitsNegative (true);
  //执行滤波,保存结果到cloud_filtered
  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);
}


在这里插入图片描述

4.2 体素滤波器(VoxelGrid filter):
4.2.1. 示例1
  // Create the filtering object
  pcl::VoxelGrid<pcl::PCLPointCloud2> sor;   // 创建滤波器对象
  sor.setInputCloud (cloud); //给滤波器对象设置需要过滤的点云
  sor.setLeafSize (0.01f, 0.01f, 0.01f);  //设置滤波时创建的体素大小为1cm立方体
  sor.filter (*cloud_filtered);
4.2.2. 示例2

https://blog.csdn.net/qq_51108184/article/details/122308706


#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/visualization/range_image_visualizer.h> //深度图可视化
#include<pcl/visualization/cloud_viewer.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 ("C:\\Users\\Empower\\Desktop\\exep\\bunny.pcd", *cloud);
      std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height 
      << " data points (" << pcl::getFieldsList (*cloud) << ").";
      // 创建滤波器对象 体素网格下采样
      pcl::VoxelGrid<pcl::PointXYZ> sor;
      //设置输入的点云
      sor.setInputCloud (cloud);
      //点云降采样
      sor.setLeafSize (0.01f, 0.01f, 0.01f);
      //执行滤波,保存结果到cloud_filtered
      sor.filter (*cloud_filtered);
      std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height 
           << " data points (" << pcl::getFieldsList (*cloud_filtered) << ").";
      pcl::PCDWriter writer;
      cout << "sta" << endl;
      //写入文件
      writer.write ("2d.pcd", *cloud_filtered, false);
      pcl::visualization::CloudViewer viewer1("ddd");//创建点云对象
      viewer1.showCloud (cloud_filtered);//显示点云
      cout << "ok" << endl;
      system("pause");
      return (0);
}

在这里插入图片描述

4.3. 统计滤波器(StatisticalOutlierRemoval filter)
4.3.3 示例1
  // Create the filtering object
  pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
  sor.setInputCloud (cloud);
  sor.setMeanK (50);  //每个点到其最近的k个点平均距离
  sor.setStddevMulThresh (1.0);
  sor.filter (*cloud_filtered);

详细实例,详见:

使用StatisticalOutlierRemoval滤波器移除离群点 https://www.pclcn.org/study/shownews.php?lang=cn&id=68

/media/limg/D/Waytous/MyNotes/my-notes-2th/src/PCL/官方教程.md

4.3.3 示例2

https://blog.csdn.net/qq_51108184/article/details/122308706


 #include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/visualization/range_image_visualizer.h> //深度图可视化
#include<pcl/visualization/cloud_viewer.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> ("C:\\Users\\Empower\\Desktop\\exep\\bunny.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);
	  //设置判断是否为离群点的阀值
	  sor.setStddevMulThresh (1.0);
	  //执行滤波,保存结果到cloud_filtered
	  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);
	 // 然后,使用同样的参数再次调用该滤波器,但是利用函数 setNegative 设置使输出取外点,以获取离群点数据 也就是原本滤除掉的点 。
	  sor.setNegative (true);
	  //执行滤波,保存结果到cloud_filtered
	  sor.filter (*cloud_filtered);
	  writer.write<pcl::PointXYZ> ("table_scene_lms400_outliers.pcd", *cloud_filtered, false);
	  pcl::visualization::CloudViewer viewer1("ddd");//创建点云对象
	  viewer1.showCloud(cloud_filtered);//显示点云
	  return (0);
}

在这里插入图片描述

4.4 半径滤波器(Radius filter):

RadiusOutlierRemoval背景知识
如图1所示,有助于形象化理解RadiusOutlierRemoval的作用,在点云数据中,用户指定每个的点一定范围内周围至少要有足够多的近邻。例如,如果指定至少要有1个邻居,只有黄色的点会被删除,如果指定至少要有2个邻居,黄色和绿色的点都将被删除。

在这里插入图片描述

4.4.1 示例1
// build the filter
    pcl::RadiusOutlierRemoval<pcl::PointXYZ> outrem;
    outrem.setInputCloud(cloud);
    outrem.setRadiusSearch(0.8);
    outrem.setMinNeighborsInRadius (2);
    // apply filter
    outrem.filter (*cloud_filtered);

显然,不同的滤波器在滤波过程中,总是先创建一个对象,再设置对象参数,最后调用滤波函数对点云进行处理(点云为智能指针指向的一块区域)

4.5 投影滤波

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/ModelCoefficients.h>
#include <pcl/filters/project_inliers.h>
int
 main (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);
  //是指输入点云
  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);
}



在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Adunn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值