Files
hoodFieldOfView/头罩视野slove/头罩视野/Services/GetArea.cs

284 lines
10 KiB
C#
Raw Normal View History

2026-04-30 08:46:44 +08:00

using MathNet.Numerics.LinearAlgebra;
using MathNetMatrix = MathNet.Numerics.LinearAlgebra.Matrix<double>;
using MathNetVector = MathNet.Numerics.LinearAlgebra.Vector<double>;
using System.Drawing;
2026-04-27 16:45:06 +08:00
namespace .Services
{
class GetArea
{
2026-05-04 17:42:39 +08:00
// 设备固定参数
2026-05-06 09:21:35 +08:00
public static double R = 325; // 半球半径
2026-05-04 17:42:39 +08:00
// 定义参数(和你代码里一致)
2026-05-05 11:02:31 +08:00
public const int totalLights = 81;
2026-04-27 16:45:06 +08:00
2026-05-05 14:18:56 +08:00
public static double verticalAngleStep = 90.0 / (totalLights - 1); // 上下灯条用
public static double horizontalAngleStep = 180.0 / (totalLights - 1); // 左右灯条用
2026-05-05 11:02:31 +08:00
///// <summary>双目标准标定总面积无面罩空标准头模的双目总实测面积国标总视野保存率计算的基准值单位cm²</summary>
//public const double StandardTotal = 10360;
2026-04-27 16:45:06 +08:00
2026-05-04 17:42:39 +08:00
//空白视野面积计算
public static readonly double _standardTotalArea = 2 * Math.PI * 330 * 330;
2026-04-27 16:45:06 +08:00
2026-05-04 17:42:39 +08:00
public static double GetBlankViewArea(double binocularTotalArea)
2026-04-27 16:45:06 +08:00
{
2026-05-04 17:42:39 +08:00
// 公式:空白 = 标准总面积 - 双目总视野
return _standardTotalArea - binocularTotalArea;
2026-04-27 16:45:06 +08:00
}
2026-05-05 11:02:31 +08:00
//下方视野 下方视野角度 = 人眼到「最低亮灯条」的夹角
2026-05-05 14:18:56 +08:00
2026-05-04 17:42:39 +08:00
public static double CalculateBottomViewAngle(int[] lightData, List<(int m, int n)> lightPositions)
2026-04-27 16:45:06 +08:00
{
2026-05-05 14:18:56 +08:00
List<double> bottomAngles = new List<double>();
2026-04-27 16:45:06 +08:00
2026-05-04 17:42:39 +08:00
for (int i = 0; i < lightData.Length; i++)
2026-04-27 16:45:06 +08:00
{
2026-05-05 14:18:56 +08:00
// 只处理亮灯的情况
2026-05-04 17:42:39 +08:00
if (lightData[i] == 1)
2026-04-27 16:45:06 +08:00
{
2026-05-06 15:00:54 +08:00
if (lightPositions.Count < lightData.Count())
{
return 0;
}
2026-05-04 17:42:39 +08:00
var (m, n) = lightPositions[i];
2026-05-05 14:18:56 +08:00
// 关键只取下爪灯条n == 1因为只有它才对应下方的垂直视野
if (n == 1)
{
double angle = m * verticalAngleStep;
bottomAngles.Add(angle);
}
2026-04-27 16:45:06 +08:00
}
}
2026-05-05 14:18:56 +08:00
// 没有亮灯返回0
if (bottomAngles.Count == 0)
2026-05-04 17:42:39 +08:00
return 0;
2026-04-27 16:45:06 +08:00
2026-05-05 14:18:56 +08:00
// 最大角度 = 最下方的亮灯条,也就是下方视野的边界
return bottomAngles.Max();
2026-04-27 16:45:06 +08:00
}
2026-05-05 14:18:56 +08:00
2026-04-27 16:45:06 +08:00
//视野保存率
2026-05-04 17:42:39 +08:00
public static double CalcVisionRate(double binocularRate)
2026-04-27 16:45:06 +08:00
{
2026-05-06 15:00:54 +08:00
2026-04-27 16:45:06 +08:00
// 1. 总视野保存率
2026-05-05 11:02:31 +08:00
double ratioTotal = binocularRate / _standardTotalArea;
2026-04-27 16:45:06 +08:00
double gammaTotal = GetVisionGamma(ratioTotal);
double totalRate = gammaTotal * ratioTotal * 100;
return (totalRate);
}
/// <summary>
/// GB2890-2022 自动获取 总视野/双目视野 校正系数γ
/// </summary>
/// <param name="ratio">实测面积/标准面积 比值(0~1)</param>
/// <returns>校正系数 γ</returns>
public static double GetVisionGamma(double ratio)
{
// 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 (ratio >= xData[i] && ratio <= xData[i + 1])
{
double t = (ratio - xData[i]) / (xData[i + 1] - xData[i]);
return yData[i] + t * (yData[i + 1] - yData[i]);
}
}
return 1.0;
}
2026-04-30 08:46:44 +08:00
2026-05-06 19:51:57 +08:00
//// 传入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;
//}
2026-04-30 08:46:44 +08:00
public static double CalculateEllipseArea(int[] lightData, List<(int m, int n)> lightPositions)
2026-05-06 19:51:57 +08:00
{
// 日志:方法入口
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)
2026-04-30 08:46:44 +08:00
{
2026-05-06 19:51:57 +08:00
var (m, n) = lightPositions[i];
System.Drawing.Point p = GetLightPoint(m, n);
brightPoints.Add(p);
}
}
2026-04-30 08:46:44 +08:00
2026-05-06 19:51:57 +08:00
System.Diagnostics.Debug.WriteLine($"收集到的亮灯点数:{brightPoints.Count}");
2026-05-06 15:00:54 +08:00
2026-05-06 19:51:57 +08:00
if (brightPoints.Count < 5)
{
System.Diagnostics.Debug.WriteLine($"警告亮灯点不足5个实际{brightPoints.Count}),无法拟合椭圆,返回 NaN");
return double.NaN;
}
2026-05-06 15:00:54 +08:00
2026-05-06 19:51:57 +08:00
// 3. 拟合椭圆
var (cx, cy, a, b, area) = FitEllipse(brightPoints);
2026-05-06 15:00:54 +08:00
2026-05-06 19:51:57 +08:00
System.Diagnostics.Debug.WriteLine($"拟合结果cx={cx}, cy={cy}, a={a}, b={b}, area={area}");
2026-04-30 08:46:44 +08:00
2026-05-06 19:51:57 +08:00
if (double.IsNaN(a) || double.IsNaN(b) || double.IsNaN(area))
{
System.Diagnostics.Debug.WriteLine("错误:拟合结果包含 NaN");
return double.NaN;
}
2026-04-30 08:46:44 +08:00
2026-05-06 19:51:57 +08:00
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;
}
2026-05-04 10:25:31 +08:00
/// 生成设备全部243盏灯的(m,n)位置 上爪1条、下爪1条、左右共用1条各81灯
2026-05-05 14:18:56 +08:00
public static System.Drawing.Point GetLightPoint(int m, int n)
2026-04-30 08:46:44 +08:00
{
2026-05-04 10:25:31 +08:00
double radH, radV;
// 上爪灯条n=0水平角固定m控制垂直角
if (n == 0)
{
radH = 0;
2026-05-05 14:18:56 +08:00
radV = m * verticalAngleStep * Math.PI / 180;
2026-05-04 10:25:31 +08:00
}
// 下爪灯条n=1水平角固定为180°m控制垂直角
else if (n == 1)
{
radH = Math.PI;
2026-05-05 14:18:56 +08:00
radV = m * verticalAngleStep * Math.PI / 180;
2026-05-04 10:25:31 +08:00
}
// 左右共用灯条n=2垂直角固定m控制水平角
else
{
2026-05-05 14:18:56 +08:00
radH = m * horizontalAngleStep * Math.PI / 180;
2026-05-04 10:25:31 +08:00
radV = 0;
}
// 保留你原来的投影公式
2026-04-30 08:46:44 +08:00
double x = R * Math.Tan(radH);
double y = R * Math.Tan(radV);
2026-05-04 10:25:31 +08:00
return new System.Drawing.Point((int)Math.Round(x), (int)Math.Round(y));
2026-04-30 08:46:44 +08:00
}
2026-05-04 10:25:31 +08:00
// 最小二乘法拟合椭圆核心算法cx椭圆中心点的 X 坐标 cy椭圆中心点的 Y 坐标 a椭圆的长半轴长度较大的那个半径b椭圆的短半轴长度较小的那个半径
2026-04-30 08:46:44 +08:00
2026-05-05 14:18:56 +08:00
public static (double cx, double cy, double a, double b, double area) FitEllipse(List<Point> points)
2026-05-06 15:00:54 +08:00
{
int n = points.Count;
2026-05-06 10:52:53 +08:00
if (n < 5)
//throw new Exception("至少需要5个点来拟合椭圆");
2026-05-06 15:00:54 +08:00
return new(0, 0, 0, 0, 0);
2026-04-30 08:46:44 +08:00
// 这里是正确写法
var M = MathNetMatrix.Build.Dense(n, 5);
var Y = MathNetVector.Build.Dense(n, i => -1.0);
for (int i = 0; i < n; i++)
2026-05-06 15:00:54 +08:00
{
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;
}
2026-04-30 08:46:44 +08:00
2026-05-06 15:00:54 +08:00
// 求解
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;
2026-04-30 08:46:44 +08:00
2026-05-06 15:00:54 +08:00
// 椭圆中心
double cx = (2 * C * D - B * E) / (B * B - 4 * A * C);
double cy = (2 * A * E - B * D) / (B * B - 4 * A * C);
2026-04-30 08:46:44 +08:00
2026-05-06 15:00:54 +08:00
// 半轴
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);
2026-04-30 08:46:44 +08:00
2026-05-06 15:00:54 +08:00
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)));
2026-04-30 08:46:44 +08:00
2026-05-06 15:00:54 +08:00
if (a < b) (a, b) = (b, a);
double area = Math.PI * a * b;
2026-04-30 08:46:44 +08:00
2026-05-04 10:25:31 +08:00
return (cx, cy, a, b, area);
2026-04-30 08:46:44 +08:00
}
2026-05-04 10:25:31 +08:00
}
2026-04-27 16:45:06 +08:00
}