平面拟合 / 调平 / 点云高度 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)));
对应数学步骤:
- 算出所有 Z 的平均值 mean
- 每个 Z 减去平均值 → 得到偏差
- 偏差平方(让正负都变成正,突出大偏差)
- 求这些平方的平均值
- 开根号 → 就是标准差
简单记:标准差 = 数据平均波动幅度
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. 核心原理
- 排序:将所有 Z 值从小到大排序
- 分位数 :
- Q1(下四分位数):第 25% 位置的值
- Q3(上四分位数):第 75% 位置的值
- 计算 IQR :
IQR = Q3 - Q1(代表中间 50% 数据的波动范围) - 确定正常区间 :
- 下限:
Q1 - k * IQR - 上限:
Q3 + k * IQR - 通常取
k = 1.5(箱线图标准),k = 3用于识别极端异常值
- 下限:
- 筛选:保留区间内的点,剔除区间外的噪点
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];
}
}
为什么这个方法最稳?
-
中位数不会被噪点带偏
- 数据:
10, 10, 10, 1000 - 平均值 = 257(被噪点毁了)
- 中位数 = 10(完全正确)
- 数据:
-
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. 工作流程
- 对每个点 ,搜索它周围半径内的所有点
- 把这些点的 Z 值加起来求平均
- 用这个平均值作为当前点的新 Z
- 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 次
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 倾斜角
最优去噪方案优先级:
- 平面残差剔除(最强、最贴合业务)
- 3σ 统计剔除(简单好用)
- 高斯平滑(抑制抖动)
- RANSAC(噪声特别多时)