Files
hoodFieldOfView/头罩视野slove/头罩视野/ModbusHelper.cs
2026-04-24 18:07:58 +08:00

442 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Modbus.Device;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Windows;
using .Services.Data;
public static class ModbusHelper
{
// 全局唯一连接,所有页面共用
public static TcpClient TcpClient => ModbusResourceManager.Instance.TcpClient;
// 统一连接方法(全项目只调用一次)
public static bool Connect(string ip, int port = 502)
{
return ModbusResourceManager.Instance.Init(ip, port);
}
// 判断是否连接成功
public static bool IsConnected => TcpClient != null && TcpClient.Connected;
public static ushort ValidThreshold { get; private set; }
// <summary>
/// 公共保存方法(用户自选文件夹)
/// </summary>
/// <param name="dataList">你的数据集合</param>
/// <param name="defaultFileName">默认文件名</param>
public static void SaveToCsv(List<dynamic> dataList, string defaultFileName)
{
if (dataList == null || dataList.Count == 0)
{
MessageBox.Show("无数据可保存!");
return;
}
// 选择文件夹
var folderDialog = new OpenFolderDialog
{
Title = "请选择保存路径"
};
if (folderDialog.ShowDialog() != true)
return;
string folderPath = folderDialog.FolderName;
string filePath = Path.Combine(folderPath, defaultFileName);
// 写入 CSV
using (var sw = new StreamWriter(filePath, false, Encoding.UTF8))
{
var firstRow = (IDictionary<string, object>)dataList[0];
sw.WriteLine(string.Join(",", firstRow.Keys));
foreach (var item in dataList)
{
var dict = (IDictionary<string, object>)item;
sw.WriteLine(string.Join(",", dict.Values));
}
}
MessageBox.Show("保存成功!\n" + filePath);
}
// 剔除异常值,用相邻数据插值
private static List<ushort> RemoveOutliers(List<ushort> data)
{
for (int i = 1; i < data.Count - 1; i++)
{
if (Math.Abs(data[i] - data[i - 1]) > 30 && Math.Abs(data[i] - data[i + 1]) > 30)
{
data[i] = (ushort)((data[i - 1] + data[i + 1]) / 2);
}
}
return data;
}
//// 过滤无效信号
private const int ValidSignalThreshold = 12;
private static void FilterInvalidSignals(List<ushort> data)
{
for (int i = 0; i < data.Count; i++)
{
if (data[i] < ValidSignalThreshold)
data[i] = 0;
}
}
/// <summary>
/// 计算单眼视野面积
/// </summary>
/// <param name="groupData">20组数据每组72个通道</param>
/// <param name="threshold">有效亮度阈值如12</param>
/// <param name="standardTotalArea">标准视野面积如140</param>
/// <returns>计算好的面积</returns>
///
public static double CalculateEyeArea(List<double[]> groupData, double threshold, double standardArea)
{
double[] avg = new double[72];
for (int c = 0; c < 72; 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 / 72.0) * standardArea;
}
//计算单眼面积调用的方法
//double leftArea = CalculateEyeArea(
// leftEye20Groups, // 左眼20组数据
// 80, // 阈值
// 120 // 标准面积
//);
//double rightArea = .CalculateEyeArea(
// rightEye20Groups, // 右眼20组数据
// 80, // 阈值
// 120 // 标准面积
//);
//计算双目视野面积
/// <summary>
/// 计算双目视野面积(左右眼同时可见)
/// </summary>
public static double CalcBinocularArea(
List<double[]> leftGroups,
List<double[]> rightGroups,
double threshold,
double standardArea)
{
// 1. 左眼平均数据
double[] leftAvg = new double[72];
for (int i = 0; i < 72; i++)
{
double sum = 0;
foreach (var g in leftGroups) sum += g[i];
leftAvg[i] = sum / leftGroups.Count;
}
// 2. 右眼平均数据
double[] rightAvg = new double[72];
for (int i = 0; i < 72; i++)
{
double sum = 0;
foreach (var g in rightGroups) sum += g[i];
rightAvg[i] = sum / rightGroups.Count;
}
// 3. 双目同时有效点数(左右都亮才算)
int biValid = 0;
for (int i = 0; i < 72; i++)
{
if (leftAvg[i] >= threshold && rightAvg[i] >= threshold)
biValid++;
}
// 4. 双目视野面积
return (biValid / 72.0) * standardArea;
}
//调用公式
// 你从Modbus拿到的20组数据
//List<double[]> left20Groups = ...;
//List<double[]> right20Groups = ...;
//double threshold = 80;
//double standardArea = 120;
//// 左眼
//double left = VisionCalculator.CalcEyeArea(left20Groups, threshold, standardArea);
//// 右眼
//double right = VisionCalculator.CalcEyeArea(right20Groups, threshold, standardArea);
//// 双目视野面积
//double binocular = VisionCalculator.CalcBinocularArea(left20Groups, right20Groups, threshold, standardArea);
//// 总视野面积
//double total = left + right - binocular;
//下方视野角度
/// <summary>
/// GB2890-2022 计算 单眼下方视野角度
/// eyeData单眼72路平均数据数组
/// threshold有效亮度阈值
/// </summary>
public static double CalcLowerAngle(double[] eyeData, double threshold = 12)
{
// 总72点 每点5°
int totalPoint = 72;
double perAngle = 5;
// 国标最下方起始点位第54号开始为正下方
int startDownIndex = 54;
int validCount = 0;
// 从最下方向上 连续检测有效点
for (int i = 0; i < 36; i++)
{
int idx = (startDownIndex + i) % totalPoint;
if (eyeData[idx] >= threshold)
{
validCount++;
}
else
{
// 断开直接停止
break;
}
}
// 下方视野角度 = 有效点数 × 单步角度
return validCount * perAngle;
}
/// <summary>
/// 计算单眼72点通道平均值数组
/// </summary>
/// <param name="eyeGroups">多组采样数据集合</param>
/// <returns>72点通道平均值数组</returns>
private static double[] GetEyeAvgArray(List<double[]> eyeGroups)
{
if (eyeGroups == null || eyeGroups.Count == 0)
return new double[72]; // 无数据时返回全0数组
double[] avg = new double[72];
for (int i = 0; i < 72; i++)
{
double sum = 0;
foreach (var group in eyeGroups)
{
sum += group[i];
}
avg[i] = sum / eyeGroups.Count;
}
return avg;
}
//下方视野计算方法
////1. 先拿到左右眼 72点平均数组
//double[] leftAvg = GetLeftEyeAvgArray();
//double[] rightAvg = GetRightEyeAvgArray();
////2. 分别算下方角度
//double leftLowerAngle = CalcLowerAngle(leftAvg, 10);
//double rightLowerAngle = CalcLowerAngle(rightAvg, 10);
////3. 最终报告取值(国标取双眼较小值)
//double finalLowerAngle = Math.Min(leftLowerAngle, rightLowerAngle);
//bool lowerAngleOk = finalLowerAngle >= 35;
//空白视野面积计算
//空白视野面积 = 标准视野总面积 实测总视野面积
//总视野面积 = 左眼面积 + 右眼面积 双目重叠面积
// 前面已经算出来的
//double leftArea = ...;
//double rightArea = ...;
//double binocularArea = ...;
//// 总视野
//double totalVisionArea = leftArea + rightArea - binocularArea;
//// 标准总面积(设备固定值,比如 120
//double standardTotalArea = 120;
//// 空白视野面积
//double blankArea = standardTotalArea - totalVisionArea;
//视野保存率
//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, double standardArea)
{
if (standardArea == 0) return 0;
return (actualArea / standardArea) * 100;
}
}
// 空头模无面罩标定的标准面积GB2890-2022
//public static double StandardSingleEye = 5200;
//// 双眼总标准面积(左+右)
//public static double StandardTotalEye = 10400;
//// 双目重叠标准面积
////public static double StandardBinocular = 4200;
//// 下方视野标准角度
//public static double StandardLowerAngle = 75;
//===== 1. 你预先标定的 空模标准面积 =====
// 单眼标准、总标准(左+右)、双目重叠标准
public static double StandardLeftEye = 5180;
public static double StandardRightEye = 5180;
public static double StandardTotal = 10360;
public static double StandardBinocular = 4150;
//===== 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;
}
internal static double CalculateEyeArea(List<dynamic> leftEyeDataList, int v1, int v2)
{
throw new NotImplementedException();
}
internal static double CalcBinocularArea(List<dynamic> leftEyeDataList, List<dynamic> rightEyeDataList, int v1, int v2)
{
throw new NotImplementedException();
}
internal static double[] GetEyeAvgArray(List<dynamic> leftEyeDataList)
{
throw new NotImplementedException();
}
internal static List<dynamic> RemoveOutliers(List<dynamic> leftEyeDataList)
{
throw new NotImplementedException();
}
// 你算出来的实际面积
//double left = 4250;
//double right = 4320;
//double bin = 2860;
//// 一键算出 国标保存率
//var result = CalcVisionRate(left, right, bin);
//double 总视野保存率 = result.totalRate;
//double 双目视野保存率 = result.binRate;
}
//// 1. 总视野保存率
//double gammaTotal = 查国标图D.4的总视野γ;
//double totalRate = gammaTotal * totalArea / GlobalData.StandardTotalEye * 100;
// // 2. 双目视野保存率
// double gammaBinoc = 查国标图D.4的双目视野γ;
//double binocRate = gammaBinoc * binocArea / GlobalData.StandardBinocular * 100;
//// 3. 下方视野(直接比角度,不用面积)
//bool lowerPass = lowerAngle >= 35;
// 四、关键澄清(你之前问的)
//下方视野:国标是角度(°),不是面积
//按左右眼视野曲线下方交点位置直接读出角度
//合格≥35°
//总视野 = 左眼 + 右眼(国标明确)
//双目视野 = 左右眼重叠部分(单独算面积)
//五、最简总结(国标一句话)
//总视野保存率 =γ ×(左 + 右实测面积))/ 标准总面积 ×100%
//双目视野保存率 =γ × 重叠实测面积)/ 标准重叠面积 ×100%
//下方视野:直接看角度 ≥35°
//}