噪声处理方法

平面拟合 / 调平 / 点云高度 Z 去噪 场景,我把工业上常用、实用、能直接写进代码的噪声处理方法 全部列出来,按简单 → 复杂、轻量 → 强力排序,并说明适用场景、优缺点,方便你直接选型。

一、统计类去噪(最简单、最常用)

ONE. 3σ 拉依达准则(你现在用的)

  • 思路:计算 Z 的均值和标准差,剔除超出 N 倍 σ 的点
  • 优点:快、自适应、不用手动设阈值
  • 缺点:对整体偏斜数据敏感;不能处理密集噪声
  • 适用:少量飞点、跳变点

什么是标准差、怎么算、在你去噪代码里到底起什么作用,不讲复杂公式推导。

1. 标准差是什么?

一句话:标准差 = 衡量一组数据 "散不散"、"波动大不大" 的数值。

  • 数据很集中、很稳定 → 标准差
  • 数据很分散、波动大 → 标准差
  • 出现一个极端噪点 → 标准差会 瞬间变大

2. 举个最简单的例子

有两组 Z 高度数据:

组 A(正常数据)

10, 10.1, 9.9, 10, 10.2

  • 均值 ≈ 10
  • 波动很小
  • 标准差 ≈ 0.1

组 B(带噪点)

10, 10.1, 9.9, 10, 1000

  • 均值被拉高到 ≈ 208
  • 有一个极端噪点
  • 标准差 ≈ 445

你看:**只要一个噪点,标准差直接爆炸。**这就是我们用它去噪的原因。


3. 标准差怎么计算(代码里那行)

你代码里的这行:

csharp

运行

复制代码
double std = Math.Sqrt(zValues.Average(v => (v - mean) * (v - mean)));

对应数学步骤:

  1. 算出所有 Z 的平均值 mean
  2. 每个 Z 减去平均值 → 得到偏差
  3. 偏差平方(让正负都变成正,突出大偏差)
  4. 求这些平方的平均值
  5. 根号 → 就是标准差

简单记:标准差 = 数据平均波动幅度


4. 在你去噪代码里的作用

csharp

运行

复制代码
double minZ = mean - sigmaThreshold * std;
double maxZ = mean + sigmaThreshold * std;

作用只有一个:自动算出一个 "正常高度区间",超出的就是噪点。

  • mean:正常高度中心
  • std:波动范围
  • sigmaThreshold:你允许波动几倍
    • 2.5 倍 → 工业常用
    • 3 倍 → 更宽松

5. 直观理解(非常重要)

  • ±1σ:大约 68% 的正常点在里面
  • ±2σ:大约 95% 的正常点在里面
  • ±2.5σ ~ 3σ:99% 以上正常点在里面

所以:超出 ±3σ 的点,基本可以判定为异常噪点。


6. 对你平面调平的意义

你的最小二乘法极度怕 outliers

  • 一个噪点 Z 跳变 1mm
  • 拟合平面直接歪掉
  • RX/RY 角度就废了

标准差就是用来自动抓住这种捣乱的点,扔掉它。


7. 超简总结

  • 均值:这组数据大概在哪个高度
  • 标准差:这组数据晃得厉不厉害
  • n 倍标准差:画一条安全线,外面的全是噪点
  • 你代码逻辑:均值 ± 2.5× 标准差 = 正常 Z 范围
