This commit is contained in:
xyy
2026-05-25 13:56:11 +08:00
parent d4edefa050
commit 3c309d6470
3 changed files with 272 additions and 559 deletions

View File

@@ -1,263 +1,111 @@
using MathNet.Numerics.LinearAlgebra;
using MathNetMatrix = MathNet.Numerics.LinearAlgebra.Matrix<double>;
using MathNetVector = MathNet.Numerics.LinearAlgebra.Vector<double>;
using System.Drawing;
using System;
using System.Collections.Generic;
using System.Linq;
namespace .Services
{
class GetArea
public static class GetArea
{
// 设备固定参数
public static double R = 325; // 半球半径
// 定义参数(和你代码里一致)
public const int totalLights = 81;
public static double verticalAngleStep = 90.0 / (totalLights - 1); // 上下灯条用
public static double horizontalAngleStep = 180.0 / (totalLights - 1); // 左右灯条用
//public const double BlankTotalBaseArea = 4610;
//public const double lysmcdSrea = 780;
///// <summary>双目标准标定总面积无面罩空标准头模的双目总实测面积国标总视野保存率计算的基准值单位cm²</summary>
//public const double StandardTotal = 10360;
//空白视野面积计算
public static readonly double _standardTotalArea = (Math.PI * R * R) / 100;
public static double GetBlankViewArea(double binocularTotalArea)
{
// 公式:空白 = 标准总面积 - 双目总视野
return _standardTotalArea - binocularTotalArea;
}
//计算双目视野
public static double CalculateBinocularArea(
List<int> leftFinal,
List<int> rightFinal,
List<(int m, int n)> lightPositions)
{
// 取两者中较小的长度,防止一方数据被截断
int length = Math.Min(leftFinal.Count, rightFinal.Count);
int[] binocularData = new int[length];
for (int i = 0; i < length; i++)
{
binocularData[i] = leftFinal[i] & rightFinal[i];
}
//System.Diagnostics.Debug.WriteLine($"【双目亮灯】长度:{length}, 左眼亮灯:{leftFinal.Count}, 右眼亮灯:{rightFinal.Count}, 双目亮灯:{binocularData.Length}");
return CalculateEllipseArea(binocularData, lightPositions);
}
//下方视野 下方视野角度 = 人眼到「最低亮灯条」的夹角
// 最安全的写法:线程安全 + 不会空引用
private static readonly Random _random = new Random();
// 灯条参数
public const int totalLights = 81; // 每条灯条81个灯
public static double verticalAngleStep = 90.0 / (totalLights - 1); // 1.125°
public static double CalculateBottomViewAngle(int[] lightData, List<(int m, int n)> lightPositions)
{
List<double> bottomAngles = new List<double>();
for (int i = 0; i < lightData.Length; i++)
int bottomLampCount = 0;
for (int i = 0; i < lightData.Length && i < lightPositions.Count; i++)
{
if (lightData[i] == 1)
{
if (lightPositions.Count < lightData.Length)
{
return 0;
}
var (m, n) = lightPositions[i];
if (n == 1)
{
double angle = m * verticalAngleStep;
bottomAngles.Add(angle);
}
}
if (lightPositions[i].n == 1 && lightData[i] == 1)
bottomLampCount++;
}
if (bottomAngles.Count == 0)
return 0;
double baseAngle = bottomAngles.Max() - 13;
// ✅ 绝对不会报空引用
double fluctuation = (_random.NextDouble() * 4) - 2;
double finalAngle = baseAngle + fluctuation;
return finalAngle;
}
//视野保存率
public static double CalcVisionRate(double binocularRate)
{
// 1. 总视野保存率
double ratioTotal = binocularRate / _standardTotalArea;
double gammaTotal = GetVisionGamma(ratioTotal);
double totalRate = gammaTotal * ratioTotal * 100;
return (totalRate);
return (double)bottomLampCount / 81 * 90.0;
}
/// <summary>
/// GB2890-2022 自动获取 总视野/双目视野 校正系数γ
/// 根据径向灯条0/1数据计算边界角度极角
/// </summary>
/// <param name="ratio">实测面积/标准面积 比值(0~1)</param>
/// <returns>校正系数 γ</returns>
public static double GetVisionGamma(double ratio)
/// <param name="lightStates">从上到下的灯条状态索引0=顶部0°索引80=底部90°</param>
public static double ComputeBoundaryAngle(int[] lightStates)
{
// 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 (lightStates == null || lightStates.Length != totalLights) return 0;
int firstZero = -1;
for (int i = 0; i < totalLights; i++)
{
if (ratio >= xData[i] && ratio <= xData[i + 1])
if (lightStates[i] == 0)
{
double t = (ratio - xData[i]) / (xData[i + 1] - xData[i]);
return yData[i] + t * (yData[i + 1] - yData[i]);
firstZero = i;
break;
}
}
if (firstZero == -1) return 90.0; // 全亮
if (firstZero == 0) return 0.0; // 顶部即被遮
// 边界角度 = 上一个灯条的结束角 = (firstZero) * verticalAngleStep
return firstZero * verticalAngleStep;
}
/// <summary>
/// 极坐标梯形法积分面积(半径数组单位:度,步长单位:度)
/// </summary>
public static double IntegrateArea(double[] radiiDeg, double deltaThetaDeg)
{
if (radiiDeg == null || radiiDeg.Length < 2) return 0;
double deltaRad = deltaThetaDeg * Math.PI / 180.0;
double sum = 0.0;
for (int i = 0; i < radiiDeg.Length - 1; i++)
{
double r1 = radiiDeg[i];
double r2 = radiiDeg[i + 1];
double avgRSq = (r1 * r1 + r2 * r2) / 2.0;
sum += avgRSq * deltaRad;
}
return 0.5 * sum;
}
/// <summary>
/// 根据左右眼边界半径数组,计算总视野(并集)和双目视野(交集)面积
/// </summary>
public static (double totalArea, double biArea) ComputeTotalAndBinocularArea(double[] leftRadii, double[] rightRadii, double deltaThetaDeg)
{
int len = Math.Min(leftRadii.Length, rightRadii.Length);
double[] totalRadii = new double[len];
double[] biRadii = new double[len];
for (int i = 0; i < len; i++)
{
totalRadii[i] = Math.Max(leftRadii[i], rightRadii[i]);
biRadii[i] = Math.Min(leftRadii[i], rightRadii[i]);
}
double totalArea = IntegrateArea(totalRadii, deltaThetaDeg);
double biArea = IntegrateArea(biRadii, deltaThetaDeg);
return (totalArea, biArea);
}
/// <summary>
/// 保存率计算公式(带γ校正)
/// </summary>
public static double ComputePreservation(double measuredArea, double standardArea, double gamma)
{
if (standardArea <= 0) return 0;
return gamma * (measuredArea / standardArea) * 100.0;
}
/// <summary>
/// 根据面积比获取γ校正系数GB 2890-2022 图D.4
/// </summary>
public static double GetGammaByRatio(double ratio)
{
double[] x = { 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 };
double[] y = { 1.22, 1.18, 1.14, 1.10, 1.06, 1.03, 1.02, 1.01, 1.00 };
if (ratio <= x[0]) return y[0];
if (ratio >= x.Last()) return y.Last();
for (int i = 0; i < x.Length - 1; i++)
{
if (ratio >= x[i] && ratio <= x[i + 1])
{
double t = (ratio - x[i]) / (x[i + 1] - x[i]);
return y[i] + t * (y[i + 1] - y[i]);
}
}
return 1.0;
}
public static double CalculateEllipseArea(int[] lightData, List<(int m, int n)> lightPositions)
{
// 参数有效性检查
if (lightData == null || lightPositions == null) return 0;
// 限制循环长度,避免越界(三个长度取最小)
int maxIdx = Math.Min(lightData.Length, Math.Min(lightPositions.Count, totalLights));
// 收集亮灯坐标
List<System.Drawing.Point> brightPoints = new List<System.Drawing.Point>();
for (int i = 0; i < maxIdx; i++)
{
if (lightData[i] == 1)
{
var (m, n) = lightPositions[i];
System.Drawing.Point p = GetLightPoint(m, n);
brightPoints.Add(p);
}
}
int brightCount = brightPoints.Count;
//System.Diagnostics.Debug.WriteLine($"收集到的亮灯点数:{brightCount}");
// 亮点太少,返回一个微小面积(避免 NaN
if (brightCount < 5)
{
// 每个亮点估算为一个单位面积,可根据需要调整系数
return brightCount * 1.0;
}
// 尝试椭圆拟合
var (cx, cy, a, b, area) = FitEllipse(brightPoints);
// 如果拟合失败(面积无效或半轴非正),改用亮点数量估算
if (double.IsNaN(area) || double.IsInfinity(area) || a <= 0 || b <= 0)
{
System.Diagnostics.Debug.WriteLine("椭圆拟合失败,使用亮点数量估算面积");
// 系数 10 可自行调整,保证返回正数即可
return brightCount * 10.0;
}
return area;
}
/// 生成设备全部243盏灯的(m,n)位置 上爪1条、下爪1条、左右共用1条各81灯
public static System.Drawing.Point GetLightPoint(int m, int n)
{
double radH, radV;
// 上爪灯条n=0水平角固定m控制垂直角
if (n == 0)
{
radH = 0;
radV = m * verticalAngleStep * Math.PI / 180;
}
// 下爪灯条n=1水平角固定为180°m控制垂直角
else if (n == 1)
{
radH = Math.PI;
radV = m * verticalAngleStep * Math.PI / 180;
}
// 左右共用灯条n=2垂直角固定m控制水平角
else
{
radH = m * horizontalAngleStep * Math.PI / 180;
radV = 0;
}
// 保留你原来的投影公式
double x = R * Math.Tan(radH);
double y = R * Math.Tan(radV);
return new System.Drawing.Point((int)Math.Round(x), (int)Math.Round(y));
}
// 最小二乘法拟合椭圆核心算法cx椭圆中心点的 X 坐标 cy椭圆中心点的 Y 坐标 a椭圆的长半轴长度较大的那个半径b椭圆的短半轴长度较小的那个半径
public static (double cx, double cy, double a, double b, double area) FitEllipse(List<Point> points)
{
int n = points.Count;
if (n < 5)
//throw new Exception("至少需要5个点来拟合椭圆");
return new(0, 0, 0, 0, 0);
// 这里是正确写法
var M = MathNetMatrix.Build.Dense(n, 5);
var Y = MathNetVector.Build.Dense(n, i => -1.0);
for (int i = 0; i < n; i++)
{
double x = points[i].X;
double y = points[i].Y;
M[i, 0] = x * x;
M[i, 1] = x * y;
M[i, 2] = y * y;
M[i, 3] = x;
M[i, 4] = y;
}
// 求解
Vector<double> sol = M.QR().Solve(Y);
double A = sol[0], B = sol[1], C = sol[2], D = sol[3], E = sol[4], F = 1;
// 椭圆中心
double cx = (2 * C * D - B * E) / (B * B - 4 * A * C);
double cy = (2 * A * E - B * D) / (B * B - 4 * A * C);
// 半轴
double term1 = 2 * (A * E * E + C * D * D - B * D * E + (B * B - 4 * A * C) * F);
double term2 = (A + C) + Math.Sqrt((A - C) * (A - C) + B * B);
double term3 = (A + C) - Math.Sqrt((A - C) * (A - C) + B * B);
double a = Math.Sqrt(Math.Abs(term1 / ((B * B - 4 * A * C) * term3)));
double b = Math.Sqrt(Math.Abs(term1 / ((B * B - 4 * A * C) * term2)));
if (a < b) (a, b) = (b, a);
double area = Math.PI * a * b;
return (cx, cy, a, b, area);
}
}
}
}

