PCL点云库入门——PCL库点云滤波算法之统计滤波(StatisticalOutlierRemoval)

1、算法原理

统计滤波算法是一种利用统计学原理对点云数据进行处理的方法。它主要通过计算点云中每个点的统计特性,如均值、方差等,来决定是否保留该点。算法首先会设定一个统计阈值,然后对点云中的每个点进行分析。如果一个点的统计特性与周围点的统计特性相差不大,即认为该点是可靠的,并将其保留;反之,如果一个点的统计特性与周围点相比差异较大,那么这个点可能是一个异常点或噪声点,算法会将其移除。通过这种方式,统计滤波算法能够有效地减少数据中的噪声和异常值,提高点云数据的质量。这种算法特别适用于需要对数据进行精细处理的场合,例如在高精度建模和测量中。由于其能够提供较为精确的数据处理结果,统计滤波算法在点云数据处理领域中也占有一席之地。算法的实现步骤如下:

**第1步:**创建点云数据的拓扑结构,如KDtree或Octree。

**第2步:**计算每一个点的K邻域集合。

**第3步:**计算每个点与其K邻域的距离dij的均值,其中i=[1,....,m] 表示点云数目,j=[1,....,k] 点的K邻域点。

**第4步:**计算点云数据高斯分布模型参数d~N(u,α),距离的均值u,距离的标准差α,具体公式内容如下:

**第5步:**计算统计滤波的阈值。

dt= u+ S*α ; ​​​

**第6步:**遍历所有点,如果其距离的均值大于指定置阈值,则为外点,将会被移除。

2、主要成员函数和变量

1.主要的成员变量

1)、 构建点云空间拓扑的方法,如KDtree和Octree

cpp 复制代码
       SearcherPtr searcher_;

2)、搜索点K邻域的个数,用于计算点的平均距离信息

cpp 复制代码
       int mean_k_;

3)、标准差修正因子

cpp 复制代码
       double std_mul_;

2.主要的成员函数

1)、设置搜索点K邻域的个数,用于计算点的平均距离,可以根据直接点云数据分布情况设置一般值15、30、50等。

cpp 复制代码
         inline void setMeanK (int nr_k);

2)、标准差修正因子 ,如0.1、0.3和0.5等。

cpp 复制代码
         inline void setStddevMulThresh (double stddev_mult)

3、主要实现代码注解

cpp 复制代码
template <typename PointT> void
pcl::StatisticalOutlierRemoval<PointT>::applyFilterIndices (std::vector<int> &indices)
{
  // 初始化构建点云拓扑关系方法
  if (!searcher_)
  {
    if (input_->isOrganized ())//有序点云
      searcher_.reset (new pcl::search::OrganizedNeighbor<PointT> ());
    else //无序点云数据用KD树方法
      searcher_.reset (new pcl::search::KdTree<PointT> (false));
  }
  searcher_->setInputCloud (input_);


  // 初始化需要的参数,K个邻域的索引集、距离集;所有点云数据的距离集
  std::vector<int> nn_indices (mean_k_);
  std::vector<float> nn_dists (mean_k_);
  std::vector<float> distances (indices_->size ());
  //内点、外点索引集初始化
  indices.resize (indices_->size ());
  removed_indices_->resize (indices_->size ());
  int oii = 0, rii = 0;  // oii = output indices iterator, rii = removed indices iterator


  //第一步:计算所有点相对于k个最近邻域点的平均距离
  int valid_distances = 0;
  for (int iii = 0; iii < static_cast<int> (indices_->size ()); ++iii)  // iii = input indices iterator
  {
      //无效点判断
    if (!std::isfinite (input_->points[(*indices_)[iii]].x) ||
        !std::isfinite (input_->points[(*indices_)[iii]].y) ||
        !std::isfinite (input_->points[(*indices_)[iii]].z))
    {
        //无序点的距离为0
      distances[iii] = 0.0;
      continue;
    }


    // P执行K个邻域点搜索
    if (searcher_->nearestKSearch ((*indices_)[iii], mean_k_ + 1, nn_indices, nn_dists) == 0)
    {
      distances[iii] = 0.0;
      PCL_WARN ("[pcl::%s::applyFilter] Searching for the closest %d neighbors failed.\n", getClassName ().c_str (), mean_k_);
      continue;
    }


    // 计算点与K个邻域点之间距离的平均值
    double dist_sum = 0.0;
    for (int k = 1; k < mean_k_ + 1; ++k)  // k = 0 is the query point
      dist_sum += sqrt (nn_dists[k]);
    //将距离均值信息保存到对应索引值中  
    distances[iii] = static_cast<float> (dist_sum / mean_k_);
    valid_distances++;
  }


  // 估计距离的均值和标准差(u,a)
  double sum = 0, sq_sum = 0;
  for (const float &distance : distances)
  {
    sum += distance;
    sq_sum += distance * distance;
  }
  //均值
  double mean = sum / static_cast<double>(valid_distances);
  //方差---》前半部分为母体方差,
  double variance = (sq_sum - sum * sum / static_cast<double>(valid_distances)) / (static_cast<double>(valid_distances) - 1);
  //标准差
  double stddev = sqrt (variance);
  //getMeanStd (distances, mean, stddev);
  // 计算外点阈值 ,等于=均值+标准差的修正系数
  double distance_threshold = mean + std_mul_ * stddev;


  // 第二步:根据计算的距离阈值和每个点的距离值(分辨率)对点进行分类(内点和外点)
  for (int iii = 0; iii < static_cast<int> (indices_->size ()); ++iii)  // iii = input indices iterator
  {
    //平均距离太高的点是离群值,传递给移除的索引
    if ((!negative_ && distances[iii] > distance_threshold) || (negative_ && distances[iii] <= distance_threshold))
    {
      if (extract_removed_indices_)
        (*removed_indices_)[rii++] = (*indices_)[iii];
      continue;
    }


    // Otherwise it was a normal point for output (inlier)
    indices[oii++] = (*indices_)[iii];
  }


  // Resize the output arrays
  indices.resize (oii);
  removed_indices_->resize (rii);
}

