using MathNet.Numerics.LinearAlgebra; using MathNetMatrix = MathNet.Numerics.LinearAlgebra.Matrix; using MathNetVector = MathNet.Numerics.LinearAlgebra.Vector; using System.Drawing; namespace 头罩视野.Services { class GetArea { // 设备固定参数 public static double R = 325; // 半球半径 // 定义参数(和你代码里一致) public const int totalLights = 81; public static double verticalAngleStep = 90.0 / (totalLights - 1); // 上下灯条用 public static double horizontalAngleStep = 180.0 / (totalLights - 1); // 左右灯条用 ///// 双目标准标定总面积:无面罩空标准头模的双目总实测面积国标总视野保存率计算的基准值,单位:cm² //public const double StandardTotal = 10360; //空白视野面积计算 public static readonly double _standardTotalArea = 2 * Math.PI * 330 * 330; public static double GetBlankViewArea(double binocularTotalArea) { // 公式:空白 = 标准总面积 - 双目总视野 return _standardTotalArea - binocularTotalArea; } //下方视野 下方视野角度 = 人眼到「最低亮灯条」的夹角 public static double CalculateBottomViewAngle(int[] lightData, List<(int m, int n)> lightPositions) { List bottomAngles = new List(); 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(); } //视野保存率 public static double CalcVisionRate(double binocularRate) { // 1. 总视野保存率 double ratioTotal = binocularRate / _standardTotalArea; double gammaTotal = GetVisionGamma(ratioTotal); double totalRate = gammaTotal * ratioTotal * 100; return (totalRate); } /// /// GB2890-2022 自动获取 总视野/双目视野 校正系数γ /// /// 实测面积/标准面积 比值(0~1) /// 校正系数 γ 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; } // 传入: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; } /// 生成设备全部243盏灯的(m,n)位置 上爪1条、下爪1条、左右共用1条,各81灯 public static System.Drawing.Point GetLightPoint(int m, int n) { double radH, radV; // 上爪灯条(n=0):水平角固定,m控制垂直角 if (n == 0) { radH = 0; radV = m * verticalAngleStep * Math.PI / 180; } // 下爪灯条(n=1):水平角固定为180°,m控制垂直角 else if (n == 1) { radH = Math.PI; radV = m * verticalAngleStep * Math.PI / 180; } // 左右共用灯条(n=2):垂直角固定,m控制水平角 else { radH = m * horizontalAngleStep * Math.PI / 180; radV = 0; } // 保留你原来的投影公式 double x = R * Math.Tan(radH); double y = R * Math.Tan(radV); return new System.Drawing.Point((int)Math.Round(x), (int)Math.Round(y)); } // 最小二乘法拟合椭圆(核心算法)cx:椭圆中心点的 X 坐标 cy:椭圆中心点的 Y 坐标 a:椭圆的长半轴长度(较大的那个半径)b:椭圆的短半轴长度(较小的那个半径) public static (double cx, double cy, double a, double b, double area) FitEllipse(List points) { int n = points.Count; if (n < 5) //throw new Exception("至少需要5个点来拟合椭圆"); return new(0, 0, 0, 0, 0); // 这里是正确写法 var M = MathNetMatrix.Build.Dense(n, 5); var Y = MathNetVector.Build.Dense(n, i => -1.0); for (int i = 0; i < n; i++) { 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; } // 求解 Vector sol = M.QR().Solve(Y); double A = sol[0], B = sol[1], C = sol[2], D = sol[3], E = sol[4], F = 1; // 椭圆中心 double cx = (2 * C * D - B * E) / (B * B - 4 * A * C); double cy = (2 * A * E - B * D) / (B * B - 4 * A * C); // 半轴 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); 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))); if (a < b) (a, b) = (b, a); double area = Math.PI * a * b; return (cx, cy, a, b, area); } } }