1 Commits

Author SHA1 Message Date
xyy
23b6debf94 2026-05-14 20:12:01 +08:00
2 changed files with 115 additions and 116 deletions

View File

@@ -22,83 +22,50 @@ namespace 头罩视野.Services
//计算双目视野
public static double CalculateBinocularArea(
List<int> leftFinal,
List<int> rightFinal,
List<(int m, int n)> lightPositions)
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++)
{
// 取两者中较小的长度,防止一方数据被截断
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];
}
binocularData[i] = leftFinal[i] & rightFinal[i]; // 改为 &(交集)
}
System.Diagnostics.Debug.WriteLine($"【双目亮灯】长度:{length}, 左眼亮灯:{leftFinal.Count}, 右眼亮灯:{rightFinal.Count}, 双目亮灯:{binocularData.Length}");
return CalculateEllipseArea(binocularData, lightPositions);
}
}
////下方视野 下方视野角度 = 人眼到「最低亮灯条」的夹角
//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++)
// {
// // 只处理亮灯的情况
// if (lightData[i] == 1)
// {
// if (lightPositions.Count < lightData.Count())
// {
// return 0;
// }
// var (m, n) = lightPositions[i];
// // 关键只取下爪灯条n == 1因为只有它才对应下方的垂直视野
// if (n == 1)
// {
// double angle = m * verticalAngleStep;
// bottomAngles.Add(angle);
// }
// }
// }
// // 没有亮灯返回0
// if (bottomAngles.Count == 0)
// return 0;
// // 最大角度 = 最下方的亮灯条,也就是下方视野的边界
// return bottomAngles.Max();
//}
/// <summary>
/// 下方视野角度 = 从正下方90°向顶部扫描,第一个被遮挡的边界角度(度)
/// 使用下爪灯条n==1的径向数据
/// 下方视野角度从正下方90°往正上方扫描,找到第一个被遮挡的灯条,返回其起始角度。
/// 假设下爪灯条索引0对应顶部索引80对应底部90°
/// </summary>
public static double CalculateBottomViewAngle(int[] lightData, List<(int m, int n)> lightPositions)
{
// 提取下爪灯条n==1的数据共81个灯
int radialCount = 81;
int[] radialData = new int[radialCount];
// 提取下爪灯条n==1保持原始顺序索引0=顶部索引80=底部)
double[] angles = new double[81];
int idx = 0;
for (int i = 0; i < lightData.Length && idx < radialCount; i++)
for (int i = 0; i < lightData.Length && idx < 81; i++)
{
var (m, n) = lightPositions[i];
if (n == 1)
{
radialData[idx++] = lightData[i];
angles[idx] = lightData[i] == 1 ? m * verticalAngleStep : -1; // -1表示灭
idx++;
}
}
if (idx != radialCount) return 0; // 数据不完整
// 反转数组使索引0对应物理底部90°索引80对应顶部0°
Array.Reverse(radialData);
// 获取径向灯条角度范围0~90°
var (startAngles, endAngles) = GetRadialLightAngles(radialCount, 90.0);
double boundary = ComputeBoundaryAngle(radialData, startAngles, endAngles);
// 转换为从顶部开始的极角(即标准下方视野角度)
return 90 - boundary;
// 从底部索引80向顶部0扫描找到第一个灭灯的位置
for (int i = 80; i >= 0; i--)
{
if (angles[i] < 0) // 灭
{
// 返回该灯条的起始角度
return i * verticalAngleStep;
}
}
// 全亮
return 90;
}
/// <summary>
@@ -180,13 +147,8 @@ 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++)
{
@@ -197,29 +159,10 @@ namespace 头罩视野.Services
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;
// 直接返回亮点数量乘以固定系数,不再拟合椭圆
const double AREA_PER_POINT = 10.0; // 可根据实际调整,但空白和试样必须一致
return brightCount * AREA_PER_POINT;
}

View File

@@ -59,6 +59,8 @@ namespace 头罩视野.Views
// 表跟数据存储列表
public List<dynamic> DataList = new List<dynamic>();
private const double SAMPLE_AREA_FACTOR = 0.7;
public PageTest()
{
InitializeComponent();
@@ -138,6 +140,11 @@ namespace 头罩视野.Views
//测试按钮
private async void Button_Click_Test(object sender, RoutedEventArgs e)
{
//// 清空历史数据
//_leftFinalData.Clear();
//_rightFinalData.Clear();
if (_isTesting)
{
// 停止测试
@@ -164,6 +171,8 @@ namespace 头罩视野.Views
// 面积也清空
isLeftOnly = btnLeft.Content.ToString() == "左眼关" && btnRight.Content.ToString() == "右眼开";
isRightOnly = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼关";
isBinocular = btnLeft.Content.ToString() == "左眼关" && btnRight.Content.ToString() == "右眼关";
@@ -192,24 +201,7 @@ 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;
// else
// finalAngle = botViAnInt;
//}
//else
//{
// // 正常模式:角度最大不超过 68
// finalAngle = botViAnInt > 70 ? 68 : botViAnInt;
//}
xfsyarea.Text = botViAnInt.ToString("0"); // 下方视野
@@ -245,17 +237,31 @@ namespace 头罩视野.Views
}
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");//总视野保存率
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");
}
}
//if (double.TryParse(smsyarea.Text, out double totalAreaForRate))
//{
// double binocularRate = GetArea.CalcVisionRate(totalAreaForRate);
@@ -267,7 +273,6 @@ namespace 头罩视野.Views
//读取灯泡的数据
private async Task ReadLightBarData()
{
if (_modbusMaster == null || !ModbusHelper.TcpClient.Connected)
@@ -287,10 +292,21 @@ 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(tempList.Cast<dynamic>());
DataList.AddRange(rawData.Cast<dynamic>());
}
System.Diagnostics.Debug.WriteLine($"灯条二进制数据总长度:{DataList.Count}");
@@ -302,6 +318,40 @@ 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; // 非测试状态直接返回
@@ -384,6 +434,12 @@ 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. 根据当前测试模式(左眼/右眼/双目),累加面积
@@ -434,7 +490,7 @@ namespace 头罩视野.Views
// 6. 更新下方视野的最大值(取所有角度中最大的)
if (bottomViewAngle > maxBottomViewAngle)
maxBottomViewAngle = bottomViewAngle;
maxBottomViewAngle *= 0.8;
await Dispatcher.InvokeAsync(() =>
{
zmsyarea.Text = _leftTotalArea.ToString("0");