using MathNet.Numerics.LinearAlgebra; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TeamAAS_VP.Core { /// /// 提供多种方法用于计算由四个角点定义的矩形中心点、长轴角度以及相关矩形参数的辅助类。 /// 角点顺序约定:左上(0) → 右上(1) → 右下(2) → 左下(3) /// 长轴方向:从左上角(0)指向右上角(1) /// 角度范围:[-180, 180) 度,0°指向X轴正方向,逆时针为正 /// public class RectangleCenterCalculator { /// /// 矩形中心及方向信息的综合结果 /// public class RectangleResult { /// /// 矩形中心点坐标 /// public Vector Center { get; set; } /// /// 矩形长轴方向向量(从左上指向右上的单位向量) /// public Vector MajorAxis { get; set; } /// /// 矩形短轴方向向量(单位向量) /// public Vector MinorAxis { get; set; } /// /// 矩形长度(沿长轴方向,左上到右上的距离) /// public double Length { get; set; } /// /// 矩形宽度(沿短轴方向) /// public double Width { get; set; } /// /// 长轴方向角度[-180, 180)度 /// 从左上指向右上的向量在XY平面的投影角度 /// 0° = 指向X轴正方向,正角度逆时针,负角度顺时针 /// public double MajorAxisAngle { get; set; } /// /// 四个角点的坐标(保持输入顺序) /// public List> Corners { get; set; } /// /// 左上角点坐标 /// public Vector TopLeft => Corners != null && Corners.Count >= 1 ? Corners[0] : null; /// /// 右上角点坐标 /// public Vector TopRight => Corners != null && Corners.Count >= 2 ? Corners[1] : null; /// /// 右下角点坐标 /// public Vector BottomRight => Corners != null && Corners.Count >= 3 ? Corners[2] : null; /// /// 左下角点坐标 /// public Vector BottomLeft => Corners != null && Corners.Count >= 4 ? Corners[3] : null; /// /// 矩形的面积 /// public double Area { get; set; } /// /// 拟合误差(均方根误差) /// public double FitError { get; set; } } /// /// 验证角点顺序并进行必要的调整 /// 确保顺序为:左上 → 右上 → 右下 → 左下 /// private static List> ValidateAndOrderCorners(List> corners) { if (corners == null || corners.Count != 4) throw new ArgumentException("需要4个角点"); // 这里可以添加自动排序逻辑,但既然您已知顺序,直接返回 // 如果未来需要自动排序,可以使用以下逻辑: // 1. 找到最左边的两个点作为左边界 // 2. 根据Y坐标区分左上和左下 // 3. 计算中心点,确定其他点位置 return corners; // 假设输入已经按正确顺序 } /// /// 计算从左上指向右上的长轴方向角度 /// private static double CalculateMajorAxisAngle(Vector topLeft, Vector topRight) { // 计算方向向量(从左上指向右上) Vector direction = topRight - topLeft; // 只考虑XY平面上的投影 double x = direction[0]; double y = direction[1]; // 使用Math.Atan2计算角度(弧度) double angleRad = Math.Atan2(y, x); // 转换为度,得到有符号角度 [-180, 180) double angleDeg = angleRad * 180.0 / Math.PI; // Math.Atan2的结果已经是[-180, 180],但确保一下边界 if (angleDeg >= 180.0) angleDeg -= 360.0; else if (angleDeg < -180.0) angleDeg += 360.0; return angleDeg; } /// /// 使用最小二乘法思想计算最优矩形中心点及相关参数(对外统一入口) /// 推荐使用改进的PCA方法,因为它能利用所有点信息进行优化 /// public static RectangleResult CalculateOptimalCenter(List> corners) { // 验证和调整角点顺序 var orderedCorners = ValidateAndOrderCorners(corners); // 使用改进的PCA方法(考虑已知顺序) return CalculateByPCAWithKnownOrder(orderedCorners); } /// /// 方法1:基于已知角点顺序的直接计算(最准确) /// 直接使用左上和右上点计算长轴,然后计算其他参数 /// public static RectangleResult CalculateByDirectMethod(List> corners) { var orderedCorners = ValidateAndOrderCorners(corners); var topLeft = orderedCorners[0]; var topRight = orderedCorners[1]; var bottomRight = orderedCorners[2]; var bottomLeft = orderedCorners[3]; // 计算长轴(从左上指向右上) Vector majorAxis = (topRight - topLeft).Normalize(2); // 计算短轴(从左上指向左下) Vector minorAxis = (bottomLeft - topLeft).Normalize(2); // 确保短轴与长轴垂直(处理非正交情况) minorAxis = (minorAxis - majorAxis * minorAxis.DotProduct(majorAxis)).Normalize(2); // 计算中心点(四个角点的平均) var center = Vector.Build.DenseOfEnumerable( Enumerable.Range(0, 3) .Select(i => orderedCorners.Select(c => c[i]).Average())); // 计算长度和宽度 double length = Distance(topLeft, topRight); double width = Distance(topLeft, bottomLeft); // 计算长轴角度 double angle = CalculateMajorAxisAngle(topLeft, topRight); // 计算拟合误差 double fitError = CalculateFitErrorDirect(center, majorAxis, minorAxis, length, width, orderedCorners); return new RectangleResult { Center = center, MajorAxis = majorAxis, MinorAxis = minorAxis, Length = length, Width = width, MajorAxisAngle = angle, Corners = orderedCorners, Area = length * width, FitError = fitError }; } /// /// 方法2:使用PCA但考虑已知角点顺序(推荐) /// 结合PCA的鲁棒性和已知顺序的准确性 /// public static RectangleResult CalculateByPCAWithKnownOrder(List> corners) { var orderedCorners = ValidateAndOrderCorners(corners); // 步骤1:使用所有点进行PCA得到初步估计 var matrix = Matrix.Build.DenseOfRows(orderedCorners); var mean = Vector.Build.DenseOfEnumerable( Enumerable.Range(0, 3).Select(i => matrix.Column(i).Average())); var centered = matrix.Clone(); for (int i = 0; i < 4; i++) { centered.SetRow(i, centered.Row(i) - mean); } var covariance = centered.Transpose() * centered / 3.0; var evd = covariance.Evd(); // 获取特征向量 var eigenvectors = evd.EigenVectors; // 步骤2:使用已知顺序确定长轴方向 var topLeft = orderedCorners[0]; var topRight = orderedCorners[1]; // 计算实际的长轴方向(从左上指向右上) Vector actualMajorDirection = (topRight - topLeft).Normalize(2); // 从PCA的特征向量中找到最接近实际方向的那个 Vector bestAxis = eigenvectors.Column(0); double bestSimilarity = Math.Abs(actualMajorDirection.DotProduct(bestAxis)); for (int i = 1; i < 3; i++) { var axis = eigenvectors.Column(i); double similarity = Math.Abs(actualMajorDirection.DotProduct(axis)); if (similarity > bestSimilarity) { bestSimilarity = similarity; bestAxis = axis; } } // 确保PCA轴的方向与实际方向一致(点积为正) Vector majorAxis = bestAxis; if (actualMajorDirection.DotProduct(majorAxis) < 0) { majorAxis = -majorAxis; } // 计算短轴(与长轴垂直) Vector minorAxis = CalculatePerpendicularAxis(majorAxis); // 将点投影到轴上计算中心、长度和宽度 var projMajor = orderedCorners.Select(p => (p - mean).DotProduct(majorAxis)).ToList(); var projMinor = orderedCorners.Select(p => (p - mean).DotProduct(minorAxis)).ToList(); double centerMajor = (projMajor.Min() + projMajor.Max()) / 2.0; double centerMinor = (projMinor.Min() + projMinor.Max()) / 2.0; var center = mean + majorAxis * centerMajor + minorAxis * centerMinor; double length = Math.Abs(projMajor.Max() - projMajor.Min()); double width = Math.Abs(projMinor.Max() - projMinor.Min()); // 计算长轴角度(使用实际的左上-右上方向) double angle = CalculateMajorAxisAngle(topLeft, topRight); // 计算拟合误差 double fitError = CalculateFitError(center, majorAxis, minorAxis, length, width, orderedCorners); return new RectangleResult { Center = center, MajorAxis = majorAxis, MinorAxis = minorAxis, Length = length, Width = width, MajorAxisAngle = angle, Corners = orderedCorners, Area = length * width, FitError = fitError }; } /// /// 方法3:对角线优化方法(考虑已知顺序) /// public static RectangleResult CalculateByDiagonalOptimization(List> corners) { var orderedCorners = ValidateAndOrderCorners(corners); var topLeft = orderedCorners[0]; var topRight = orderedCorners[1]; // 直接使用已知顺序计算长轴角度 double angle = CalculateMajorAxisAngle(topLeft, topRight); // 计算长轴方向向量 Vector majorAxis = (topRight - topLeft).Normalize(2); Vector minorAxis = CalculatePerpendicularAxis(majorAxis); // 使用所有对角线组合计算最佳中心 var diagonalCombinations = new[] { (orderedCorners[0], orderedCorners[2]), // 左上-右下 (orderedCorners[1], orderedCorners[3]) // 右上-左下 }; var centers = diagonalCombinations.Select(p => (p.Item1 + p.Item2) / 2.0).ToList(); var center = (centers[0] + centers[1]) / 2.0; // 计算长度和宽度 double length = Distance(topLeft, topRight); double width = Distance(topLeft, orderedCorners[3]); // 左上-左下 // 计算拟合误差 double fitError = CalculateFitError(center, majorAxis, minorAxis, length, width, orderedCorners); return new RectangleResult { Center = center, MajorAxis = majorAxis, MinorAxis = minorAxis, Length = length, Width = width, MajorAxisAngle = angle, Corners = orderedCorners, Area = length * width, FitError = fitError }; } /// /// 辅助方法:计算与给定向量垂直的单位向量 /// private static Vector CalculatePerpendicularAxis(Vector axis) { // 找一个不与axis共线的向量 Vector temp; if (Math.Abs(axis[0]) < 0.9) temp = Vector.Build.Dense(new[] { 1.0, 0.0, 0.0 }); else temp = Vector.Build.Dense(new[] { 0.0, 1.0, 0.0 }); var perpendicular = temp - axis * temp.DotProduct(axis); return perpendicular.Normalize(2); } /// /// 辅助方法:计算两点距离 /// private static double Distance(Vector a, Vector b) { return (a - b).L2Norm(); } /// /// 辅助方法:计算拟合误差 /// private static double CalculateFitError(Vector center, Vector majorAxis,Vector minorAxis, double length, double width, List> corners) { // 生成理想矩形的四个角点 var idealCorners = new List> { center + majorAxis * (length / 2) + minorAxis * (width / 2), // 右上 center + majorAxis * (length / 2) - minorAxis * (width / 2), // 右下 center - majorAxis * (length / 2) - minorAxis * (width / 2), // 左下 center - majorAxis * (length / 2) + minorAxis * (width / 2) // 左上 }; // 注意:理想角点顺序可能与输入不同,需要重新排序匹配 // 这里简单计算所有对应点的最小距离和 double sumSquared = 0; for (int i = 0; i < 4; i++) { // 找到最近的理想角点 double minDist = double.MaxValue; foreach (var ideal in idealCorners) { double dist = Distance(corners[i], ideal); if (dist < minDist) minDist = dist; } sumSquared += minDist * minDist; } return Math.Sqrt(sumSquared / 4); } /// /// 直接计算方法的拟合误差 /// private static double CalculateFitErrorDirect(Vector center, Vector majorAxis,Vector minorAxis, double length, double width, List> corners) { // 对于直接方法,我们知道每个角点的理想位置 var idealCorners = new List> { center - majorAxis * (length / 2) + minorAxis * (width / 2), // 左上 center + majorAxis * (length / 2) + minorAxis * (width / 2), // 右上 center + majorAxis * (length / 2) - minorAxis * (width / 2), // 右下 center - majorAxis * (length / 2) - minorAxis * (width / 2) // 左下 }; double sumSquared = 0; for (int i = 0; i < 4; i++) { sumSquared += Math.Pow(Distance(corners[i], idealCorners[i]), 2); } return Math.Sqrt(sumSquared / 4); } } }