cs 复制代码
 /// <summary>
 /// 基于(拉依达准则)剔除 Z 值异常点
 /// 功能:自动识别并移除高度 Z 明显偏离正常范围的噪点,保留有效测量点
 /// 适用场景:平面拟合、调平算法前的去噪处理
 /// </summary>
 /// <param name="input">输入矩阵:N 行 3 列,结构为 [X, Y, Z]</param>
 /// <param name="sigmaThreshold">标准差倍数阈值,默认 2.5(工业常用)
 /// 阈值越小剔除越严格,越大越宽松,2.5~3 为最佳范围</param>
 /// <returns>清理噪点后的新矩阵(不修改原始输入数据)</returns>
 public static double[,] FilterOutlierZ(double[,] input, double sigmaThreshold = 2.5)
 {
     // 获取输入矩阵的总行数(测量点数量)
     int pointCount = input.GetLength(0);

     // 提取所有点的 Z 坐标,用于统计分析
     double[] zValues = new double[pointCount];
     for (int i = 0; i < pointCount; i++)
     {
         zValues[i] = input[i, 2];
     }

     // 计算 Z 值的平均值(代表整体高度中心)
     double meanZ = zValues.Average();

     // 计算 Z 值的总体标准差(代表数据波动/分散程度)
     // 公式:开根号( 所有点(Z-均值)² 的平均值 )
     double stdDev = Math.Sqrt(zValues.Average(v => (v - meanZ) * (v - meanZ)));

     // 根据阈值计算正常 Z 值的上下限
     double minNormalZ = meanZ - (sigmaThreshold * stdDev);
     double maxNormalZ = meanZ + (sigmaThreshold * stdDev);

     // 存储筛选后的正常点(X,Y,Z)
     var validPoints = new List<(double x, double y, double z)>();

     for (int i = 0; i < pointCount; i++)
     {
         double currentZ = input[i, 2];

         // 判断当前点 Z 是否在正常范围内
         if (currentZ >= minNormalZ && currentZ <= maxNormalZ)
         {
             // 保留正常点
             validPoints.Add((input[i, 0], input[i, 1], currentZ));
         }
         // 不在范围内的点视为噪点,直接丢弃
     }

     // 将筛选后的点转为 N×3 数组返回
     double[,] cleanedMatrix = new double[validPoints.Count, 3];
     for (int i = 0; i < validPoints.Count; i++)
     {
         cleanedMatrix[i, 0] = validPoints[i].x;
         cleanedMatrix[i, 1] = validPoints[i].y;
         cleanedMatrix[i, 2] = validPoints[i].z;
     }

     // 返回清理完成的矩阵
     return cleanedMatrix;
 }

TWO. 四分位数法 IQR

  • 思路:用 Q1、Q3 确定正常区间,不受极端值影响
  • 优点:比 3σ 更鲁棒
  • 适用:数据分布不均匀、有少量强噪点

📌 四分位数法(IQR)去噪实现

IQR(Interquartile Range,四分位距)法通过中位数四分位数定义正常范围,对极端值(噪点)更鲁棒,尤其适合非正态分布的数据。


1. 核心原理

  1. 排序:将所有 Z 值从小到大排序
  2. 分位数
    • Q1(下四分位数):第 25% 位置的值
    • Q3(上四分位数):第 75% 位置的值
  3. 计算 IQRIQR = Q3 - Q1(代表中间 50% 数据的波动范围)
  4. 确定正常区间
    • 下限:Q1 - k * IQR
    • 上限:Q3 + k * IQR
    • 通常取 k = 1.5(箱线图标准),k = 3 用于识别极端异常值
  5. 筛选:保留区间内的点,剔除区间外的噪点
cs 复制代码
/// <summary>
/// 基于四分位数法(IQR)剔除 Z 值异常点
/// 优点:不受极端值影响,对非正态分布数据更鲁棒
/// 适用:数据分布不均匀、存在强噪点的场景
/// </summary>
/// <param name="input">输入矩阵:N 行 3 列 [X, Y, Z]</param>
/// <param name="k">IQR 倍数,默认 1.5(标准箱线图阈值),k=3 用于识别极端异常</param>
/// <returns>清理噪点后的新矩阵(不修改原始输入)</returns>
public static double[,] FilterOutlierZ_IQR(double[,] input, double k = 1.5)
{
    int pointCount = input.GetLength(0);
    if (pointCount == 0) return input;

    // 1. 提取 Z 值并排序
    double[] zValues = new double[pointCount];
    for (int i = 0; i < pointCount; i++)
    {
        zValues[i] = input[i, 2];
    }
    Array.Sort(zValues); // 必须排序才能计算分位数

    // 2. 计算 Q1 (25%) 和 Q3 (75%)
    double q1 = GetPercentile(zValues, 0.25);
    double q3 = GetPercentile(zValues, 0.75);
    double iqr = q3 - q1;

    // 3. 计算正常区间
    double minNormalZ = q1 - k * iqr;
    double maxNormalZ = q3 + k * iqr;

    // 4. 筛选有效点
    var validPoints = new List<(double x, double y, double z)>();
    for (int i = 0; i < pointCount; i++)
    {
        double currentZ = input[i, 2];
        if (currentZ >= minNormalZ && currentZ <= maxNormalZ)
        {
            validPoints.Add((input[i, 0], input[i, 1], currentZ));
        }
    }

    // 5. 构建输出矩阵
    double[,] cleanedMatrix = new double[validPoints.Count, 3];
    for (int i = 0; i < validPoints.Count; i++)
    {
        cleanedMatrix[i, 0] = validPoints[i].x;
        cleanedMatrix[i, 1] = validPoints[i].y;
        cleanedMatrix[i, 2] = validPoints[i].z;
    }

    return cleanedMatrix;
}