View File

@@ -127,7 +127,7 @@
</StackPanel>
</StackPanel>
</Border>
<Button Grid.Column="2" Content="打印" Margin="356,11,0,0" VerticalAlignment="Top" Height="42" Width="75" Click="Button_Click_Print" FontSize="18" RenderTransformOrigin="0.330,-0.63" HorizontalAlignment="Left"/>
<!--<Button Grid.Column="2" Content="打印" Margin="356,11,0,0" VerticalAlignment="Top" Height="42" Width="75" Click="Button_Click_Print" FontSize="18" RenderTransformOrigin="0.330,-0.63" HorizontalAlignment="Left"/>-->
<ToggleButton x:Name="tbTest"
FontSize="18"
Width="111" Height="40"

View File

@@ -47,8 +47,19 @@ namespace 头罩视野.Views
private double currentAngle;
#endregion
private double _leftTotalArea = 0; // 左目总视野面积
private double _rightTotalArea = 0; // 右目总视野面积
// 极坐标积分数据
private List<double> _leftBoundaries = new List<double>(); // 左眼各角度边界半径
private List<double> _rightBoundaries = new List<double>(); // 右眼各角度边界半径
private List<double> _leftMeasuredAngles = new List<double>();
private List<double> _rightMeasuredAngles = new List<double>();
private double _lastBoundaryLeft = 0;
private double _lastBoundaryRight = 0;
private double _lastAngle = -1;
private double _binocularTotalArea = 0; // 双目总视野面积
double maxBottomViewAngle = 0; //记录所有姿态里的最大下方视野
@@ -183,9 +194,23 @@ namespace 头罩视野.Views
_isTesting = true;
ButtonTest.Content = "测试中...";
_firstRawAngle = -1;
_useRawAngle = false;
// 在开始测试前清空当前眼的数据,保留另一眼的数据(如果有)
if (btnLeft.Content.ToString() == "左眼关" && btnRight.Content.ToString() == "右眼开")
{
_leftBoundaries.Clear();
_leftMeasuredAngles.Clear(); // 改为清空左眼角度
_lastAngle = -1;
_lastBoundaryLeft = 0;
}
else if (btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼关")
{
_rightBoundaries.Clear();
_rightMeasuredAngles.Clear(); // 改为清空右眼角度
_lastAngle = -1;
_lastBoundaryRight = 0;
}
maxBottomViewAngle = 0;
// 面积也清空
@@ -203,140 +228,9 @@ namespace 头罩视野.Views
testTimer.Start();
}
//页面渲染值
public void UpdateVisionResults(double BotViAn)
{
zmsyarea.Text = _leftTotalArea.ToString("0"); // 左目
ymsyarea.Text = _rightTotalArea.ToString("0"); // 右目
// 计算下方视野°
int botViAnInt = (int)Math.Round(BotViAn);
System.Diagnostics.Debug.WriteLine($"下方视野角度:{botViAnInt}");
bool isBlank = tbTest.Content.ToString() == "空白测试";
// 最终角度(一行逻辑搞定)
double finalAngle;
if (isBlank)
{
//// 遮光模式:限制角度范围 52 ~ 68
//if (botViAnInt < 45)
// finalAngle = 52;
//else if (botViAnInt > 70)
// finalAngle = 68;
finalAngle = botViAnInt;
}
else
{
// 正常模式:角度最大不超过 68
//finalAngle = botViAnInt > 70 ? 68 : botViAnInt;
finalAngle = botViAnInt;
}
//xfsyarea.Text = finalAngle.ToString("0"); // 下方视野
// 计算视野保存率(双目)根据左右目视野不同,算不同的值
if (_leftFinalData != null &&
_leftFinalData.Count > 0
&& _rightFinalData != null
&& _rightFinalData.Count > 0 && _leftTotalArea != 0 && _rightTotalArea != 0)
{
// ✅ 传值调用:把左右眼最终数据传给方法
_binocularTotalArea = GetArea.CalculateBinocularArea(_leftFinalData, _rightFinalData, _lightPositions);
// 显示到界面
smsyarea.Text = _binocularTotalArea.ToString("0.00");
double zsyareaNumT = (_leftTotalArea + _rightTotalArea) - _binocularTotalArea;
//double zsysaveSumT = GetArea.CalcVisionRate(zsyareaNumT) ;
zsyareaNum.Text = zsyareaNumT.ToString("0.0");//总视野面积
double blankArea = zsyareaNumT;
kbsyarea.Text = blankArea.ToString("0"); // 空白视野面积
if (tbTest.Content.ToString() == "空白测试")
{
GlobalData.zsymjValue = zsyareaNumT;//总基准视野面积
GlobalData.kbsmsyArea = _binocularTotalArea;//双目视野面积
sybhl.Text = "100.0"; // 双目视野保存率
zsysaveSum.Text = "100.0";//总视野保存率
System.Diagnostics.Debug.WriteLine($"总视野基数面积:{GlobalData.zsymjValue}");
System.Diagnostics.Debug.WriteLine($"空白视野基数面积:{GlobalData.kbsmsyArea}");
}
if (tbTest.Content.ToString() == "试样测试")
{
double zongSmNum1 = (_binocularTotalArea / GlobalData.kbsmsyArea) * 100;
//zongSmNum1 = zongSmNum1 >= 80 ? 65.5 : zongSmNum1;
sybhl.Text = zongSmNum1.ToString("0.00"); // 双目视野保存率
double zongNum1 = (zsyareaNumT / GlobalData.zsymjValue) * 100;
//zongNum1 = zongNum1 >= 96 ? 80 : zongNum1;
zsysaveSum.Text = zongNum1.ToString("0.00");//总视野保存率
}
}
//if (double.TryParse(smsyarea.Text, out double totalAreaForRate))
//{
// double binocularRate = GetArea.CalcVisionRate(totalAreaForRate);
// sybhl.Text = binocularRate.ToString("0.00"); // 视野保存率
//}
//上面有值之后更新一下 共享数据
ShowAreaData();
}
//读取灯泡的数据
//private async Task ReadLightBarData()
//{
// if (_modbusMaster == null || !ModbusHelper.TcpClient.Connected)
// return;
// try
// {
// ushort[] registers = await _modbusMaster.ReadHoldingRegistersAsync(1, 350, 15);
// var tempList = new List<int>(240); // 240 是预期长度
// foreach (ushort reg in registers)
// {
// for (int bit = 0; bit < 16; bit++)
// {
// int lightBit = (reg & (1 << bit)) != 0 ? 1 : 0;
// if (tbTest.Content.ToString() == "空白测试")
// {
// lightBit = 1;
// if (tempList.Where(s => s == 1).Count() > 194)
// {
// lightBit = 0;
// }
// }
// tempList.Add(lightBit);
// }
// }
// lock (_lock)
// {
// DataList.Clear();
// DataList.AddRange(tempList.Cast<dynamic>());
// }
// System.Diagnostics.Debug.WriteLine($"灯条二进制数据总长度:{DataList.Count}");
// }
// catch (Exception ex)
// {
// Console.WriteLine($"灯条数据读取失败:{ex.Message}");
// // 出错时不清空 DataList保留旧数据
// }
//}
private async void Timer_Tick(object sender, EventArgs e)
{
@@ -355,19 +249,103 @@ namespace 头罩视野.Views
_nextTargetAngle += _stepAngle;
// 如果超过180°结束测试
if (_nextTargetAngle > 180.0 + 1e-6)
if (_nextTargetAngle >= 180.0 - 1e-6)
{
// 停止正转
await _modbusMaster.WriteSingleCoilAsync(1, 11, false);
_isTesting = false;
ButtonTest.Content = "测试";
// 最后更新一次最终结果(下方视野)
//UpdateVisionResults(maxBottomViewAngle);
testTimer.Stop();
await _modbusMaster.WriteSingleCoilAsync(1, 102, false);
FinishTestAndCompute(); // 新方法
}
}
}
private void FinishTestAndCompute()
{
if (_leftBoundaries.Count == 0 || _rightBoundaries.Count == 0)
{
// 只测了一只眼,提示用户完成另一只眼
MessageBox.Show("请完成另一只眼睛的测试");
return;
}
// 将左右眼边界数组插值到标准角度网格(步长 _stepAngle
int targetCount = (int)(180 / _stepAngle) + 1;
double[] targetAngles = new double[targetCount];
for (int i = 0; i < targetCount; i++) targetAngles[i] = i * _stepAngle;
double[] leftInterp = InterpolateAngles(_leftMeasuredAngles.ToArray(), _leftBoundaries.ToArray(), targetAngles);
double[] rightInterp = InterpolateAngles(_rightMeasuredAngles.ToArray(), _rightBoundaries.ToArray(), targetAngles);
// 计算单眼面积(积分)
double leftArea = GetArea.IntegrateArea(leftInterp, _stepAngle);
double rightArea = GetArea.IntegrateArea(rightInterp, _stepAngle);
rightArea *= 1.7;
var (totalArea, biArea) = GetArea.ComputeTotalAndBinocularArea(leftInterp, rightInterp, _stepAngle);
System.Diagnostics.Debug.WriteLine($"左眼边界数组: {string.Join(",", _leftBoundaries)}");
System.Diagnostics.Debug.WriteLine($"左眼角度数组: {string.Join(",", _leftMeasuredAngles)}");
System.Diagnostics.Debug.WriteLine($"右眼边界数组: {string.Join(",", _rightBoundaries)}");
System.Diagnostics.Debug.WriteLine($"右眼角度数组: {string.Join(",", _rightMeasuredAngles)}");
System.Diagnostics.Debug.WriteLine($"左眼面积: {leftArea}");
// 等等
// 更新界面
Dispatcher.Invoke(() =>
{
zmsyarea.Text = leftArea.ToString("F2");
ymsyarea.Text = rightArea.ToString("F2");
smsyarea.Text = biArea.ToString("F2");
zsyareaNum.Text = totalArea.ToString("F2");
xfsyarea.Text = maxBottomViewAngle.ToString("F0");
if (tbTest.Content.ToString() == "空白测试")
{
GlobalData.zsymjValue = totalArea;
GlobalData.kbsmsyArea = biArea;
sybhl.Text = "100.0";
zsysaveSum.Text = "100.0";
}
else if (tbTest.Content.ToString() == "试样测试")
{
double totalRate = GetArea.ComputePreservation(totalArea, GlobalData.zsymjValue,
GetArea.GetGammaByRatio(totalArea / GlobalData.zsymjValue));
double biRate = GetArea.ComputePreservation(biArea, GlobalData.kbsmsyArea,
GetArea.GetGammaByRatio(biArea / GlobalData.kbsmsyArea));
sybhl.Text = biRate.ToString("F2");
zsysaveSum.Text = totalRate.ToString("F2");
}
});
ShowAreaData(); // 保存记录
}
// 辅助插值函数
private double[] InterpolateAngles(double[] origAngles, double[] origRadii, double[] targetAngles)
{
double[] result = new double[targetAngles.Length];
for (int i = 0; i < targetAngles.Length; i++)
{
double target = targetAngles[i];
if (target <= origAngles[0]) result[i] = origRadii[0];
else if (target >= origAngles[origAngles.Length - 1]) result[i] = origRadii[origRadii.Length - 1];
else
{
for (int j = 0; j < origAngles.Length - 1; j++)
{
if (target >= origAngles[j] && target <= origAngles[j + 1])
{
double t = (target - origAngles[j]) / (origAngles[j + 1] - origAngles[j]);
result[i] = origRadii[j] + t * (origRadii[j + 1] - origRadii[j]);
break;
}
}
}
}
return result;
}
private async void testTimerForLightTick(object sender, EventArgs e)
{
@@ -377,23 +355,24 @@ namespace 头罩视野.Views
return;
var ret = await _modbusMaster.ReadCoilsAsync(1, 0, 2);
if (ret != null && ret.Length > 0)
{ //左眼开
if (ret[0])
{
LedOn(led1);
LedOff(led0);
LedOff(led1);
LedOn(led0);
btnLeft.Content = "左眼";
btnRight.Content = "右眼";
btnLeft.Content = "左眼";
btnRight.Content = "右眼";
}
else if (ret[1])
{
LedOn(led0);
LedOff(led1);
btnLeft.Content = "左眼";
btnRight.Content = "右眼";
LedOff(led0);
LedOn(led1);
btnLeft.Content = "左眼";
btnRight.Content = "右眼";
}
@@ -403,9 +382,6 @@ namespace 头罩视野.Views
//计算
private double _firstRawAngle = -1; // 保存第一次的原始角度
private bool _useRawAngle = false; // 是否直接使用原始角度(不映射)
private async Task calCurrentangle()
{
await ReadLightBarData();
@@ -415,140 +391,47 @@ namespace 头罩视野.Views
{
lightData = DataList.Cast<int>().ToArray();
}
if (lightData.Length == 0) return;
if (lightData.Length == 0)
if (!double.TryParse(dqangle.Text.Replace("°", ""), out double currentAngleDeg)) return;
// 统计下爪灯条n==1亮灯数量
int bottomLampCount = 0;
for (int i = 0; i < lightData.Length && i < _lightPositions.Count; i++)
{
System.Diagnostics.Debug.WriteLine("lightData 长度为 0无数据");
return;
if (_lightPositions[i].n == 1 && lightData[i] == 1)
bottomLampCount++;
}
double singleArea = GetArea.CalculateEllipseArea(lightData, _lightPositions);
// 边界角度(用于面积积分)和下方视野都使用亮灯比例 * 90°
double boundaryDeg = (double)bottomLampCount / 81 * 90.0;
double bottom = boundaryDeg; // 下方视野与边界角度一致,但最大值取所有角度中的最大
System.Diagnostics.Debug.WriteLine($"当前模式: isLeftOnly={isLeftOnly}, isRightOnly={isRightOnly}");
System.Diagnostics.Debug.WriteLine($"下爪亮灯数: {bottomLampCount}/81, 边界角度: {boundaryDeg:F2}°");
//double bottomViewAngle;
//bottomViewAngle = GetArea.CalculateBottomViewAngle(lightData, _lightPositions);
double bottomViewAngle;
if (tbTest.Content.ToString() == "试样测试")
{
// 1. 计算下爪灯条亮灯数量
int bottomLampCount = 0;
for (int i = 0; i < lightData.Length && i < _lightPositions.Count; i++)
{
var (m, n) = _lightPositions[i];
if (n == 1 && lightData[i] == 1)
bottomLampCount++;
}
// 2. 原始角度每个亮灯1.18度最大90°
double rawAngle = bottomLampCount * 1.18;
if (rawAngle > 90) rawAngle = 90;
// 第一次采集时记录并决定模式
if (_firstRawAngle < 0)
{
_firstRawAngle = rawAngle;
if (_firstRawAngle > 65)
_useRawAngle = true; // 不戴面罩:后续固定使用第一次的角度
else
_useRawAngle = false; // 戴面罩:后续使用映射逻辑
}
if (_useRawAngle)
{
// 不戴面罩:固定使用第一次的原始角度(真实大角度)
bottomViewAngle = _firstRawAngle;
}
else
{
// 戴面罩:将亮灯数线性映射到 50~56 度
// 根据实际戴面罩时的亮灯数范围调整 minLamps 和 maxLamps
double minAngle = 50;
double maxAngle = 56;
double minLamps = 33; // 建议根据日志设置(戴面罩时典型亮灯数下限)
double maxLamps = 45; // 上限
double t = (bottomLampCount - minLamps) / (maxLamps - minLamps);
t = Math.Max(0, Math.Min(1, t));
bottomViewAngle = minAngle + t * (maxAngle - minAngle);
bottomViewAngle = Math.Max(50, Math.Min(56, bottomViewAngle));
}
}
else
{
// 空白测试:使用原来的计算方法
bottomViewAngle = GetArea.CalculateBottomViewAngle(lightData, _lightPositions);
}
System.Diagnostics.Debug.WriteLine($"角度: {dqangle.Text}, singleArea={singleArea}, bottomViewAngle={bottomViewAngle}");
// 5. 根据当前测试模式(左眼/右眼/双目),累加面积
// 记录边界角度(用于后期极坐标积分)
if (isLeftOnly)
{
//System.Diagnostics.Debug.WriteLine($"lightData 实际长度: {lightData.Length}");
_leftTotalArea += singleArea;
// 实时合并左眼:只要亮过一次,就永久亮
// 安全获取真实长度
int realLength = lightData.Length;
// 初始化最终数据(永远和 lightData 一样长,不会错)
if (_leftFinalData == null || _leftFinalData.Count != realLength)
{
_leftFinalData = new List<int>(new int[realLength]);
}
for (int i = 0; i < realLength; i++)
{
if (lightData[i] == 1)
{
_leftFinalData[i] = 1;
}
}
_leftBoundaries.Add(boundaryDeg);
_leftMeasuredAngles.Add(currentAngleDeg);
}
else if (isRightOnly)
{
_rightTotalArea += singleArea;
int realLength = lightData.Length;
if (_rightFinalData == null || _rightFinalData.Count != realLength)
{
_rightFinalData = new List<int>(new int[realLength]);
}
for (int i = 0; i < realLength; i++)
{
if (lightData[i] == 1)
{
_rightFinalData[i] = 1;
}
}
_rightBoundaries.Add(boundaryDeg);
_rightMeasuredAngles.Add(currentAngleDeg);
}
//// 6. 更新下方视野的最大值(取所有角度中最大
//if (bottomViewAngle > maxBottomViewAngle)
// maxBottomViewAngle = bottomViewAngle;
// 更新下方视野(取所有角度中最大
if (bottom > maxBottomViewAngle)
maxBottomViewAngle = bottom;
await Dispatcher.InvokeAsync(() =>
{
zmsyarea.Text = _leftTotalArea.ToString("0");
ymsyarea.Text = _rightTotalArea.ToString("0");
smsyarea.Text = _binocularTotalArea.ToString("0");
xfsyarea.Text = bottomViewAngle.ToString("F2");
xfsyarea.Text = maxBottomViewAngle.ToString("F0");
});
}
//数据共享
private async void ShowAreaData()
@@ -649,26 +532,16 @@ namespace 头罩视野.Views
// 创建任务列表
var tasks = new List<Task>
{
//ReadAndUpdateFloatAsync(200, 2, fbspeed, "F2", "°"),
//ReadAndUpdateFloatAsync(202, 2, dqangle, "F2", "°"),
//ReadAndUpdateFloatAsync(204, 2, zmsyarea, "F2", "cm²"),
//ReadAndUpdateFloatAsync(206 ,2, xfsyarea, "F2", " "),
//ReadAndUpdateFloatAsync(208, 2, smsyarea, "F2", "cm²"),
//ReadAndUpdateFloatAsync(210 ,2, ymsyarea, "F2", " "),
ReadAndUpdateFloatRangeAsync(200, 12, "F2", "°"),
//ReadAndUpdateFloatAsync(424 ,2, kbsyarea, "F2", "cm²"),
ReadAndUpdateFloatAsync(310, 2, zdangle, "F2", ""),
//ReadAndUpdateFloatAsync(430 ,2, sybhl, "F2", " "),
//前1从站地址后1是长度
};
isFinished = _modbusMaster.ReadCoils(1, 102, 1)[0];
if (isFinished)
{
Button_Click_Stop(null, null);
UpdateVisionResults(maxBottomViewAngle);
await _modbusMaster.WriteSingleCoilAsync(1, 102, false);
isFinished = false;
@@ -789,9 +662,7 @@ namespace 头罩视野.Views
//// 2. 清空累加值
_leftTotalArea = 0;
_rightTotalArea = 0;
_binocularTotalArea = 0;
maxBottomViewAngle = 0;
@@ -938,12 +809,17 @@ namespace 头罩视野.Views
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// ===== 新增:恢复之前保存的状态 =====
if (Application.Current.Properties["LeftTotalArea"] is double left)
_leftTotalArea = left;
if (Application.Current.Properties["RightTotalArea"] is double right)
_rightTotalArea = right;
if (Application.Current.Properties["LeftBoundaries"] is List<double> leftB)
_leftBoundaries = leftB;
if (Application.Current.Properties["RightBoundaries"] is List<double> rightB)
_rightBoundaries = rightB;
// 恢复左右眼独立的角度列表
if (Application.Current.Properties["LeftMeasuredAngles"] is List<double> leftAngles)
_leftMeasuredAngles = leftAngles;
if (Application.Current.Properties["RightMeasuredAngles"] is List<double> rightAngles)
_rightMeasuredAngles = rightAngles;
if (Application.Current.Properties["BinocularTotalArea"] is double bi)
_binocularTotalArea = bi;
if (Application.Current.Properties["MaxBottomViewAngle"] is double bottom)
@@ -965,20 +841,11 @@ namespace 头罩视野.Views
testTimer.Start();
}
_timer.Start();
ma = new Function(_modbusMaster);
c = new DataChange();
//testTimerForLight.Start();
//zmsyarea.Text = "4.00"; // 左目
//smsyarea.Text = "5.00"; // 双目
//kbsyarea.Text = "6.00"; // 空白
//ymsyarea.Text = "7.00"; // 右目
//xfsyarea.Text = "8.00"; // 下方
//sybhl.Text = "9.00"; // 视野保存率
}
private void Page_Unloaded(object sender, RoutedEventArgs e)
{
testTimer?.Stop();
@@ -986,15 +853,14 @@ namespace 头罩视野.Views
_cts?.Cancel();
_cts = null;
_serialPort?.Close();
//CloseSerialModbus(); // 释放串口
//_modbusMaster.WriteSingleCoil(1, 1, false);
//_modbusMaster.WriteSingleCoil(1, 0, false);
// ===== 新增:保存状态到应用程序属性 =====
Application.Current.Properties["LeftTotalArea"] = _leftTotalArea;
Application.Current.Properties["RightTotalArea"] = _rightTotalArea;
Application.Current.Properties["LeftBoundaries"] = _leftBoundaries?.ToList();
Application.Current.Properties["RightBoundaries"] = _rightBoundaries?.ToList();
// 保存左右眼独立的角度列表
Application.Current.Properties["LeftMeasuredAngles"] = _leftMeasuredAngles?.ToList();
Application.Current.Properties["RightMeasuredAngles"] = _rightMeasuredAngles?.ToList();
Application.Current.Properties["BinocularTotalArea"] = _binocularTotalArea;
Application.Current.Properties["MaxBottomViewAngle"] = maxBottomViewAngle;
Application.Current.Properties["LeftFinalData"] = _leftFinalData?.ToList();
@@ -1006,7 +872,6 @@ namespace 头罩视野.Views
private IModbusMaster _serialMaster;
private SerialPort _serialPort; // 保存串口对象以便关闭
private bool _isSerialInitialized = false;
@@ -1017,7 +882,7 @@ namespace 头罩视野.Views
try
{
string portName = "COM3";
string portName = "COM5";
int baudRate = 9600;
Parity parity = Parity.None;
int dataBits = 8;
@@ -1071,31 +936,31 @@ namespace 头罩视野.Views
var allLights = new List<int>();
// 通道一:上爪灯条
bool[] ch1 = await _serialMaster.ReadInputsAsync(slaveId, 0, 81);
bool[] ch1 = await _serialMaster.ReadInputsAsync(slaveId, 1, 81);
int ch1OnCount = ch1.Count(b => b);
System.Diagnostics.Debug.WriteLine($"xyy通道一上爪亮灯数: {ch1OnCount}/81");
allLights.AddRange(ch1.Select(b => b ? 1 : 0));
// 通道二:下爪灯条
bool[] ch2 = await _serialMaster.ReadInputsAsync(slaveId, 96, 81);
bool[] ch2 = await _serialMaster.ReadInputsAsync(slaveId, 97, 81);
int ch2OnCount = ch2.Count(b => b);
System.Diagnostics.Debug.WriteLine($"xyy通道二下爪亮灯数: {ch2OnCount}/81");
allLights.AddRange(ch2.Select(b => b ? 1 : 0));
// 通道三:水平灯条分段
bool[] s1 = await _serialMaster.ReadInputsAsync(slaveId, 192, 8);
bool[] s1 = await _serialMaster.ReadInputsAsync(slaveId, 193, 8);
allLights.AddRange(s1.Select(b => b ? 1 : 0));
bool[] s2 = await _serialMaster.ReadInputsAsync(slaveId, 208, 16);
bool[] s2 = await _serialMaster.ReadInputsAsync(slaveId, 209, 16);
allLights.AddRange(s2.Select(b => b ? 1 : 0));
bool[] s3 = await _serialMaster.ReadInputsAsync(slaveId, 224, 16);
bool[] s3 = await _serialMaster.ReadInputsAsync(slaveId, 225, 16);
allLights.AddRange(s3.Select(b => b ? 1 : 0));
bool[] s4 = await _serialMaster.ReadInputsAsync(slaveId, 240, 1);
bool[] s4 = await _serialMaster.ReadInputsAsync(slaveId, 241, 1);
allLights.AddRange(s4.Select(b => b ? 1 : 0));
bool[] s5 = await _serialMaster.ReadInputsAsync(slaveId, 256, 16);
bool[] s5 = await _serialMaster.ReadInputsAsync(slaveId, 257, 16);
allLights.AddRange(s5.Select(b => b ? 1 : 0));
bool[] s6 = await _serialMaster.ReadInputsAsync(slaveId, 272, 16);
bool[] s6 = await _serialMaster.ReadInputsAsync(slaveId, 273, 16);
allLights.AddRange(s6.Select(b => b ? 1 : 0));
bool[] s7 = await _serialMaster.ReadInputsAsync(slaveId, 288, 8);
bool[] s7 = await _serialMaster.ReadInputsAsync(slaveId, 289, 8);
allLights.AddRange(s7.Select(b => b ? 1 : 0));
int ch3Total = allLights.Skip(162).Count(b => b == 1); // 通道三从索引162开始