234 lines
8.1 KiB
C#
234 lines
8.1 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Text;
|
||
|
||
namespace 头罩视野.Services
|
||
{
|
||
class GetArea
|
||
{
|
||
//public const double standardArea = 140;
|
||
|
||
/// <summary>有效亮度阈值:区分有效视野和噪声/遮挡的门槛设备标定经验值,≥12判定为有效视野</summary>
|
||
public const int threshold = 12;
|
||
|
||
/// <summary>异常值差值阈值:过滤孤立尖峰噪声当前点与前后点差值均>30时,判定为异常值并插值修正</summary>
|
||
public const int OutlierDiffThreshold = 30;
|
||
|
||
/// <summary>灯条通道总数:360°圆周采样点数量对应每点5°(360° ÷ 72 = 5°/点),符合国标GB2890-2022要求</summary>
|
||
public const int lightNum = 72;
|
||
|
||
/// <summary>设备最大检测半径:理论上的最大视野半径单位:mm,用来计算理论圆面积</summary> 这个是我们自己的设备值
|
||
public const int maxRadius_mm = 330;
|
||
|
||
/// <summary>单眼标准标定面积:无面罩空标准头模的单眼实测面积 国标视野保存率计算的基准值,单位:cm²</summary>
|
||
public const double standardArea = 5180;
|
||
|
||
/// <summary>双目标准标定总面积:无面罩空标准头模的双目总实测面积国标总视野保存率计算的基准值,单位:cm²</summary>
|
||
public const double StandardTotal = 10360;
|
||
|
||
// 补充:用半径计算的单眼理论圆面积(供参考) 公式:π × 半径²,单位:cm²
|
||
public static readonly double standardAreaOus = Math.PI * maxRadius_mm * maxRadius_mm / 100;
|
||
|
||
//双目重叠标准
|
||
public static double StandardBinocular = 4150;
|
||
|
||
// 剔除异常值,用相邻数据插值
|
||
|
||
public static List<dynamic> RemoveOutliers(List<dynamic> data)
|
||
{
|
||
for (int i = 1; i < data.Count - 1; i++)
|
||
{
|
||
if (Math.Abs(data[i] - data[i - 1]) > OutlierDiffThreshold && Math.Abs(data[i] - data[i + 1]) > OutlierDiffThreshold)
|
||
{
|
||
data[i] = (dynamic)((data[i - 1] + data[i + 1]) / 2);
|
||
}
|
||
//过滤无效信号
|
||
if (data[i] < threshold)
|
||
{
|
||
data[i] = 0;
|
||
}
|
||
|
||
}
|
||
return data;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 计算单眼视野面积
|
||
/// </summary>
|
||
/// <param name="groupData">20组数据,每组lightNum个通道</param>
|
||
/// <param name="threshold">有效亮度阈值(如12)</param>
|
||
/// <param name="standardArea">(如140)</param>
|
||
/// <returns>计算好的面积</returns>
|
||
///
|
||
public static double CalculateEyeArea(List<dynamic> groupData)
|
||
{
|
||
double[] avg = new double[lightNum];
|
||
for (int c = 0; c < lightNum; c++)
|
||
{
|
||
double sum = 0;
|
||
foreach (var g in groupData) sum += g[c];
|
||
avg[c] = sum / groupData.Count;
|
||
}
|
||
|
||
int valid = avg.Count(v => v >= threshold);
|
||
return (valid / lightNum) * standardArea;
|
||
}
|
||
|
||
//计算双目视野面积
|
||
/// <summary>
|
||
/// 计算双目视野面积(左右眼同时可见)
|
||
/// </summary>
|
||
public static double CalcBinocularArea(
|
||
List<dynamic> leftGroups,
|
||
List<dynamic> rightGroups
|
||
)
|
||
{
|
||
// 1. 左眼平均数据
|
||
double[] leftAvg = GetEyeAvgArray(leftGroups);
|
||
|
||
// 2. 右眼平均数据
|
||
double[] rightAvg = GetEyeAvgArray(rightGroups);
|
||
|
||
// 3. 双目同时有效点数(左右都亮才算)
|
||
int biValid = 0;
|
||
for (int i = 0; i < lightNum; i++)
|
||
{
|
||
if (leftAvg[i] >= threshold && rightAvg[i] >= threshold)
|
||
biValid++;
|
||
}
|
||
|
||
// 4. 双目视野面积
|
||
return (biValid / lightNum) * standardArea;
|
||
}
|
||
|
||
//下方视野角度
|
||
/// <summary>
|
||
/// GB2890-2022 计算 单眼下方视野角度
|
||
/// eyeData:单眼lightNum路平均数据数组
|
||
/// threshold:有效亮度阈值
|
||
/// </summary>
|
||
public static double CalcLowerAngle(double[] eyeData, double perAngle)
|
||
{
|
||
// 总lightNum点 每点5°
|
||
int totalPoint = lightNum;
|
||
|
||
// 国标:最下方起始点位(第54号开始为正下方)
|
||
int startDownIndex = 54;
|
||
|
||
int validCount = 0;
|
||
|
||
// 从最下方向上 连续检测有效点
|
||
for (int i = 0; i < lightNum / 2; i++)
|
||
{
|
||
int idx = (startDownIndex + i) % totalPoint;
|
||
|
||
if (eyeData[idx] >= threshold)
|
||
{
|
||
validCount++;
|
||
}
|
||
else
|
||
{
|
||
// 断开直接停止
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 下方视野角度 = 有效点数 × 单步角度
|
||
return validCount * perAngle;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 计算单眼lightNum点通道平均值数组
|
||
/// </summary>
|
||
/// <param name="eyeGroups">多组采样数据集合</param>
|
||
/// <returns>lightNum点通道平均值数组</returns>
|
||
public static double[] GetEyeAvgArray(List<dynamic> eyeGroups)
|
||
{
|
||
if (eyeGroups == null || eyeGroups.Count == 0)
|
||
return new double[lightNum]; // 无数据时返回全0数组
|
||
|
||
double[] avg = new double[lightNum];
|
||
|
||
for (int i = 0; i < lightNum; i++)
|
||
{
|
||
double sum = 0;
|
||
foreach (var group in eyeGroups)
|
||
{
|
||
sum += group[i];
|
||
}
|
||
avg[i] = sum / eyeGroups.Count;
|
||
}
|
||
|
||
return avg;
|
||
}
|
||
|
||
|
||
//视野保存率
|
||
//double totalSaveRate = (总视野面积 / 标准总视野面积) * 100;
|
||
public static class VisionCalculator
|
||
{
|
||
/// <summary>
|
||
/// 计算视野保存率
|
||
/// </summary>
|
||
/// <param name="actualArea">实测面积</param>
|
||
/// <param name="standardArea">标准面积</param>
|
||
/// <returns>保存率 %</returns>
|
||
public static double CalculateVisionSaveRate(double actualArea)
|
||
{
|
||
|
||
return (actualArea / standardArea) * 100;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//===== 2. 传入你采集的实测面积 =====
|
||
// leftArea:左眼实测 rightArea:右眼实测 binArea:双目重叠实测
|
||
public static double CalcVisionRate(double leftArea, double rightArea)
|
||
{
|
||
// 总视野实测 = 左+右
|
||
double totalSi = leftArea + rightArea;
|
||
|
||
// 1. 总视野保存率
|
||
double ratioTotal = totalSi / StandardTotal;
|
||
double gammaTotal = GetVisionGamma(ratioTotal);
|
||
double totalRate = gammaTotal * ratioTotal * 100;
|
||
return (totalRate);
|
||
}
|
||
|
||
/// <summary>
|
||
/// GB2890-2022 自动获取 总视野/双目视野 校正系数γ
|
||
/// </summary>
|
||
/// <param name="ratio">实测面积/标准面积 比值(0~1)</param>
|
||
/// <returns>校正系数 γ</returns>
|
||
public static double GetVisionGamma(double ratio)
|
||
{
|
||
// X:视野残存率 Si/S0
|
||
double[] xData = { 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 };
|
||
|
||
// 总视野 γ 对应值
|
||
double[] gammaTotal = { 1.22, 1.18, 1.14, 1.10, 1.06, 1.03, 1.02, 1.01, 1.00 };
|
||
|
||
double[] yData = gammaTotal;
|
||
|
||
// 边界限制
|
||
if (ratio <= xData[0]) return yData[0];
|
||
if (ratio >= xData.Last()) return 1.0;
|
||
|
||
// 线性插值
|
||
for (int i = 0; i < xData.Length - 1; i++)
|
||
{
|
||
if (ratio >= xData[i] && ratio <= xData[i + 1])
|
||
{
|
||
double t = (ratio - xData[i]) / (xData[i + 1] - xData[i]);
|
||
return yData[i] + t * (yData[i + 1] - yData[i]);
|
||
}
|
||
}
|
||
return 1.0;
|
||
}
|
||
}
|
||
}
|