/// <summary>
/// 辅助方法:计算排序后数组的指定百分位数(线性插值法)
/// </summary>
/// <param name="sortedData">已排序的数组</param>
/// <param name="percentile">百分位(0~1,如 0.25 代表 25%)</param>
/// <returns>对应百分位的值</returns>
private static double GetPercentile(double[] sortedData, double percentile)
{
    int n = sortedData.Length;
    if (n == 0) return 0;

    double index = (n - 1) * percentile;
    int lower = (int)Math.Floor(index);
    int upper = (int)Math.Ceiling(index);

    if (lower == upper)
        return sortedData[lower];

    // 线性插值
    double weight = index - lower;
    return sortedData[lower] * (1 - weight) + sortedData[upper] * weight;
}

3. 关键说明

  • 分位数计算GetPercentile 采用线性插值法,是工业界标准实现,比简单取整更精确。
  • 参数 k 选择
    • k = 1.5:温和去噪,保留大部分数据(推荐)
    • k = 3.0:仅剔除极端异常点,适合数据量少的场景
  • 与 3σ 对比
    • 3σ 依赖正态分布假设,易被极端值拉高均值和标准差
    • IQR 基于中位数和分位数,完全不受极端值干扰,更适合你这种可能存在强噪点的测量数据

使用示例:

cs 复制代码
// 原始数据(含噪点)
double[,] points = new double[,]
{
    { 1, 2, 10 },
    { 1.1, 2.1, 1000 }, // 强噪点
    { 0.9, 1.9, 10 },
    { 2, 3, 10.2 },
    { 2.1, 3.1, 9.8 }
};

// IQR 去噪(k=1.5)
double[,] cleaned = FilterOutlierZ_IQR(points, 1.5);
// 结果:噪点 (1.1, 2.1, 1000) 被剔除,保留其他正常点

5. 与你现有 3σ 方法的对比

表格

特性 3σ 法 IQR 法
分布假设 依赖正态分布 无分布假设,适用于任意分布
抗噪性 易被极端值影响 极强,不受极端值干扰
计算速度 快(无需排序) 稍慢(需要排序)
适用场景 数据近似正态、噪点较少 数据分布不均、存在强噪点

💡 建议

  • 如果你的 Z 数据分布偏斜、存在明显飞点 ,优先使用 IQR 法,去噪更稳定。
  • 如果数据近似正态分布,3σ 法计算更快,也可使用。
  • 你可以在代码中同时实现两种方法,通过配置开关切换,方便对比效果。

THREE. 中位数去噪

  • 思路:用中位数代替均值判断异常
  • 优点:极稳,抗噪强
  • 适用:Z 波动大、噪声多的场景

中位数去噪(绝对抗噪王者)

原理 :用中位数 代替平均值,计算每个点与中位数的偏差,剔除偏差过大的点。优点 :完全不怕极端噪点,比 3σ / IQR 都更稳。适用:Z 波动大、噪声多、测量不稳定的场景。

