点云滤波降采样聚类分割整理

一、点云滤波降噪算法

统计滤波:

概念:去除明显分布稀疏的离群点。根据给定的均值和方差去除方差之外的点。

步骤:对每个点都计算最近的K个点的距离,求个平均值。此时点云里每个点都有一个平均值。算所有的均值和方差,然后根据123方差阈值来滤除离群点。

当判断点的k近邻平均距高(mean distance)大于全局的1倍标准差+平均距离(global distances meanm and standard),则为离群点。

直通滤波:

根据点云的属性,设置一个范围,来进行滤除。比如常见的xyz范围。

半径滤波:

设定一个半径R,统计在其R内的点的个数,小于阈值则为离群点。

中值滤波:

用于去除图像或者信号中的噪声。具体实现是将像素值替换为滑动窗口里的中间值来实现滤波。

因为其选择中值作为替代,所以不受异常值影响。

均值滤波:

通过计算领域窗口内的平均值来替换像素值达到去噪的效果,但是会破坏图像细节,使得图像变得模糊。起到平滑图像和信号的效果。

高斯滤波:

用于平滑图像并减少图像中的噪声。首先确定高斯核的大小和标准差,标准差决定高斯核的形状以及平滑程度。根据这两个生成二维高斯权值矩阵,进行卷积。标准差越大,越模糊。高斯核越大越模糊。

二、点云降采样算法

1.随机降采样

2.体素降采样

分两种,将点云变为体素之后,可以在每个体素里随机选择一个点,这样快,也可以在体素里求一个平均值。

3.最远点降采样

每次选取一个点,下次找剩下的离他最远的那个点,不断迭代。容易受到噪声点影响,可以将一些密集的点去掉。

三、点云聚类算法

1.K-means聚类算法

K未知需要人为设置,可以使用手肘法,遍历K的大小取loss最低的。

对于初始点比较敏感,可以多选择几种初始化方法,最终选择loss最小的。

对于噪声点问题,可以选取类里到其他点距离最短的那个点作为中心点,而不是求均值。

算法流程:首先随机选取K个中心点,然后计算每个点距离哪个中心点最近,就属于哪一类。然后计算每个类的平均值,作为新的中心点,开始迭代。直到一定次数或者这个中心点变化很小。

算法复杂度为:t(迭代次数) * k(中心点个数)* N(点总数)* d(每个点的维度)。

2.Mean-shift聚类算法

随机选择一个点作为球心,半径为R,然后求半径内的点平均值,作为新的中心点,就这样不断迭代。直到中心点不再变化或变化很小。过程中的所有点都归为这一类。然后判断收敛的中心点与已经存在的中心点距离是否大于阈值,不是就合并。

因为meanshift每迭代一次都要计算这个中心点与其他所有点的距离,很耗时。而我们的思路是,所有点来找已经存在的中心点,这样耗时很少。

简化版本:遍历数据,计算数据与已经存在的中心点的距离,如果小于阈值就归为一类,然后更新该类新的中心值。

3.DBSCAN聚类算法

自带滤波的聚类算法。首先设置半径R,半径里面点数少于一定的为噪声点,其余为核心点,基于密度的聚类算法,核心点半径内的核心点为一类,然后迭代R内的其他核心点,也都为这一类。直到R内只有此核心点为止。从所有点里移除此类点,继续上面的迭代。

R选择技巧:一般先选一个点,计算它与其他所有点的距离,然后排序,找到前后变化很大的一处,然后R就选则突变点即可。这个选的太大,簇就少,选的太小,簇就多,可以适当调整

min_point一般这个值都偏小,可以多次尝试一下。

四、点云分割算法

1.Ransac

是一种随机抽样算法,从样本里不断的随机采样,然后计算离群误差。只要属于采样方程里的内点数大于一定阈值就直接退出。

#include <iostream>
#include <vector>
#include <cmath>
#include <random>

struct Point {
  double x;
  double y;
};

// 计算两点之间的距离
double distance(const Point& p1, const Point& p2) {
  return std::sqrt(std::pow(p2.x - p1.x, 2) + std::pow(p2.y - p1.y, 2));
}

// 使用 RANSAC 算法拟合直线
void ransacLineFitting(const std::vector<Point>& points,
                      int maxIterations,
                      double distanceThreshold,
                      double inlierRatioThreshold,
                      std::vector<Point>& inliers,
                      double& slope,
                      double& intercept) {
  std::random_device rd;
  std::mt19937 rng(rd());
  std::uniform_int_distribution<int> uni(0, points.size() - 1);

  int bestInlierCount = 0;

  for (int iteration = 0; iteration < maxIterations; ++iteration) {
    // 随机选择两个点
    int index1 = uni(rng);
    int index2 = uni(rng);

    const Point& p1 = points[index1];
    const Point& p2 = points[index2];

    // 计算斜率和截距
    slope = (p2.y - p1.y) / (p2.x - p1.x);
    intercept = p1.y - slope * p1.x;

    // 用于保存内点
    std::vector<Point> currentInliers;

    for (const Point& point : points) {
      // 计算点到直线的距离
      double d = std::abs(point.y - slope * point.x - intercept);

      // 判断是否是内点
      if (d < distanceThreshold) {
        currentInliers.push_back(point);
      }
    }

    // 更新最佳内点数和内点集合
    if (currentInliers.size() > bestInlierCount) {
      bestInlierCount = currentInliers.size();
      inliers = std::move(currentInliers);

      // 计算内点比例
      double inlierRatio = static_cast<double>(bestInlierCount) / points.size();

      // 如果内点比例达到阈值,提前退出循环
      if (inlierRatio > inlierRatioThreshold) {
        break;
      }
    }
  }
}

int main() {
  // 构造示例数据点云
  std::vector<Point> points {
    {0.0, 0.5},
    {1.0, 2.0},
    {2.0, 3.5},
    {3.0, 4.5},
    {4.0, 6.0},
    {5.0, 7.5},
    {6.0, 9.0},
    {7.0, 10.5}
  };

  // RANSAC 参数
  int maxIterations = 1000;       // 最大迭代次数
  double distanceThreshold = 0.5; // 内点阈值
  double inlierRatioThreshold = 0.8; // 内点比例阈值

  // 输出结果保存变量
  std::vector<Point> inliers;
  double slope, intercept;

  // 使用 RANSAC 拟合直线
  ransacLineFitting(points, maxIterations, distanceThreshold, inlierRatioThreshold,
                    inliers, slope, intercept);

  // 输出拟合结果
  std::cout << "拟合直线方程:y = " << slope << "x + " << intercept << std::endl;
  std::cout << "内点数量:" << inliers.size() << std::endl;

  return 0;
}

猜你喜欢

转载自blog.csdn.net/slamer111/article/details/131750126
今日推荐