RectangleCenterCalculator.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. using MathNet.Numerics.LinearAlgebra;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace TeamAAS_VP.Core
  8. {
  9. /// <summary>
  10. /// 提供多种方法用于计算由四个角点定义的矩形中心点以及相关矩形参数的辅助类。
  11. /// 该类包含基于对角线优化、PCA(主成分分析)、RANSAC 与最小二乘拟合的不同策略,
  12. /// 以应对不同噪声和异常值情形。注意输入假定为三维向量(长度为 3 的向量)。
  13. /// </summary>
  14. public class RectangleCenterCalculator
  15. {
  16. /// <summary>
  17. /// 使用最小二乘法思想计算最优矩形中心点(对外统一入口)。
  18. /// 调用当前实现中更鲁棒的对角线优化方法来估计中心。
  19. /// </summary>
  20. /// <param name="corners">矩形的四个角点坐标,每个点为长度为 3 的 <see cref="Vector{Double}"/>。</param>
  21. /// <returns>返回估计的中心点坐标(长度为 3 的向量)。</returns>
  22. /// <exception cref="ArgumentException">当 <paramref name="corners"/> 为 null 或数量不等于 4 时抛出。</exception>
  23. public static Vector<double> CalculateOptimalCenter(List<Vector<double>> corners)
  24. {
  25. if (corners == null || corners.Count != 4)
  26. throw new ArgumentException("需要4个角点");
  27. // 方法1:利用矩形对角线相交于中心点的性质
  28. return CalculateByDiagonalOptimization(corners);
  29. }
  30. /// <summary>
  31. /// 方法1:通过对角线中点的组合优化来估计中心点。
  32. /// 由于角点测量可能存在噪声,函数尝试不同的对角线组合,并采用使得四个角点到候选中心距离方差最小的候选中心。
  33. /// </summary>
  34. /// <param name="corners">四个角点坐标。</param>
  35. /// <returns>估计的中心点向量(长度为 3)。</returns>
  36. private static Vector<double> CalculateByDiagonalOptimization(List<Vector<double>> corners)
  37. {
  38. // 由于点可能有噪声,尝试所有可能的对角线组合
  39. // 对角线应该是相对的点
  40. var bestCenter = Vector<double>.Build.Dense(3);
  41. double minError = double.MaxValue;
  42. // 常见的对角线组合(这里列出三种互不等价的配对)
  43. var diagonalPairs = new List<(int, int, int, int)>
  44. {
  45. (0, 2, 1, 3), // p1-p3, p2-p4
  46. (0, 1, 2, 3), // p1-p2, p3-p4
  47. (0, 3, 1, 2) // p1-p4, p2-p3
  48. };
  49. foreach (var (i1, i2, i3, i4) in diagonalPairs)
  50. {
  51. // 计算两条对角线的中点
  52. var mid1 = (corners[i1] + corners[i2]) / 2.0;
  53. var mid2 = (corners[i3] + corners[i4]) / 2.0;
  54. // 计算两个中点的平均值作为候选中心
  55. var candidate = (mid1 + mid2) / 2.0;
  56. // 计算误差:中心到四个角点的距离方差
  57. double error = CalculateCenterError(candidate, corners);
  58. if (error < minError)
  59. {
  60. minError = error;
  61. bestCenter = candidate;
  62. }
  63. }
  64. return bestCenter;
  65. }
  66. /// <summary>
  67. /// 计算给定中心到四个角点距离的方差,作为中心优劣的度量(方差越小越优)。
  68. /// </summary>
  69. /// <param name="center">待评估的中心点向量(长度为 3)。</param>
  70. /// <param name="corners">四个角点坐标。</param>
  71. /// <returns>距离的方差值(double)。</returns>
  72. private static double CalculateCenterError(Vector<double> center, List<Vector<double>> corners)
  73. {
  74. // 计算中心到每个角点的距离
  75. var distances = corners.Select(c => Distance(center, c)).ToList();
  76. // 计算距离的方差 - 越小说明中心越优
  77. double mean = distances.Average();
  78. double variance = distances.Select(d => Math.Pow(d - mean, 2)).Average();
  79. return variance;
  80. }
  81. /// <summary>
  82. /// 计算两个向量之间的欧几里得距离(L2 范数)。
  83. /// </summary>
  84. /// <param name="a">向量 a(长度为 3)。</param>
  85. /// <param name="b">向量 b(长度为 3)。</param>
  86. /// <returns>返回 a 与 b 之间的 L2 距离。</returns>
  87. private static double Distance(Vector<double> a, Vector<double> b)
  88. {
  89. return (a - b).L2Norm();
  90. }
  91. /// <summary>
  92. /// 方法2:使用 PCA(主成分分析)找到矩形的主方向,然后根据在主轴上的最小/最大投影值计算中心位置。
  93. /// 适合点分布呈矩形、噪声较低的情况。
  94. /// </summary>
  95. /// <param name="corners">四个角点的坐标列表。</param>
  96. /// <returns>在原始坐标系中的估计中心点向量(长度为 3)。</returns>
  97. public static Vector<double> CalculateByPCA(List<Vector<double>> corners)
  98. {
  99. // 将点转换为矩阵
  100. var matrix = Matrix<double>.Build.DenseOfRows(corners);
  101. // 计算均值(初步中心估计)
  102. var mean = Vector<double>.Build.DenseOfEnumerable(
  103. Enumerable.Range(0, 3).Select(i => matrix.Column(i).Average()));
  104. // 中心化数据
  105. var centered = matrix.Clone();
  106. for (int i = 0; i < 4; i++)
  107. {
  108. centered.SetRow(i, centered.Row(i) - mean);
  109. }
  110. // 计算协方差矩阵(样本协方差:注意除以 n-1 或 n,当前实现使用除以 3)
  111. var covariance = centered.Transpose() * centered / 3.0;
  112. // 特征值分解(PCA)
  113. var evd = covariance.Evd();
  114. // 主方向(前两个特征向量)
  115. var axis1 = evd.EigenVectors.Column(0);
  116. var axis2 = evd.EigenVectors.Column(1);
  117. // 将点投影到两个主轴上
  118. var proj1 = corners.Select(p => (p - mean).DotProduct(axis1)).ToList();
  119. var proj2 = corners.Select(p => (p - mean).DotProduct(axis2)).ToList();
  120. // 在每个轴上找到最小和最大值
  121. double min1 = proj1.Min();
  122. double max1 = proj1.Max();
  123. double min2 = proj2.Min();
  124. double max2 = proj2.Max();
  125. // 计算在主轴上的中心位置(即区间中点)
  126. double center1 = (min1 + max1) / 2.0;
  127. double center2 = (min2 + max2) / 2.0;
  128. // 转换回原始坐标系得到中心点
  129. var center = mean + axis1 * center1 + axis2 * center2;
  130. return center;
  131. }
  132. /// <summary>
  133. /// 方法3:使用最小二乘法直接拟合矩形并返回参数(简化实现)。
  134. /// 这里返回的是基于 PCA 的初始估计以及默认轴向量,作为示例接口。
  135. /// </summary>
  136. /// <param name="corners">四个角点坐标。</param>
  137. /// <returns>
  138. /// 返回一个元组,包含:
  139. /// <see cref="Vector{Double}"/> Center - 估计的中心点;
  140. /// <see cref="Vector{Double}"/> Axis1 - 第一个轴(单位方向向量,当前示例返回笛卡尔 X 轴);
  141. /// <see cref="Vector{Double}"/> Axis2 - 第二个轴(单位方向向量,当前示例返回笛卡尔 Y 轴)。
  142. /// </returns>
  143. public static (Vector<double> Center, Vector<double> Axis1, Vector<double> Axis2) FitRectangleByLeastSquares(List<Vector<double>> corners)
  144. {
  145. // 步骤1:计算初始质心
  146. var initialCenter = Vector<double>.Build.DenseOfEnumerable(
  147. Enumerable.Range(0, 3).Select(i => corners.Select(c => c[i]).Average()));
  148. // 步骤2:使用Levenberg-Marquardt优化
  149. // 定义优化问题:找到中心、两个轴方向、长度和宽度
  150. // 这里简化实现,使用PCA结果作为初始值
  151. var pcaCenter = CalculateByPCA(corners);
  152. // 计算平均偏差(用于诊断输出)
  153. var errors = corners.Select(c => (c - pcaCenter).L2Norm()).ToList();
  154. double avgError = errors.Average();
  155. Console.WriteLine($"PCA中心点: [{pcaCenter[0]:F4}, {pcaCenter[1]:F4}, {pcaCenter[2]:F4}]");
  156. Console.WriteLine($"平均误差: {avgError:F6}");
  157. // 返回简化的轴向量(占位,实际实现可替换为优化得到的方向)
  158. var axis1 = Vector<double>.Build.Dense(new[] { 1.0, 0.0, 0.0 });
  159. var axis2 = Vector<double>.Build.Dense(new[] { 0.0, 1.0, 0.0 });
  160. return (pcaCenter, axis1, axis2);
  161. }
  162. /// <summary>
  163. /// 方法4:使用 RANSAC(随机采样一致性)估计中心,适用于存在异常点的情况。
  164. /// 随机选择两点作为候选对角线端点,统计满足阈值的内点数量,选择内点最多的候选中心,并用内点重计算中心。
  165. /// </summary>
  166. /// <param name="corners">四个角点坐标。</param>
  167. /// <param name="iterations">RANSAC 迭代次数,越大越稳健但计算开销越高。默认 100。</param>
  168. /// <param name="threshold">判断内点的距离阈值(以与坐标单位一致的长度度量)。默认 0.01。</param>
  169. /// <returns>估计得到的中心点向量(长度为 3)。若没有足够内点则返回在迭代过程中最佳的候选中心(可能为零向量)。</returns>
  170. public static Vector<double> CalculateByRANSAC(List<Vector<double>> corners,int iterations = 100, double threshold = 0.01)
  171. {
  172. var bestCenter = Vector<double>.Build.Dense(3);
  173. int bestInliers = 0;
  174. var random = new Random();
  175. for (int i = 0; i < iterations; i++)
  176. {
  177. // 随机选择两个点作为对角线端点
  178. int idx1 = random.Next(4);
  179. int idx2;
  180. do
  181. {
  182. idx2 = random.Next(4);
  183. } while (idx2 == idx1);
  184. // 计算对角线中点
  185. var candidateCenter = (corners[idx1] + corners[idx2]) / 2.0;
  186. // 统计内点(距离接近的点)
  187. int inliers = corners.Count(c =>
  188. (c - candidateCenter).L2Norm() < threshold);
  189. if (inliers > bestInliers)
  190. {
  191. bestInliers = inliers;
  192. bestCenter = candidateCenter;
  193. }
  194. }
  195. // 使用所有内点重新计算中心(若存在内点)
  196. var inlierPoints = corners.Where(c =>
  197. (c - bestCenter).L2Norm() < threshold).ToList();
  198. if (inlierPoints.Count > 0)
  199. {
  200. bestCenter = Vector<double>.Build.DenseOfEnumerable(
  201. Enumerable.Range(0, 3)
  202. .Select(dim => inlierPoints.Select(p => p[dim]).Average()));
  203. }
  204. return bestCenter;
  205. }
  206. }
  207. }