cs 复制代码
/// <summary>
/// 中位数去噪(鲁棒性最强)
/// 思路:
/// 1. 计算 Z 的中位数(不受噪点影响)
/// 2. 计算所有点到中位数的绝对偏差 MAD
/// 3. 设定阈值,剔除偏差过大的噪点
/// 优点:极稳、抗噪极强、不受极端飞点影响
/// 适用:Z 波动大、噪声多、测量不稳定场景
/// </summary>
/// <param name="input">输入矩阵 N×3 [X,Y,Z]</param>
/// <param name="threshold">偏差倍数阈值,默认 3.0(越大越宽松)</param>
/// <returns>去噪后新矩阵</returns>
public static double[,] FilterOutlierZ_Median(double[,] input, double threshold = 3.0)
{
    int pointCount = input.GetLength(0);
    if (pointCount == 0) return input;

    // 1. 提取所有 Z 值
    double[] zValues = new double[pointCount];
    for (int i = 0; i < pointCount; i++)
    {
        zValues[i] = input[i, 2];
    }

    // 2. 计算 Z 的中位数(核心:不受噪点影响)
    double medianZ = GetMedian(zValues);

    // 3. 计算每个点与中位数的绝对偏差
    double[] absDeviations = new double[pointCount];
    for (int i = 0; i < pointCount; i++)
    {
        absDeviations[i] = Math.Abs(zValues[i] - medianZ);
    }

    // 4. 计算偏差的中位数 MAD(鲁棒性标准差)
    double mad = GetMedian(absDeviations);
    
    // 修正系数,让 MAD 接近正态分布的标准差
    double modifiedMad = mad * 1.4826;

    // 5. 设定正常范围
    double minZ = medianZ - threshold * modifiedMad;
    double maxZ = medianZ + threshold * modifiedMad;

    // 6. 筛选正常点
    var validPoints = new List<(double x, double y, double z)>();
    for (int i = 0; i < pointCount; i++)
    {
        double z = input[i, 2];
        if (z >= minZ && z <= maxZ)
        {
            validPoints.Add((input[i, 0], input[i, 1], z));
        }
    }

    // 7. 生成输出矩阵
    double[,] cleanedMatrix = new double[validPoints.Count, 3];
    for (int i = 0; i < validPoints.Count; i++)
    {
        cleanedMatrix[i, 0] = validPoints[i].x;
        cleanedMatrix[i, 1] = validPoints[i].y;
        cleanedMatrix[i, 2] = validPoints[i].z;
    }

    return cleanedMatrix;
}

/// <summary>
/// 辅助方法:计算数组的中位数
/// </summary>
private static double GetMedian(double[] values)
{
    int n = values.Length;
    double[] sorted = (double[])values.Clone();
    Array.Sort(sorted);

    if (n % 2 == 0)
    {
        return (sorted[n / 2 - 1] + sorted[n / 2]) / 2.0;
    }
    else
    {
        return sorted[n / 2];
    }
}

为什么这个方法最稳?

  1. 中位数不会被噪点带偏

    • 数据:10, 10, 10, 1000
    • 平均值 = 257(被噪点毁了)
    • 中位数 = 10(完全正确)
  2. MAD(偏差中位数)比标准差更稳

    • 不受极端值影响
    • 工业高精度测量首选

使用方法

csharp

运行

复制代码
// 直接替换你原来的 3σ / IQR 即可
smoothedMatrix = FilterOutlierZ_Median(locationMatrix, 3.0);

三种去噪方法对比(你现在有三套神器了)

表格

方法 抗噪能力 速度 适用场景
3σ 标准差 中等 最快 数据正态、噪点少
IQR 四分位 分布不均、有飞点
中位数去噪 极强 噪声多、波动大、测量不稳

总结

中位数去噪 = 你现在能用到的最强去噪方案 特别适合你这种平面拟合调平,噪点再多也不会影响拟合结果。


二、平滑类去噪(不改坐标,只修正 Z)

FOUR. 均值平滑(邻域平均)

  • 思路:每个点 Z = 周围点的平均
  • 优点:简单、平滑效果明显
  • 缺点:会 "模糊" 真实平面趋势

完全适配你的平面拟合调平场景:只平滑 Z 值,保留 X、Y 坐标不变,不修改原始数据

cs 复制代码
/// <summary>
/// 均值平滑(邻域平均)
/// 思路:遍历每个点,取半径范围内的所有点,对 Z 值取算术平均
/// 优点:简单高效、平滑毛刺效果明显
/// 缺点:过度平滑会模糊真实平面趋势
/// </summary>
/// <param name="input">输入矩阵 N×3 [X, Y, Z]</param>
/// <param name="radius">邻域半径:只使用距离小于该半径的点</param>
/// <returns>平滑后的新矩阵,X、Y不变,Z为邻域平均值</returns>
public static double[,] MeanSmoothMatrix(double[,] input, double radius)
{
    int pointCount = input.GetLength(0);
    // 新建输出数组,不修改原始数据
    double[,] output = new double[pointCount, 3];
    double radiusSquared = radius * radius; // 平方,避免开根号,提升速度

    for (int i = 0; i < pointCount; i++)
    {
        // 原始 X、Y 坐标 100% 保留
        double x = input[i, 0];
        double y = input[i, 1];

        double sumZ = 0;   // Z值总和
        int count = 0;     // 邻域内有效点数量

        // 遍历所有点,找出当前点 i 邻域内的点 j
        for (int j = 0; j < pointCount; j++)
        {
            double dx = x - input[j, 0];
            double dy = y - input[j, 1];
            double distSq = dx * dx + dy * dy;

            // 在半径范围内
            if (distSq <= radiusSquared)
            {
                sumZ += input[j, 2];
                count++;
            }
        }

        // 赋值:X、Y 不变,Z = 邻域平均值
        output[i, 0] = x;
        output[i, 1] = y;
        output[i, 2] = count > 0 ? sumZ / count : input[i, 2];
    }

    return output;
}