4、代码示例

cpp 复制代码
/*****************************************************************//**
* \file   PCLPCLRadiusOutliermain.cpp
* \brief
*
* \author YZS
* \date   January 2025
*********************************************************************/
#include<iostream>
#include <vector>
#include <ctime>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/auto_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/statistical_outlier_removal.h>
using namespace std;


void PCLStatisticalOutlier()
{
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new  pcl::PointCloud<pcl::PointXYZ>());
	std::string fileName = "E:/PCLlearnData/11/table.pcd";
	pcl::io::load(fileName, *cloud);
	std::cout << "Cloud Size:" << cloud->points.size() << std::endl;
	//保存滤波后的结果点云
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloudFilter(new  pcl::PointCloud<pcl::PointXYZ>());

	//统计滤波算法
	pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sorFilter;// 统计滤波器对象
	sorFilter.setInputCloud(cloud);
	sorFilter.setMeanK(30);//设置搜索点K邻域的个数
	sorFilter.setStddevMulThresh(0.3);//设置标准差修正因子 ,如0.1、0.3和0.5等
	
	sorFilter.filter(*cloudFilter); // 执行滤波,并且保存结果到cloudFilter中
	std::cout << "filter Cloud Size:" << cloudFilter->points.size() << std::endl;
	//结果可视化
// PCLVisualizer对象
	pcl::visualization::PCLVisualizer viewer("FilterVIS");
	//创建左右窗口的ID v1和v2
	int v1(0);
	int v2(1);
	//设置V1窗口尺寸和背景颜色
	viewer.createViewPort(0.0, 0.0, 0.5, 1, v1);
	viewer.setBackgroundColor(0, 0, 0, v1);
	//设置V2窗口尺寸和背景颜色
	viewer.createViewPort(0.5, 0.0, 1, 1, v2);
	viewer.setBackgroundColor(0.1, 0.1, 0.1, v2);
	// 添加2d文字标签
	viewer.addText("v1", 10, 10, 20, 1, 0, 0, "Txtv1", v1);
	viewer.addText("v2", 10, 10, 20, 0, 1, 0, "Txtv2", v2);
	//设置cloud1的渲染属性,点云的ID和指定可视化窗口v1
	viewer.addPointCloud(cloud, "cloud1", v1);
	viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud1");
	//设置cloud2的渲染属性,点云的ID和指定可视化窗口v2
	viewer.addPointCloud(cloudFilter, "cloud2", v2);
	viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud2");
	// 可视化循环主体
	while (!viewer.wasStopped())
	{
		viewer.spinOnce();
	}
}
int main(int argc, char* argv[])
{
	PCLStatisticalOutlier();
	std::cout << "Hello World!" << std::endl;
	std::system("pause");
	return 0;
}

结果:

5、统计滤波算法缺点

1)、计算每一个点的邻域集合时,可能会因为数据集的复杂性导致邻域集合的计算不准确,从而影响后续的距离计算和滤波效果。

2)、在计算每个点与其邻域的距离dij时,如果点云数据中存在噪声或异常值,这些异常值可能会扭曲距离的计算,导致滤波结果不理想。

3)、高斯分布模型参数的计算依赖于距离的均值u和标准差α,如果数据集中的离群值较多,可能会使得均值和标准差的估计不准确,进而影响到离群值的识别和删除。

4)、遍历所有点并移除外点的过程中,如果设定的置信度阈值过高或过低,可能会导致过多的正常点被错误地识别为离群点而被移除,或者真正的离群点没有被有效识别,从而影响数据集的质量。

至此完成第十二节PCL库点云滤波算法之统计滤波(StatisticalOutlierRemoval) 学习,下一节我们将进入**《PCL库中点云特征》**的学习。

相关推荐
m0_6759882324 分钟前
Leetcode731. 我的日程安排表 II
python·算法·leetcode
闻缺陷则喜何志丹1 小时前
【C++动态规划】3144. 分割字符频率相等的最少子字符串|1917
c++·算法·动态规划·力扣·分割·子字符串·最少
想要AC的sjh1 小时前
【Leetcode】732. 我的日程安排表 III
c++·算法·leetcode·职场和发展
sophiaRachel1 小时前
1.1.3 插入排序
数据结构·算法·排序算法
不去幼儿园2 小时前
【强化学习】Double DQN(Double Deep Q-Network)算法
人工智能·算法·机器学习·强化学习·马尔科夫决策
今晚打老虎2 小时前
c++第13课
数据结构·c++·算法
haaaaaaarry2 小时前
快速排序排序方法演示及算法分析(附代码和实例)
算法·排序算法
慌糖2 小时前
关于数组的一些应用--------数组作函数的返回值(斐波那契数列数列的实现)
算法
Ritsu栗子2 小时前
代码随想录算法训练营day21
c++·算法
对,就是哥2 小时前
ABAP弹出对对话框错误信息设计
java·数据库·算法