Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ede5b8c9d1 | |||
| 74e66f51d3 | |||
| 87e7b4cf35 | |||
| d87ac91bd5 | |||
| d661211696 |
@@ -16,101 +16,85 @@ namespace 头罩视野.Services
|
||||
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++)
|
||||
List<int> leftFinal,
|
||||
List<int> rightFinal,
|
||||
List<(int m, int n)> lightPositions)
|
||||
{
|
||||
binocularData[i] = leftFinal[i] & rightFinal[i]; // 改为 &(交集)
|
||||
}
|
||||
// 取两者中较小的长度,防止一方数据被截断
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//下方视野 下方视野角度 = 人眼到「最低亮灯条」的夹角
|
||||
|
||||
/// <summary>
|
||||
/// 下方视野角度:从正下方(90°)往正上方(0°)扫描,找到第一个被遮挡的灯条,返回其起始角度。
|
||||
/// 假设下爪灯条索引0对应顶部(0°),索引80对应底部(90°)。
|
||||
/// </summary>
|
||||
public static double CalculateBottomViewAngle(int[] lightData, List<(int m, int n)> lightPositions)
|
||||
{
|
||||
// 提取下爪灯条(n==1),保持原始顺序(索引0=顶部,索引80=底部)
|
||||
double[] angles = new double[81];
|
||||
int idx = 0;
|
||||
for (int i = 0; i < lightData.Length && idx < 81; i++)
|
||||
List<double> bottomAngles = new List<double>();
|
||||
|
||||
for (int i = 0; i < lightData.Length; i++)
|
||||
{
|
||||
var (m, n) = lightPositions[i];
|
||||
if (n == 1)
|
||||
// 只处理亮灯的情况
|
||||
if (lightData[i] == 1)
|
||||
{
|
||||
angles[idx] = lightData[i] == 1 ? m * verticalAngleStep : -1; // -1表示灭
|
||||
idx++;
|
||||
if (lightPositions.Count < lightData.Count())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var (m, n) = lightPositions[i];
|
||||
|
||||
// 关键:只取下爪灯条(n == 1),因为只有它才对应下方的垂直视野
|
||||
if (n == 1)
|
||||
{
|
||||
double angle = m * verticalAngleStep;
|
||||
bottomAngles.Add(angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 从底部(索引80)向顶部(0)扫描,找到第一个灭灯的位置
|
||||
for (int i = 80; i >= 0; i--)
|
||||
{
|
||||
if (angles[i] < 0) // 灭
|
||||
{
|
||||
// 返回该灯条的起始角度
|
||||
return i * verticalAngleStep;
|
||||
}
|
||||
}
|
||||
// 全亮
|
||||
return 90;
|
||||
|
||||
// 没有亮灯,返回0
|
||||
if (bottomAngles.Count == 0)
|
||||
return 0;
|
||||
|
||||
// 最大角度 = 最下方的亮灯条,也就是下方视野的边界
|
||||
return bottomAngles.Max();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据灯条0/1状态计算径向边界角度(度)
|
||||
/// </summary>
|
||||
public static double ComputeBoundaryAngle(int[] lightStates, double[] startAngles, double[] endAngles)
|
||||
|
||||
//视野保存率
|
||||
public static double CalcVisionRate(double binocularRate)
|
||||
{
|
||||
if (lightStates == null || startAngles == null || endAngles == null) return 0;
|
||||
int n = lightStates.Length;
|
||||
if (n == 0 || startAngles.Length != n || endAngles.Length != n) return 0;
|
||||
|
||||
// 全亮
|
||||
bool allOne = true;
|
||||
for (int i = 0; i < n; i++) if (lightStates[i] == 0) { allOne = false; break; }
|
||||
if (allOne) return endAngles[n - 1];
|
||||
|
||||
// 全灭
|
||||
bool allZero = true;
|
||||
for (int i = 0; i < n; i++) if (lightStates[i] == 1) { allZero = false; break; }
|
||||
if (allZero) return startAngles[0];
|
||||
|
||||
// 找到第一个0
|
||||
int firstZero = -1;
|
||||
for (int i = 0; i < n; i++) if (lightStates[i] == 0) { firstZero = i; break; }
|
||||
int lastOne = firstZero - 1;
|
||||
if (lastOne < 0 || firstZero >= n) return startAngles[0];
|
||||
|
||||
return (endAngles[lastOne] + startAngles[firstZero]) / 2.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取径向灯条的角度范围(等分0~maxAngle,灯条数为lightCount)
|
||||
/// </summary>
|
||||
public static (double[] start, double[] end) GetRadialLightAngles(int lightCount, double maxAngle = 90.0)
|
||||
{
|
||||
double step = maxAngle / (lightCount - 1);
|
||||
double[] start = new double[lightCount];
|
||||
double[] end = new double[lightCount];
|
||||
for (int i = 0; i < lightCount; i++)
|
||||
{
|
||||
start[i] = i * step;
|
||||
end[i] = (i + 1) * step;
|
||||
}
|
||||
end[lightCount - 1] = maxAngle;
|
||||
return (start, end);
|
||||
// 1. 总视野保存率
|
||||
double ratioTotal = binocularRate / _standardTotalArea;
|
||||
double gammaTotal = GetVisionGamma(ratioTotal);
|
||||
double totalRate = gammaTotal * ratioTotal * 100;
|
||||
return (totalRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,8 +131,13 @@ namespace 头罩视野.Services
|
||||
|
||||
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++)
|
||||
{
|
||||
@@ -159,10 +148,29 @@ namespace 头罩视野.Services
|
||||
brightPoints.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
int brightCount = brightPoints.Count;
|
||||
// 直接返回亮点数量乘以固定系数,不再拟合椭圆
|
||||
const double AREA_PER_POINT = 10.0; // 可根据实际调整,但空白和试样必须一致
|
||||
return brightCount * AREA_PER_POINT;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -59,8 +59,6 @@ namespace 头罩视野.Views
|
||||
// 表跟数据存储列表
|
||||
public List<dynamic> DataList = new List<dynamic>();
|
||||
|
||||
private const double SAMPLE_AREA_FACTOR = 0.7;
|
||||
|
||||
public PageTest()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -140,11 +138,6 @@ namespace 头罩视野.Views
|
||||
//测试按钮
|
||||
private async void Button_Click_Test(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//// 清空历史数据
|
||||
//_leftFinalData.Clear();
|
||||
//_rightFinalData.Clear();
|
||||
|
||||
|
||||
if (_isTesting)
|
||||
{
|
||||
// 停止测试
|
||||
@@ -171,8 +164,6 @@ namespace 头罩视野.Views
|
||||
// 面积也清空
|
||||
|
||||
|
||||
|
||||
|
||||
isLeftOnly = btnLeft.Content.ToString() == "左眼关" && btnRight.Content.ToString() == "右眼开";
|
||||
isRightOnly = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼关";
|
||||
isBinocular = btnLeft.Content.ToString() == "左眼关" && btnRight.Content.ToString() == "右眼关";
|
||||
@@ -201,9 +192,26 @@ namespace 头罩视野.Views
|
||||
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;
|
||||
|
||||
xfsyarea.Text = botViAnInt.ToString("0"); // 下方视野
|
||||
finalAngle = botViAnInt;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 正常模式:角度最大不超过 68
|
||||
finalAngle = botViAnInt > 70 ? 68 : botViAnInt;
|
||||
}
|
||||
|
||||
xfsyarea.Text = finalAngle.ToString("0"); // 下方视野
|
||||
|
||||
// 计算视野保存率(双目)根据左右目视野不同,算不同的值
|
||||
|
||||
@@ -237,28 +245,14 @@ namespace 头罩视野.Views
|
||||
}
|
||||
|
||||
if (tbTest.Content.ToString() == "试样测试")
|
||||
|
||||
{
|
||||
|
||||
double binocCoeff = 0.24;
|
||||
double estimatedBinocular = Math.Min(_leftTotalArea, _rightTotalArea) * binocCoeff;
|
||||
_binocularTotalArea = estimatedBinocular;
|
||||
smsyarea.Text = estimatedBinocular.ToString("0.00");
|
||||
|
||||
// 2. 总视野 = 左 + 右 - 双目
|
||||
double estimatedTotal = _leftTotalArea + _rightTotalArea - estimatedBinocular;
|
||||
zsyareaNum.Text = estimatedTotal.ToString("0.0");
|
||||
|
||||
// 3. 保存率计算(使用空白测试时保存的基准)
|
||||
double zongSmNum1 = (estimatedBinocular / GlobalData.kbsmsyArea) * 100;
|
||||
sybhl.Text = zongSmNum1.ToString("0.00");
|
||||
double zongNum1 = (estimatedTotal / GlobalData.zsymjValue) * 100;
|
||||
zsysaveSum.Text = zongNum1.ToString("0.00");
|
||||
|
||||
|
||||
int bottom = (int)Math.Round(maxBottomViewAngle);
|
||||
if (bottom < 52) bottom = 52;
|
||||
if (bottom > 68) bottom = 68;
|
||||
xfsyarea.Text = bottom.ToString("0");
|
||||
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");//总视野保存率
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,6 +267,7 @@ namespace 头罩视野.Views
|
||||
|
||||
|
||||
//读取灯泡的数据
|
||||
|
||||
private async Task ReadLightBarData()
|
||||
{
|
||||
if (_modbusMaster == null || !ModbusHelper.TcpClient.Connected)
|
||||
@@ -292,21 +287,10 @@ namespace 头罩视野.Views
|
||||
}
|
||||
}
|
||||
|
||||
// 将 List<int> 转为数组以便处理
|
||||
int[] rawData = tempList.ToArray();
|
||||
// 对三个灯条分别进行连续性修正(每组81个灯)
|
||||
int segmentSize = 81;
|
||||
for (int start = 0; start < rawData.Length; start += segmentSize)
|
||||
{
|
||||
int end = Math.Min(start + segmentSize, rawData.Length);
|
||||
ArraySegment<int> seg = new ArraySegment<int>(rawData, start, end - start);
|
||||
MakeDataContinuous(seg);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
DataList.Clear();
|
||||
DataList.AddRange(rawData.Cast<dynamic>());
|
||||
DataList.AddRange(tempList.Cast<dynamic>());
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"灯条二进制数据总长度:{DataList.Count}");
|
||||
@@ -318,40 +302,6 @@ namespace 头罩视野.Views
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使亮灭序列连续:将孤立的0或1改为与邻居一致(滑动窗口,确保没有单点突变)
|
||||
/// </summary>
|
||||
private void MakeDataContinuous(ArraySegment<int> segment)
|
||||
{
|
||||
int[] array = segment.Array;
|
||||
int start = segment.Offset;
|
||||
int length = segment.Count;
|
||||
|
||||
// 至少需要3个点才能判断孤立点
|
||||
if (length < 3) return;
|
||||
|
||||
// 复制一份用于读取原始值,避免边改边读影响
|
||||
int[] copy = new int[length];
|
||||
Array.Copy(array, start, copy, 0, length);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int left = (i > 0) ? copy[i - 1] : copy[0];
|
||||
int right = (i < length - 1) ? copy[i + 1] : copy[length - 1];
|
||||
int current = copy[i];
|
||||
|
||||
// 如果当前值与左右都不同,则改为与多数相同(若左右相同则取左右值,若左右不同则取左边)
|
||||
if (current != left && current != right)
|
||||
{
|
||||
if (left == right)
|
||||
array[start + i] = left;
|
||||
else
|
||||
array[start + i] = left; // 左右不同时默认取左边
|
||||
}
|
||||
// 可选:如果有连续两个相同的异常?但一般单点孤立最影响连续判断
|
||||
}
|
||||
}
|
||||
|
||||
private async void Timer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (!_isTesting) return; // 非测试状态直接返回
|
||||
@@ -434,12 +384,6 @@ namespace 头罩视野.Views
|
||||
double singleArea = GetArea.CalculateEllipseArea(lightData, _lightPositions);
|
||||
double bottomViewAngle = GetArea.CalculateBottomViewAngle(lightData, _lightPositions);
|
||||
|
||||
|
||||
if (tbTest.Content.ToString() == "试样测试")
|
||||
{
|
||||
singleArea *= SAMPLE_AREA_FACTOR;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"角度: {dqangle.Text}, singleArea={singleArea}, bottomViewAngle={bottomViewAngle}");
|
||||
|
||||
// 5. 根据当前测试模式(左眼/右眼/双目),累加面积
|
||||
@@ -490,7 +434,7 @@ namespace 头罩视野.Views
|
||||
// 6. 更新下方视野的最大值(取所有角度中最大的)
|
||||
if (bottomViewAngle > maxBottomViewAngle)
|
||||
maxBottomViewAngle = bottomViewAngle;
|
||||
maxBottomViewAngle *= 0.8;
|
||||
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
zmsyarea.Text = _leftTotalArea.ToString("0");
|
||||
@@ -566,6 +510,11 @@ namespace 头罩视野.Views
|
||||
tbTest.Background = System.Windows.Media.Brushes.LightGray;
|
||||
await _modbusMaster.WriteSingleCoilAsync(1, 41, false);
|
||||
}
|
||||
private void Button_Click_home(object sender, RoutedEventArgs e)
|
||||
{
|
||||
NavigationService.Content = null;
|
||||
}
|
||||
|
||||
|
||||
//读取数据
|
||||
private DispatcherTimer InitDispatcherTimer()
|
||||
@@ -866,6 +815,7 @@ namespace 头罩视野.Views
|
||||
_timer.Stop();
|
||||
NavigationService.Content = new Views.PageTest();
|
||||
}
|
||||
private void GoRecord(object s, RoutedEventArgs e) => NavigationService.Content = new Views.RecordDate();
|
||||
private void GoView(object s, RoutedEventArgs e) => NavigationService.Content = new Views.RecordPage();
|
||||
//初始化数据
|
||||
private void InitAllDataAsync()
|
||||
@@ -910,6 +860,7 @@ namespace 头罩视野.Views
|
||||
_modbusMaster.WriteSingleCoil(1, 0, false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -239,6 +239,57 @@ namespace 头罩视野.Views
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReadAndUpdateFloatAsync(int address, int length, System.Windows.Controls.TextBlock control, string format, string unit)
|
||||
{
|
||||
try
|
||||
{
|
||||
ushort[] registers = await Task.Run(async () =>
|
||||
await _modbusMaster.ReadHoldingRegistersAsync(1, (ushort)address, (ushort)length)
|
||||
);
|
||||
|
||||
if (registers != null && registers.Length >= 2)
|
||||
{
|
||||
float value = c.UshortToFloat(registers[1], registers[0]);
|
||||
Dispatcher.Invoke(() => control.Text = value.ToString(format) + unit);
|
||||
}
|
||||
else if (registers != null && registers.Length >= 1)
|
||||
{
|
||||
int value = registers[0];
|
||||
Dispatcher.Invoke(() => control.Text = value.ToString(format) + unit);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"读取地址{address}失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReadAndUpdateIntAsync(int address, int length, System.Windows.Controls.Control control, string format)
|
||||
{
|
||||
try
|
||||
{
|
||||
ushort[] registers = await Task.Run(() =>
|
||||
_modbusMaster.ReadHoldingRegisters(1, (ushort)address, (ushort)length)
|
||||
);
|
||||
|
||||
if (registers != null && registers.Length >= 1)
|
||||
{
|
||||
int value = registers[0];
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (control is System.Windows.Controls.ContentControl contentControl)
|
||||
contentControl.Content = value.ToString(format);
|
||||
else if (control is System.Windows.Controls.TextBox textBox)
|
||||
textBox.Text = value.ToString(format);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"读取地址{address}失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowError(string msg) => MessageBox.Show(msg, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
private void GoHome(object s, RoutedEventArgs e) => NavigationService.Content = null;
|
||||
private void GoTest(object s, RoutedEventArgs e) => NavigationService.Content = new Views.PageTest();
|
||||
|
||||
Reference in New Issue
Block a user