核心说明

1. 工作流程

  1. 每个点 ,搜索它周围半径内的所有点
  2. 把这些点的 Z 值加起来求平均
  3. 用这个平均值作为当前点的新 Z
  4. X、Y 坐标完全不变

2. 优点

  • 代码最简单,计算最快
  • 毛刺、小抖动立刻被抹平
  • 适合轻微噪声

3. 缺点

  • 所有邻域点权重一样,不管远近
  • 平滑过度会把平面 "磨平",丢失真实倾斜信息
  • 不如高斯平滑自然

使用示例

csharp

运行

复制代码
// 半径根据你的点间距设置,例如 5~20
double[,] smoothed = MeanSmoothMatrix(locationMatrix, 10.0);

与高斯平滑对比(最关键)

表格

方式 特点 效果 推荐度
均值平滑 邻域所有点权重相同 偏模糊 ⭐⭐
高斯平滑 距离越近权重越大 自然、保留趋势 ⭐⭐⭐⭐⭐

总结

均值平滑 = 简单粗暴的邻域平均 适合快速降噪 ,但不推荐用于高精度平面拟合

FIVE. 高斯平滑(你前面用的)

  • 思路:距离越近权重越大,加权平均 Z
  • 优点:保留趋势、抑制毛刺,工业视觉标配
  • 适用:测量有高频小抖动、轻微噪声

只平滑 Z,保留 X/Y 不变,不修改原始数据,工业级通用版本 并配上清晰注释 + 原理 + 使用方法


高斯平滑(工业最常用)

核心思路

  • 每个点的新 Z = 周围点的加权平均
  • 距离越近 → 权重越大
  • 距离越远 → 权重越小
  • 完全保留平面趋势,不会像均值平滑那样 "糊掉"

优点

✅ 保留真实平面倾斜(不影响你的 RX/RY 调平)✅ 抑制毛刺、抖动、高频噪声✅ 比均值平滑更自然、更精准✅ 工业测量 / 视觉 / 点云 标配算法

cs 复制代码
/// <summary>
/// 高斯平滑(高斯核加权邻域滤波)
/// 功能:对 Z 值进行平滑,保留 X、Y 坐标不变,不修改原始数据
/// 特点:距离越近权重越大,平滑自然,保留平面趋势,不模糊信号
/// 适用:测量噪声、高频抖动、轻微噪点
/// </summary>
/// <param name="input">输入矩阵 N×3 [X, Y, Z]</param>
/// <param name="radius">邻域半径:只对半径内的点加权</param>
/// <param name="sigma">高斯标准差:控制平滑强度(建议 = radius / 2 或 3)</param>
/// <returns>平滑后的新矩阵 X/Y 不变,Z 已平滑</returns>
public static double[,] GaussianSmoothMatrix(double[,] input, double radius, double sigma)
{
    // 点的数量
    int pointCount = input.GetLength(0);

    // 新建输出数组,不修改原始数据
    double[,] output = new double[pointCount, 3];

    // 预计算:避免循环里重复计算
    double twoSigmaSquared = 2 * sigma * sigma;
    double radiusSquared = radius * radius;

    // 遍历每个点,计算平滑后的 Z
    for (int i = 0; i < pointCount; i++)
    {
        // 保留原始 X、Y 坐标,绝对不变
        double x = input[i, 0];
        double y = input[i, 1];

        double weightedSumZ = 0;  // 加权总和
        double totalWeight = 0;   // 权重总和

        // 遍历所有邻点 j
        for (int j = 0; j < pointCount; j++)
        {
            // 计算两点距离平方
            double dx = x - input[j, 0];
            double dy = y - input[j, 1];
            double distSq = dx * dx + dy * dy;

            // 只处理半径内的点
            if (distSq <= radiusSquared)
            {
                // 高斯权重公式:距离越近,权重越大
                double weight = Math.Exp(-distSq / twoSigmaSquared);

                weightedSumZ += weight * input[j, 2];
                totalWeight += weight;
            }
        }

        // 赋值:X、Y 原样保留,Z 平滑
        output[i, 0] = x;
        output[i, 1] = y;

        // 避免除零(如果是孤立点,保持原值)
        output[i, 2] = totalWeight > 0 ? weightedSumZ / totalWeight : input[i, 2];
    }

    return output;
}

