前言
通常从传感器(3D相机、雷达)中获取到的点云存在噪点(杂点、离群点、孤岛点等各种叫法)。噪点产生的原因有不同,可能是扫描到了不想要扫描的物体,可能是待测工件表面反光形成的,也可能是相机内部的原因。在进行其他算法处理之前,通常需要先去除噪声,避免带来干扰。去噪的方法有很多,效率和效果也是各不相同,应用场景也不太一样,本篇内容就是想要将不同的去噪方法进行归纳。
环境:
Windows + VS2019 + PCL1.11.1
在开始之前,先贴出用于测试的点云,可以看出来空间中漂浮了很多噪点。原始点云总数为5882482个点,x和y方向间距为0.05。
1.半径滤波
PCL中集成有半径滤波,可以用于噪点的去除。主要需要设定两个参数,一个是搜索半径,另一个是在搜索半径内近邻点的最小数量。这两个参数需要根据点云的x,y,z方向上的点间距来设定(或者说分辨率)。一般使用线扫相机或者结构光相机这类3D相机得到的点在x,y方向上间距都比较均匀。假设点间距是0.05,那么将搜索半径设置为0.1,则搜索半径内理论上在上下左右方向上分别有2个点,共8个点,所以近邻点最起码也有8个。
头文件:
#include <pcl/filters/radius_outlier_removal.h>
代码如下:
/// <summary>
/// 使用半径滤波进行点云噪点去除
/// </summary>
/// <param name="cloud">输入点云</param>
/// <param name="cloud_out">输出除噪后的点云</param>
/// <param name="radius">搜索半径</param>
/// <param name="neighbors">单个搜索内近邻点数</param>
/// <returns>return true表示去噪成功,return false表示失败</returns>
bool NoiseRemoveROR(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_out, const float& radius,
const int& neighbors)
{
if (cloud == nullptr) return false;
if (cloud->points.size() < 10) return false;
pcl::RadiusOutlierRemoval<pcl::PointXYZ> rorfilter(true); // 初始化为true以获取被移除的索引
rorfilter.setInputCloud(cloud);
rorfilter.setRadiusSearch(radius); // 设置搜索半径
rorfilter.setMinNeighborsInRadius(neighbors); // 设置搜索半径内满足的点数
//rorfilter.setUserFilterValue(1); // 将不符合要求的点更改为新的值,而不是去去除它们,与setKeepOrganized一起使用
//rorfilter.setKeepOrganized(false); // 保证有序点云的结构,与setUserFilterValue一起使用
rorfilter.filter(*cloud_out); // 进行离群点去除
return true;
}
一般近邻点的数量设置与搜索半径有直接关系。而经过测试发现,搜索的近邻点数量需要越多则耗时越长。较小的搜索半径只需要设置较小数量的近邻点,但是这样可能会导致有的噪点去除不干净。稍大一点的搜索半径以及近邻点数量适当调大可以得到更稳定的去噪效果。因此,效果和效率之间需要找到一个平衡点。
测试及结果:搜索半径设置为0.2,近邻点数量为20,耗时10.94s。效果如下:
针对于半径滤波耗时问题其实可以将点云使用直通滤波分成若干份,然后使用OpenMP对这几份点云并行处理进行半径滤波,将结果再合并成一个点云,这个方法可能能够起到加速的作用。
有一篇论文《Fast Radius Outlier Filter Variant for Large Point Clouds》,更快速的半径滤波,还未找到源码,若有人找到请联系我,感谢。
2.统计学滤波
统计学滤波也是一种比较常见的去噪算法。其官方解释如下:
The statistical outlier removal process is a bit more refined. First, for every point, the mean distance to its K neighbors is computed. Then, if we asume that the result is a normal (gaussian) distribution with a mean μ and a standard deviation σ, we can deem it safe to remove all points with mean distances that fall out of the global mean plus deviation. Basically, it runs a statistical analysis of the distances between neighboring points, and trims all which are not considered “normal” (you define what “normal” is with the parameters of the algorithm).
个人理解就是遍历点云,对于每个点,都先搜索与其最相近的k个点,计算这k个点与该点的距离,并得到一个平均距离和一个平均距离的标准差。然后比较距离是否是大于μ+stddev*σ,如果大于则表示是离群点。
头文件:#include <pcl/filters/statistical_outlier_removal.h>
代码如下:
/// <summary>
/// 使用PCL中集成的统计学滤波进行去噪
/// </summary>
/// <param name="cloud">输入点云</param>
/// <param name="cloud_out">输出点云</param>
/// <param name="k">搜索近邻点的个数</param>
/// <param name="stddev">平均距离标准差的乘数</param>
/// <returns>return true表示去噪成功,return false表示失败</returns>
bool NoiseRemoveSOR(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_out, const int& k, const double& stddev)
{
if (cloud == nullptr) return false;
if (cloud->points.size() < 10) return false;
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> filter; // Filter object.
filter.setInputCloud(cloud);
filter.setMeanK(k); // Set number of neighbors to consider to K.
filter.setStddevMulThresh(stddev); // Points with a distance larger than stddev standard deviation of the mean distance will be outliers.
filter.filter(*cloud_out);
return true;
}
使用这个统计学滤波感觉还是十分耗时,但是效果确实是不错的。
测试及结果:近邻点k值的数量为20,系数设定为1.0,耗时11.07s。效果如下:
有一篇论文《Fast statistical outlier removal based method for large 3d point clouds of outdoor environments》,根据其题目理解来说是一种更快的统计学滤波方法去除离群点,论文还没看,源码也没找到,谁能找到麻烦联系我,感谢!
3.RANSAC
没错,就是RANSAC。使用RANSAC去去噪需要满足条件,那就是目标点云是具有几何特征的。如果目标点云是一个平面,那么就可以使用RANSAC拟合一个平面,并且将距离平面较远的点(外点)去除。这样当然也可以达到去噪的效果,而且速度还比较快。
头文件:
#include <pcl/kdtree/kdtree.h>
#include <pcl/segmentation/sac_segmentation.h>
/// <summary>
/// 使用PCL中集成的用于点云分割的RANSAC方法进行平面拟合
/// </summary>
/// <param name="cloud_in">输入待拟合的点云</param>
/// <param name="inliers">RANSAC拟合得到的内点</param>
/// <param name="coefficients">得到的平面方程参数</param>
/// <param name="iterations">平面拟合最大迭代次数</param>
/// <param name="threshold">RANSAC拟合算法距离阈值</param>
void SEG_RANSAC(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_in, pcl::PointIndices::Ptr& inliers, Eigen::VectorXf& coefficients,
const int& iterations, const double& threshold)
{
if (inliers == nullptr) inliers.reset(new pcl::PointIndices);
pcl::ModelCoefficients::Ptr coefficients_m(new pcl::ModelCoefficients);
pcl::shared_ptr<pcl::search::KdTree<pcl::PointXYZ>> tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(cloud_in);
pcl::SACSegmentation<pcl::PointXYZ> seg;
seg.setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_PLANE);
seg.setMethodType(pcl::SAC_RANSAC);
seg.setMaxIterations(iterations); // 设置最大迭代次数
seg.setDistanceThreshold(threshold); // 设定阈值
seg.setNumberOfThreads(10); // 设置线程数量
seg.setSamplesMaxDist(3, tree);
seg.setInputCloud(cloud_in);
seg.segment(*inliers, *coefficients_m);
coefficients.resize(4);
coefficients[0] = coefficients_m->values[0]; coefficients[1] = coefficients_m->values[1];
coefficients[2] = coefficients_m->values[2]; coefficients[3] = coefficients_m->values[3];
std::cout << "SEG coefficients: " << coefficients[0] << ", " << coefficients[1] << ", " << coefficients[2] << ", " << coefficients[3] << std::endl;
}
运行时间只需要1.5s,迭代了200次。速度比半径滤波和统计学滤波要快很多。但是这个方法也是很有局限性的,只适合特定点云才能用,而且如下图所示,距离平面较近的噪点无法去除,所以是存在去除噪点不干净的情况,如果追求速度,而对去噪要求没那么高,则可以考虑使用该方法。
4.欧式聚类
欧式聚类既可以用于分割,也可以用于去噪,其实跟上述半径滤波区别不大。噪点肯定是距离想要的点云比较“远”的,设置好minSize,把想要的点聚成一个类,噪点自然就去除了。
代码如下:
/// <summary>
/// PCL中集成的欧式聚类
/// </summary>
/// <param name="cloud">输入点云</param>
/// <param name="cluster_indices">聚类索引的数组</param>
/// <param name="tolerance ">距离阈值</param>
/// <param name="MinClusterSize">单个类最少的点数</param>
/// <param name="MaxClusterSize">单个类最大的点数</param>
/// <returns>return true表示有聚类结果,return false表示聚类失败</returns>
bool NoiseRemoveEC(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, std::vector<pcl::PointIndices>& cluster_indices, const double& tolerance,
const int& MinClusterSize, const int& MaxClusterSize)
{
if (cloud == nullptr) return false;
if (cloud->points.size() < 10) return false;
int maxSize = MaxClusterSize;
if (maxSize < 0) maxSize = cloud->points.size();
if (MinClusterSize > maxSize) return false;
pcl::shared_ptr<pcl::search::KdTree<pcl::PointXYZ>> tree(new pcl::search::KdTree<pcl::PointXYZ>); // Creating the KdTree object for the search method of the extraction
tree->setInputCloud(cloud);
cluster_indices.clear();
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec; // 欧式聚类对象
ec.setClusterTolerance(tolerance); // 设置近邻搜索的搜索半径为 单位是m
ec.setMinClusterSize(MinClusterSize); // 设置一个聚类需要的最少的点数目
ec.setMaxClusterSize(maxSize); // 设置一个聚类需要的最大点数目
ec.setSearchMethod(tree); // 设置点云的搜索机制
ec.setInputCloud(cloud); // 输入聚类点云
ec.extract(cluster_indices); // 从点云中提取聚类,并将点云索引保存在cluster_indices中
if (cluster_indices.empty()) return false;
return true;
}
设置的距离阈值为0.1,是两倍的点距大小(x,y方向点距都是0.05)。耗时10.9s。当距离阈值tolerance设置的比较大时,就会特别耗时,该方法就几乎不可用了。
5 引导滤波
引导滤波即Guide Filter,点云引导滤波的基本思想是利用点云的几何结构信息来指导滤波过程。代码的出处来自于这里。对其进行了一些简单的修改,使用了多线程的方法去处理,运行速度提升了不少。
代码:
/// <summary>
/// 使用引导滤波对点云进行平滑
/// </summary>
/// <param name="cloud">输入点云</param>
/// <param name="cloud_filtered">输出滤波点云</param>
/// <param name="k">k邻近搜索点数</param>
/// <param name="epsilon">惩罚值</param>
void guidedfilter(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_filtered, int k, float epsilon)
{
std::vector<int> indices;
std::vector<float> dist;
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
kdtree.setInputCloud(cloud);
std::vector<int> index(cloud->points.size(), 0);
pcl::PointCloud<pcl::PointXYZ>::Ptr TempCloud(new pcl::PointCloud<pcl::PointXYZ>);
*TempCloud = *cloud;
#pragma omp parallel for
for (int i = 0; i < cloud->size(); ++i)
{
if (kdtree.nearestKSearch(cloud->points[i], k, indices, dist) > 0)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr neighbors(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*cloud, indices, *neighbors);
pcl::PointXYZ mean(0.0, 0.0, 0.0);
float mean2 = 0.0;
for (int j = 0; j < neighbors->size(); ++j)
{
mean.x += neighbors->points[j].x;
mean.y += neighbors->points[j].y;
mean.z += neighbors->points[j].z;
mean2 += pow(neighbors->points[j].x, 2) + pow(neighbors->points[j].y, 2) + pow(neighbors->points[j].z, 2);
}
mean.x /= neighbors->size();
mean.y /= neighbors->size();
mean.z /= neighbors->size();
mean2 /= neighbors->size();
float mean_2 = pow(mean.x, 2) + pow(mean.y, 2) + pow(mean.z, 2);
float a = (mean2 - mean_2) / (mean2 - mean_2 + epsilon);
pcl::PointXYZ b((1.0 - a) * mean.x, (1.0 - a) * mean.y, (1.0 - a) * mean.z);
pcl::PointXYZ filtered_point(a * cloud->points[i].x + b.x, a * cloud->points[i].y + b.y, a * cloud->points[i].z + b.z);
TempCloud->points[i] = filtered_point;
index[i] = i;
}
}
index.erase(std::remove(index.begin(), index.end(), 0), index.end()); // 删除0
pcl::copyPointCloud(*TempCloud, index, *cloud_filtered);
}
输入点云数量是82137个点,运行消耗时间是0.164s。算法参数最近邻k值是50,惩罚系数为0.5。
处理效果如下图所示,白色点为原始点云,红色点是引导滤波之后的结果。处理前后两份点云的数量一致,但是从点云效果来看,整体几何形状并没有发生什么改变,只不过点云更“凝练”了一点。
为了使得该滤波算法具备去噪功能,进一步改进,将kdtree.nearestKSearch修改为kdtree.radiusSearch。该算法就具备了半径滤波的功能,同时还具备引导滤波平滑特性。
/// <summary>
/// 使用引导滤波对点云进行平滑去噪
/// </summary>
/// <param name="cloud">输入点云</param>
/// <param name="cloud_filtered">输出滤波点云</param>
/// <param name="radius">搜索半径</param>
/// <param name="minPts">最少点数</param>
/// <param name="epsilon">惩罚系数</param>
void GuideFilterRemove(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_filtered, double radius, int minPts, float epsilon)
{
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
kdtree.setInputCloud(cloud);
std::vector<int> index(cloud->points.size(), 0);
pcl::PointCloud<pcl::PointXYZ>::Ptr TempCloud(new pcl::PointCloud<pcl::PointXYZ>);
*TempCloud = *cloud;
#pragma omp parallel for
for (int i = 0; i < cloud->size(); ++i)
{
std::vector<int> indices;
std::vector<float> dist;
if (kdtree.radiusSearch(cloud->points[i], radius, indices, dist) > minPts)
{
//std::cout << "indices size: " << indices.size() << std::endl;
pcl::PointCloud<pcl::PointXYZ>::Ptr neighbors(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*cloud, indices, *neighbors);
pcl::PointXYZ mean(0.0, 0.0, 0.0);
float mean2 = 0.0;
for (int j = 0; j < neighbors->size(); ++j)
{
mean.x += neighbors->points[j].x;
mean.y += neighbors->points[j].y;
mean.z += neighbors->points[j].z;
mean2 += pow(neighbors->points[j].x, 2) + pow(neighbors->points[j].y, 2) + pow(neighbors->points[j].z, 2);
}
mean.x /= neighbors->size();
mean.y /= neighbors->size();
mean.z /= neighbors->size();
mean2 /= neighbors->size();
float mean_2 = pow(mean.x, 2) + pow(mean.y, 2) + pow(mean.z, 2);
float a = (mean2 - mean_2) / (mean2 - mean_2 + epsilon);
pcl::PointXYZ b((1.0 - a) * mean.x, (1.0 - a) * mean.y, (1.0 - a) * mean.z);
pcl::PointXYZ filtered_point(a * cloud->points[i].x + b.x, a * cloud->points[i].y + b.y, a * cloud->points[i].z + b.z);
TempCloud->points[i] = filtered_point;
index[i] = i;
}
}
index.erase(std::remove(index.begin(), index.end(), 0), index.end()); // 删除0
pcl::copyPointCloud(*TempCloud, index, *cloud_filtered);
}
效果如下,周边的杂点基本上都被滤除了:
6.快速统计学滤波滤波
该方法是PCL中统计学滤波的快速版本,在参数设定一致的情况下,得到的结果与统计学滤波一致。
快速统计学滤波源码如下:
#pragma once
#include <iostream>
#include <map>
#include <tuple>
#include <vector>
#include <pcl/io/ply_io.h>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include "nanoflann.hpp"
#include <omp.h>
namespace FPCFilter {
struct PointCloud {
pcl::PointCloud<pcl::PointXYZ> &pts;
PointCloud(pcl::PointCloud<pcl::PointXYZ>& points) : pts(points) {
this->pts = points;
}
// Must return the number of data points
inline size_t kdtree_get_point_count() const { return pts.size(); }
// Returns the dim'th component of the idx'th point in the class:
// Since this is inlined and the "dim" argument is typically an immediate value, the
// "if/else's" are actually solved at compile time.
inline float kdtree_get_pt(const size_t idx, const size_t dim) const
{
if (dim == 0) return pts[idx].x;
else if (dim == 1) return pts[idx].y;
else return pts[idx].z;
}
double kdtree_distance(const float* p1, const size_t p2_idx,
size_t /*numDims*/) const
{
double d0 = p1[0] - pts[p2_idx].x;
double d1 = p1[1] - pts[p2_idx].y;
double d2 = p1[2] - pts[p2_idx].z;
return (d0 * d0 + d1 * d1 + d2 * d2);
}
// Optional bounding-box computation: return false to default to a standard bbox computation loop.
// Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again.
// Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds)
template <class BBOX>
bool kdtree_get_bbox(BBOX& /* bb */) const { return false; }
};
class FastOutlierFilter {
typedef nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<
double, PointCloud, double>, PointCloud, -1, std::size_t> KDTree;
double multiplier;
int meanK;
std::ostream& log = std::cout;
bool isVerbose;
std::unique_ptr<KDTree> tree;
const nanoflann::SearchParams params;
pcl::PointCloud<pcl::PointXYZ> cloudin;
public:
FastOutlierFilter()
{
meanK = 10;
multiplier = 1.0;
/*std::ostream& lll = std::cout;
log = lll;*/
isVerbose = false;
}
FastOutlierFilter(double std, int meanK, std::ostream &logstream, bool isVerbose) :
multiplier(std), meanK(meanK), isVerbose(isVerbose), log(logstream), params(nanoflann::SearchParams(10)) {}
void knnSearch(pcl::PointXYZ& point, size_t k,
std::vector<size_t>& indices, std::vector<double>& sqr_dists) const
{
nanoflann::KNNResultSet<double, size_t, size_t> resultSet(k);
resultSet.init(&indices.front(), &sqr_dists.front());
std::array<double, 3> pt = {point.x, point.y, point.z};
tree->findNeighbors(resultSet, &pt[0], this->params);
}
void setInputCloud(pcl::PointCloud<pcl::PointXYZ> cloud) { cloudin = cloud; }
void setMeanK(int nr_k) { meanK = nr_k; }
void setStddevMulThresh(double stddev_mult) { multiplier = stddev_mult; }
void setVerbose(bool isOutlog) { isVerbose = isOutlog; }
void filter(pcl::PointCloud<pcl::PointXYZ>& cloudout)
{
if (cloudin.points.size() < 10)
{
std::cout << "please input cloud!" << std::endl;
return;
}
PointCloud pointCloud(cloudin);
auto start = std::chrono::steady_clock::now();
tree = std::make_unique<KDTree>(3, pointCloud, nanoflann::KDTreeSingleIndexAdaptorParams(100));
tree->buildIndex();
if (this->isVerbose)
{
const std::chrono::duration<double> diff = std::chrono::steady_clock::now() - start;
std::cout << " ?> Done building index in " << diff.count() << "s" << std::endl;
}
size_t np = cloudin.size();
pcl::PointCloud<pcl::PointXYZ> newPoints;
newPoints.reserve(np);
pcl::PointCloud<pcl::PointNormal> newExtras;
newExtras.reserve(np);
std::vector<size_t> inliers, outliers;
std::vector<double> distances(np, 0.0);
// we increase the count by one because the query point itself will
// be included with a distance of 0
size_t count = (size_t)meanK + 1;
std::vector<size_t> indices;
std::vector<double> sqr_dists;
start = std::chrono::steady_clock::now();
#pragma omp parallel private (indices, sqr_dists)
{
indices.resize(count);
sqr_dists.resize(count);
// We are using 'long long' instead of size_t (unsigned long long) because OpenMP parallel for needs a signed index
#pragma omp for
for (long long i = 0; i < np; ++i)
{
knnSearch(cloudin[i], count, indices, sqr_dists);
for (size_t j = 1; j < count; ++j)
{
double delta = std::sqrt(sqr_dists[j]) - distances[i];
distances[i] += (delta / j);
}
indices.clear(); indices.resize(count);
sqr_dists.clear(); sqr_dists.resize(count);
}
}
if (this->isVerbose) {
const std::chrono::duration<double> diff = std::chrono::steady_clock::now() - start;
std::cout << " ?> Done calculating point neighbors average distances in " << diff.count() << "s" << std::endl;
}
size_t n(0);
double M1(0.0);
double M2(0.0);
start = std::chrono::steady_clock::now();
for (auto const& d : distances)
{
size_t n1(n);
n++;
double delta = d - M1;
double delta_n = delta / n;
M1 += delta_n;
M2 += delta * delta_n * n1;
}
double mean = M1;
double variance = M2 / (n - 1.0);
double stdev = std::sqrt(variance);
double threshold = mean + multiplier * stdev;
if (this->isVerbose) {
const std::chrono::duration<double> diff = std::chrono::steady_clock::now() - start;
std::cout << " ?> Done calculating cloud average distance " << diff.count() << "s" << std::endl;
}
if (true)
{
start = std::chrono::steady_clock::now();
for (size_t i = 0; i < np; ++i)
{
if (distances[i] < threshold) {
//const auto pt = cloudin.points[i];
//const auto xs = file.extras[i];
newPoints.emplace_back(cloudin.points[i]);
//newExtras.emplace_back(xs.nx, xs.ny, xs.nz);
}
}
newPoints.points.shrink_to_fit();
newExtras.points.shrink_to_fit();
//file.points = newPoints;
//file.extras = newExtras;
//cloudin = newPoints;
cloudout = newPoints;
if (this->isVerbose) {
const std::chrono::duration<double> diff = std::chrono::steady_clock::now() - start;
std::cout << " ?> Done filtering points in " << diff.count() << "s" << std::endl;
}
}
else {
start = std::chrono::steady_clock::now();
for (size_t i = 0; i < np; ++i)
{
if (distances[i] < threshold) {
//const auto pt = file.points[i];
newPoints.emplace_back(cloudin.points[i]);
}
}
newPoints.points.shrink_to_fit();
cloudout = newPoints;
if (this->isVerbose) {
const std::chrono::duration<double> diff = std::chrono::steady_clock::now() - start;
std::cout << " ?> Done filtering points in " << diff.count() << "s" << std::endl;
}
}
}
};
}
封装调用代码:
/// <summary>
/// 使用快速统计学滤波进行去噪
/// </summary>
/// <param name="cloud">输入点云</param>
/// <param name="cloud_out">输出点云</param>
/// <param name="k">搜索近邻点的个数</param>
/// <param name="stddev">平均距离标准差的乘数</param>
/// <returns>return true表示去噪成功,return false表示失败</returns>
bool FastOutlierFilter(pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_filtered, const int& k, const double& stddev)
{
if (cloud == nullptr) return false;
if (cloud->points.size() < 10) return false;
//FPCFilter::FastOutlierFilter filter(stdDeviation, meanK, logstream, isVerbose);
FPCFilter::FastOutlierFilter filter;
filter.setInputCloud(*cloud);
filter.setMeanK(k);
filter.setStddevMulThresh(stddev);
filter.filter(*cloud_filtered);
return true;
}
运行时间如下,确实在相同的参数和点云下,快速统计学滤波速度明显优于统计学滤波: