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 = (Math.PI * R * R) / 100; 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; //} //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 brightPoints = new List(); // for (int i = 0; i < totalLights && i < lightData.Length && i < lightPositions.Count; i++) // { // if (lightData[i] == 1) // { // 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; //} 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 brightPoints = new List(); for (int i = 0; i < maxIdx; i++) { if (lightData[i] == 1) { var (m, n) = lightPositions[i]; System.Drawing.Point p = GetLightPoint(m, n); 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; } /// 生成设备全部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); } } }