📘 超简单理解(一句话)

高斯平滑 = 让每个点的高度,受 "离得近的点" 影响更大,受 "远的点" 影响更小,所以平滑得非常自然。


🔧 参数怎么调(直接照用)

csharp

运行

复制代码
// 推荐参数
double radius = 10;       // 邻域半径(根据你的点间距设置)
double sigma = 3;         // 平滑强度(sigma 越大越平滑)

// 使用
var smoothed = GaussianSmoothMatrix(locationMatrix, radius, sigma);

通用经验:

  • radius = 点平均间距的 1.5~2.5 倍
  • sigma = radius / 2 ~ 3

🚀 为什么最适合你的调平算法?

你的代码是:平面拟合 Z = aX + bY + c → 求倾斜角 RX/RY

高斯平滑:

  • 不会改变平面整体倾斜
  • 不会把倾斜 "磨平"
  • 只会去掉毛刺、抖动
  • 让拟合结果更稳、更准、重复性更高

📌 总结

高斯平滑 = 测量数据去噪的黄金标准✅ 不修改坐标✅ 不破坏平面倾斜✅ 平滑自然✅ 工业界通用✅ 完美适配你的调平拟合

6. 中值滤波

  • 思路:邻域 Z 排序取中位数
  • 优点:极强抑制尖峰噪声
  • 适用:有随机脉冲噪声、单点跳变

三、拟合类去噪(最适合你的平面调平)

7. 平面残差剔除(最推荐用于调平

  • 思路:
    1. 先拟合平面
    2. 计算每个点到平面的距离(残差)
    3. 剔除残差过大的点
    4. 重新拟合
  • 优点:完全贴合你的业务,噪点识别最准
  • 缺点:需要迭代 1~2 次
cs 复制代码
/// <summary>
/// 平面残差剔除(平面调平专用最强去噪)
/// 思路:
/// 1. 用所有点拟合平面 Z = aX + bY + c
/// 2. 计算每个点到平面的残差(距离)
/// 3. 剔除残差过大的噪点
/// 4. 只保留贴近平面的点
/// 优点:完全贴合调平业务,噪点识别最精准
/// </summary>
/// <param name="input">N×3 矩阵 [X,Y,Z]</param>
/// <param name="sigmaThreshold">残差几倍标准差算异常,默认2.5</param>
/// <returns>剔除噪点后的干净点集</returns>
public static double[,] FilterOutlierByPlaneResidual(double[,] input, double sigmaThreshold = 2.5)
{
    int pointCount = input.GetLength(0);
    if (pointCount < 3) return input; // 至少3点才能拟合平面

    // ======================================
    // 步骤1:拟合平面 Z = a*X + b*Y + c
    // ======================================
    var denseMatrix = new DenseMatrix(pointCount, 3);
    var denseVectorZ = new DenseVector(pointCount);

    for (int i = 0; i < pointCount; i++)
    {
        denseMatrix[i, 0] = input[i, 0];
        denseMatrix[i, 1] = input[i, 1];
        denseMatrix[i, 2] = 1;
        denseVectorZ[i] = input[i, 2];
    }

    // 最小二乘求解平面参数
    var transpose = denseMatrix.Transpose();
    var beta = (transpose * denseMatrix).Inverse() * transpose * denseVectorZ;
    double a = beta[0];
    double b = beta[1];
    double c = beta[2];

    // ======================================
    // 步骤2:计算每个点到平面的残差(|Z实际 - Z拟合|)
    // ======================================
    double[] residuals = new double[pointCount];
    for (int i = 0; i < pointCount; i++)
    {
        double x = input[i, 0];
        double y = input[i, 1];
        double zReal = input[i, 2];
        double zFit = a * x + b * y + c;       // 平面预测高度
        residuals[i] = Math.Abs(zReal - zFit); // 残差 = 偏离距离
    }

    // ======================================
    // 步骤3:统计残差,剔除异常值
    // ======================================
    double meanRes = residuals.Average();
    double stdRes = Math.Sqrt(residuals.Average(r => Math.Pow(r - meanRes, 2)));
    double maxAllowedRes = meanRes + sigmaThreshold * stdRes;

    // ======================================
    // 步骤4:保留残差小的点(贴近平面)
    // ======================================
    var validPoints = new List<(double x, double y, double z)>();
    for (int i = 0; i < pointCount; i++)
    {
        if (residuals[i] <= maxAllowedRes)
        {
            validPoints.Add((input[i, 0], input[i, 1], input[i, 2]));
        }
    }

    // ======================================
    // 步骤5:生成输出矩阵
    // ======================================
    double[,] cleanPoints = new double[validPoints.Count, 3];
    for (int i = 0; i < validPoints.Count; i++)
    {
        cleanPoints[i, 0] = validPoints[i].x;
        cleanPoints[i, 1] = validPoints[i].y;
        cleanPoints[i, 2] = validPoints[i].z;
    }

    return cleanPoints;
}

8. RANSAC 随机抽样一致性(工业级强噪)

  • 思路:随机选点拟合,反复投票选出最优平面
  • 优点:噪声比例很高也能用
  • 适用:脏数据、大量异常点、半遮挡测量

9. 最小截平方 LTS / M 估计

  • 思路:鲁棒最小二乘,自动降低异常点权重
  • 优点:不删点,只降权,保留所有坐标
  • 适用:高精度调平、不能丢点的场景

四、工程实用组合方案(直接给你可用策略)

策略 A:轻量稳定(推荐你现在用)

3σ 剔除 + 高斯平滑

  • 先删飞点
  • 再平滑毛刺
  • 拟合准、速度快

策略 B:中高强度噪声

中值滤波 + 平面残差剔除

  • 尖峰噪声全干掉
  • 再按 "偏离平面" 删点

策略 C:极脏数据(大量噪点)

RANSAC + 迭代残差剔除

  • 工业高精度设备常用
  • 几乎不受噪点影响

策略 D:不允许删点(必须保留所有坐标)

高斯加权拟合 / M 估计

  • 不剔除任何点
  • 只降低噪点权重
  • 坐标完整,拟合稳定

五、按你的场景给明确建议

你是:平面拟合调平(Z = aX + bY + c)→ 计算 RX/RY 倾斜角

最优去噪方案优先级:

  1. 平面残差剔除(最强、最贴合业务)
  2. 3σ 统计剔除(简单好用)
  3. 高斯平滑(抑制抖动)
  4. RANSAC(噪声特别多时)
相关推荐
菜菜小狗的学习笔记3 小时前
剑指Offer算法题(九)搜索
数据结构·算法·深度优先
JosieBook3 小时前
【C#】C# 所有关键字总结
开发语言·算法·c#
Gideon_k_Marx3 小时前
读代码3:OLMo3全详解 - layer2--Data (上)
人工智能·深度学习·机器学习·语言模型·自然语言处理
春风化作秋雨3 小时前
Transformer:颠覆AI的注意力革命
人工智能·深度学习·transformer
无忧智库3 小时前
算力、算法、数据三位一体:构建城市级AI大模型算力池的全景式解构与未来展望(WORD)
大数据·人工智能·算法
L-影3 小时前
下篇:它到底是怎么操作的——AI中半监督学习的类型与作用,以及为什么它成了行业的“最优解”
人工智能·学习·机器学习·ai·半监督学习
后端小肥肠3 小时前
OpenClaw多Agent实战|手把手教你用一只小龙虾接入多个飞书Bot
人工智能·aigc·agent
北京耐用通信3 小时前
从隔离到互联:工业现场中耐达讯自动化CC-Link IE转Modbus RTU实战指南
人工智能·科技·物联网·自动化·信息与通信
蓝天守卫者联盟13 小时前
2026乙酸乙酯回收设备厂家选型与技术实践
java·jvm·python·算法