From 78b6d61b03a41f6a0bd238c995a95a246ccd50bb Mon Sep 17 00:00:00 2001 From: xyy <544939200@qq.com> Date: Wed, 6 May 2026 19:51:57 +0800 Subject: [PATCH] --- 头罩视野slove/头罩视野/Services/GetArea.cs | 118 ++++++-- 头罩视野slove/头罩视野/Views/PageTest.xaml.cs | 281 ++++++++++-------- 2 files changed, 240 insertions(+), 159 deletions(-) diff --git a/头罩视野slove/头罩视野/Services/GetArea.cs b/头罩视野slove/头罩视野/Services/GetArea.cs index edef976..88a8d90 100644 --- a/头罩视野slove/头罩视野/Services/GetArea.cs +++ b/头罩视野slove/头罩视野/Services/GetArea.cs @@ -107,37 +107,99 @@ namespace 头罩视野.Services } - // 传入:81个灯的亮灭数据(0=灭,1=亮) - // 返回:椭圆面积 + //// 传入:81个灯的亮灭数据(0=灭,1=亮) + //// 返回:椭圆面积 + //public static double CalculateEllipseArea(int[] lightData, List<(int m, int n)> lightPositions) + //{ + // //if (lightData.Length != totalLights || lightPositions.Count != totalLights) + // // throw new Exception("必须是81个灯的数据"); + + // // 第一步:收集所有亮灯坐标 + // List brightPoints = new List(); + // for (int i = 0; i < totalLights; i++) + // { + // if (totalLights > lightData.Count()) + // { + + // } + // if (lightData[i] == 1) + // { + + + // var (m, n) = lightPositions[i]; + // System.Drawing.Point p = GetLightPoint(m, n); + // brightPoints.Add(p); + // } + // } + + // // 第二步:用亮点拟合椭圆 + // var (cx, cy, a, b, area) = FitEllipse(brightPoints); + + // // 返回面积 + // return area; + //} + public static double CalculateEllipseArea(int[] lightData, List<(int m, int n)> lightPositions) +{ + // 日志:方法入口 + System.Diagnostics.Debug.WriteLine($"===== CalculateEllipseArea 开始 ====="); + System.Diagnostics.Debug.WriteLine($"lightData.Length = {lightData?.Length ?? 0}"); + System.Diagnostics.Debug.WriteLine($"lightPositions.Count = {lightPositions?.Count ?? 0}"); + System.Diagnostics.Debug.WriteLine($"totalLights = {totalLights}"); + + // 1. 参数校验 + if (lightData == null || lightPositions == null) + { + System.Diagnostics.Debug.WriteLine("错误:lightData 或 lightPositions 为 null"); + return double.NaN; + } + + if (lightData.Length != totalLights || lightPositions.Count != totalLights) + { + System.Diagnostics.Debug.WriteLine($"数据长度不匹配:lightData.Length={lightData.Length}, totalLights={totalLights}, lightPositions.Count={lightPositions.Count}"); + // 这里可以根据实际需要决定是否抛出异常或使用较小长度继续 + } + + // 2. 收集亮灯坐标 + List brightPoints = new List(); + for (int i = 0; i < totalLights && i < lightData.Length && i < lightPositions.Count; i++) + { + if (lightData[i] == 1) { - //if (lightData.Length != totalLights || lightPositions.Count != totalLights) - // throw new Exception("必须是81个灯的数据"); - - // 第一步:收集所有亮灯坐标 - List brightPoints = new List(); - for (int i = 0; i < totalLights; i++) - { - if (totalLights > lightData.Count()) - { - - } - if (lightData[i] == 1) - { - - - var (m, n) = lightPositions[i]; - System.Drawing.Point p = GetLightPoint(m, n); - brightPoints.Add(p); - } - } - - // 第二步:用亮点拟合椭圆 - var (cx, cy, a, b, area) = FitEllipse(brightPoints); - - // 返回面积 - return area; + var (m, n) = lightPositions[i]; + System.Drawing.Point p = GetLightPoint(m, n); + brightPoints.Add(p); } + } + + System.Diagnostics.Debug.WriteLine($"收集到的亮灯点数:{brightPoints.Count}"); + + if (brightPoints.Count < 5) + { + System.Diagnostics.Debug.WriteLine($"警告:亮灯点不足5个(实际{brightPoints.Count}),无法拟合椭圆,返回 NaN"); + return double.NaN; + } + + // 3. 拟合椭圆 + var (cx, cy, a, b, area) = FitEllipse(brightPoints); + + System.Diagnostics.Debug.WriteLine($"拟合结果:cx={cx}, cy={cy}, a={a}, b={b}, area={area}"); + + if (double.IsNaN(a) || double.IsNaN(b) || double.IsNaN(area)) + { + System.Diagnostics.Debug.WriteLine("错误:拟合结果包含 NaN"); + return double.NaN; + } + + if (a <= 0 || b <= 0) + { + System.Diagnostics.Debug.WriteLine($"错误:椭圆半轴无效(a={a}, b={b}),面积计算将得到 NaN 或 0"); + return double.NaN; + } + + System.Diagnostics.Debug.WriteLine($"最终返回面积:{area}"); + return area; +} /// 生成设备全部243盏灯的(m,n)位置 上爪1条、下爪1条、左右共用1条,各81灯 public static System.Drawing.Point GetLightPoint(int m, int n) { diff --git a/头罩视野slove/头罩视野/Views/PageTest.xaml.cs b/头罩视野slove/头罩视野/Views/PageTest.xaml.cs index a1395c4..18db1ff 100644 --- a/头罩视野slove/头罩视野/Views/PageTest.xaml.cs +++ b/头罩视野slove/头罩视野/Views/PageTest.xaml.cs @@ -75,6 +75,12 @@ namespace 头罩视野.Views private const int HalfLights = (LightsPerStrip - 1) / 2; // 40,给左右灯条用 double stepAngle; + private bool _isTesting = false; // 是否正在测试 + private double _stepAngle = 10.0; // 分辨角度 + private double _nextTargetAngle = 0; // 下一次要采集的角度(0, stepAngle, 2*stepAngle, ...) + + + bool isLeftOnly = false; bool isRightOnly = false; @@ -211,35 +217,83 @@ namespace 头罩视野.Views { ma.BtnClickFunction(Function.ButtonType.复归型, 103); ButtonTest.Content = "测试"; + _isTesting = false; testTimer.Stop(); - - } - //测试btn 测试按钮:读取数据,存入共享列表 + + ////测试btn 测试按钮:读取数据,存入共享列表 + //private async void Button_Click_Test(object sender, RoutedEventArgs e) + //{ + + // ma.BtnClickFunction(Function.ButtonType.复归型, 100); + // ButtonTest.Content = "测试中...."; + // _isTesting = true; + // testTimer.Start(); + // bool isLeftOnly = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼关"; + // bool isRightOnly = btnRight.Content.ToString() == "右眼开" && btnLeft.Content.ToString() == "左眼关"; + // bool isBinocular = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼开"; + // if (isLeftOnly) + // { + // _leftTotalArea = 0; + // } + // else if (isRightOnly) + // { + // _rightTotalArea = 0; + + // } + // else if (isBinocular) + // { + // _binocularTotalArea = 0; + + // } + //} private async void Button_Click_Test(object sender, RoutedEventArgs e) { + if (_isTesting) + { + // 停止测试 + await _modbusMaster.WriteSingleCoilAsync(1, 11, false); + _isTesting = false; + ButtonTest.Content = "测试"; + testTimer.Stop(); + return; + } + + // 1. 获取分辨角度 + if (!double.TryParse(fbspeed.Text.Trim(), out double step)) + { + MessageBox.Show("请输入有效的分辨角度(5~30)"); + return; + } + _stepAngle = Math.Max(1, Math.Min(30, step)); + _nextTargetAngle = _stepAngle; + _isTesting = true; + ButtonTest.Content = "测试中..."; + + // 2. 清空累加值 + _leftTotalArea = 0; + _rightTotalArea = 0; + _binocularTotalArea = 0; + maxBottomViewAngle = 0; + + isLeftOnly = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼关"; + isRightOnly = btnRight.Content.ToString() == "右眼开" && btnLeft.Content.ToString() == "左眼关"; + isBinocular = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼开"; + + + + await calCurrentangle(); + + _nextTargetAngle = _stepAngle; ma.BtnClickFunction(Function.ButtonType.复归型, 100); - ButtonTest.Content = "测试中...."; + + // 6. 启动定时器(每500ms检查一次角度) testTimer.Start(); - bool isLeftOnly = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼关"; - bool isRightOnly = btnRight.Content.ToString() == "右眼开" && btnLeft.Content.ToString() == "左眼关"; - bool isBinocular = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼开"; - if (isLeftOnly) - { - _leftTotalArea = 0; - } - else if (isRightOnly) - { - _rightTotalArea = 0; - - } - else if (isBinocular) - { - _binocularTotalArea = 0; - - } } + + + //页面渲染值 public void UpdateVisionResults(double BotViAn) @@ -270,149 +324,114 @@ namespace 头罩视野.Views private async Task ReadLightBarData() { - // 无连接直接返回 if (_modbusMaster == null || !ModbusHelper.TcpClient.Connected) return; try { - DataList.Clear(); - // 读取灯条寄存器(地址350,长度15) ushort[] registers = await _modbusMaster.ReadHoldingRegistersAsync(1, 350, 15); + var tempList = new List(240); // 240 是预期长度 - // 每个 ushort 是16位,低位在前 foreach (ushort reg in registers) { for (int bit = 0; bit < 16; bit++) { - // 取第bit位的值:0或1 int lightBit = (reg & (1 << bit)) != 0 ? 1 : 0; - lock (_lock) - { - DataList.Add(lightBit); - } - - + tempList.Add(lightBit); } } - // 调试:打印转换后的数据长度 + + lock (_lock) + { + DataList.Clear(); + DataList.AddRange(tempList.Cast()); + } + System.Diagnostics.Debug.WriteLine($"灯条二进制数据总长度:{DataList.Count}"); } catch (Exception ex) { Console.WriteLine($"灯条数据读取失败:{ex.Message}"); - //DataList.Clear(); // 出错时清空数据 + // 出错时不清空 DataList,保留旧数据 } } + private async void Timer_Tick(object sender, EventArgs e) - { - //System.Diagnostics.Debug.WriteLine("定时器触发了!" + DateTime.Now); - // 调用你原来的读取方 - //await ReadAddr262DataAsync(); - await calCurrentangle(); - // 组装当前数据 - var data = new TestDataStore.TestRecord - { - Date = DateTime.Now.ToString("yyyy-MM-dd"), - Time = DateTime.Now.ToString("HH:mm:ss"), - LeftEyeArea = double.TryParse(zmsyarea.Text, out var l) ? l : 0, - RightEyeArea = double.TryParse(ymsyarea.Text, out var r) ? r : 0, - BinocularArea = double.TryParse(smsyarea.Text, out var b) ? b : 0, - LowerVision = double.TryParse(xfsyarea.Text, out var lv) ? lv : 0, - VisionRetentionRate = double.TryParse(sybhl.Text, out var vr) ? vr : 0 - }; + if (!_isTesting) return; // 非测试状态直接返回 + if (!double.TryParse(dqangle.Text.Replace("°", ""), out double currentAngle)) + return; - // ==================== 去重判断 ==================== - if (_lastRecord != null && - data.LeftEyeArea == _lastRecord.LeftEyeArea && - data.RightEyeArea == _lastRecord.RightEyeArea && - data.BinocularArea == _lastRecord.BinocularArea && - data.LowerVision == _lastRecord.LowerVision && - data.VisionRetentionRate == _lastRecord.VisionRetentionRate) + // 判断是否达到下一个目标角度(允许±1°误差) + if (currentAngle >= _nextTargetAngle - 1.0) { - return; // 一样就不添加 + // 采集当前角度的数据 + await calCurrentangle(); + + // 更新下一个目标角度 + _nextTargetAngle += _stepAngle; + + // 如果超过180°,结束测试 + if (_nextTargetAngle > 180.0 + 1e-6) + { + // 停止正转 + await _modbusMaster.WriteSingleCoilAsync(1, 11, false); + _isTesting = false; + ButtonTest.Content = "测试"; + // 最后更新一次最终结果(下方视野) + UpdateVisionResults(maxBottomViewAngle); + + await _modbusMaster.WriteSingleCoilAsync(1, 102, false); + } } - //原来存的数据清空 切换页面会清空 - //TestDataStore.Records.Clear(); - // 不一样 → 插入表格 - TestDataStore.AddNewRecord(data); - _lastRecord = data; - - } - - + /// + /// 采集当前水平角度下的灯条数据,计算该角度的视野面积贡献,并累加到总视野面积中。 + /// 每次调用只采集一次数据(对应一个水平角度,比如 10°、20°…)。 + /// private async Task calCurrentangle() { + await ReadLightBarData(); - double.TryParse(fbspeed.Text.Trim(), out double val); - - //// 范围判断:必须在 0 ~ 180 之间 - //if (val <= 0 || val > 180) - //{ - // stepAngle = 10.0; // 超出范围用默认值 - //} - //else - //{ - // stepAngle = val; // 正常就用输入值 - //} - // 1. 读取输入框 - // 分辨角度 例:10 - - - - // 2. 从0转到180,每 stepAngle 执行一次 - for (double current = stepAngle; current < 180; current += stepAngle) + int[] lightData; + lock (_lock) { - - await ReadLightBarData(); - - // 读完再转数组 - int[] lightData = DataList.Cast().ToArray(); - - if (lightData.Length == 0) - { - return; - } - //开始计算视野面积 - double singleArea = GetArea.CalculateEllipseArea(lightData, _lightPositions); - - if (isLeftOnly) - { - _leftTotalArea += singleArea; - - } - else if (isRightOnly) - { - _rightTotalArea += singleArea; - - } - else if (isBinocular) - { - _binocularTotalArea += singleArea; - - } - - // 单次计算下方视野角度 - double bottomViewAngle = GetArea.CalculateBottomViewAngle(lightData, _lightPositions); - - // 记录所有姿态里的最大/最小值 - if (bottomViewAngle > maxBottomViewAngle) - { - maxBottomViewAngle = bottomViewAngle; - } - - // 如果你要取最小值,用下面这段 - // if (bottomViewAngle < minBottomViewAngle && bottomViewAngle > 0) - // { - // minBottomViewAngle = bottomViewAngle; - // } + lightData = DataList.Cast().ToArray(); } - } + if (lightData.Length == 0) + { + System.Diagnostics.Debug.WriteLine("lightData 长度为 0,无数据"); + return; + } + + double singleArea = GetArea.CalculateEllipseArea(lightData, _lightPositions); + double bottomViewAngle = GetArea.CalculateBottomViewAngle(lightData, _lightPositions); + + System.Diagnostics.Debug.WriteLine($"角度: {dqangle.Text}, singleArea={singleArea}, bottomViewAngle={bottomViewAngle}"); + + // 5. 根据当前测试模式(左眼/右眼/双目),累加面积 + if (isLeftOnly) + _leftTotalArea += singleArea; + else if (isRightOnly) + _rightTotalArea += singleArea; + else if (isBinocular) + _binocularTotalArea += singleArea; + + // 6. 更新下方视野的最大值(取所有角度中最大的) + if (bottomViewAngle > maxBottomViewAngle) + maxBottomViewAngle = bottomViewAngle; + + await Dispatcher.InvokeAsync(() => + { + zmsyarea.Text = _leftTotalArea.ToString("0.00"); + ymsyarea.Text = _rightTotalArea.ToString("0.00"); + smsyarea.Text = _binocularTotalArea.ToString("0.00"); + xfsyarea.Text = maxBottomViewAngle.ToString("0.0"); + }); + } //打印 private void Button_Click_Print(object sender, RoutedEventArgs e) @@ -564,8 +583,8 @@ namespace 头罩视野.Views Dispatcher.Invoke(() => { - fbspeed.Text = value.ToString(format) + unit; - dqangle.Text = value2.ToString(format) + unit; + fbspeed.Text = value.ToString(format); + dqangle.Text = value2.ToString(format); //zmsyarea.Text = value3.ToString(format) + unit; //xfsyarea.Text = value4.ToString(format) + unit; //smsyarea.Text = value5.ToString(format) + unit;