This commit is contained in:
@@ -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<System.Drawing.Point> brightPoints = new List<System.Drawing.Point>();
|
||||
// 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<System.Drawing.Point> brightPoints = new List<System.Drawing.Point>();
|
||||
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<System.Drawing.Point> brightPoints = new List<System.Drawing.Point>();
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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<int>(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<dynamic>());
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 采集当前水平角度下的灯条数据,计算该角度的视野面积贡献,并累加到总视野面积中。
|
||||
/// 每次调用只采集一次数据(对应一个水平角度,比如 10°、20°…)。
|
||||
/// </summary>
|
||||
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<int>().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<int>().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;
|
||||
|
||||
Reference in New Issue
Block a user