RectangleCenterCalculator.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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. /// 角点顺序约定:左上(0) → 右上(1) → 右下(2) → 左下(3)
  12. /// 长轴方向:从左上角(0)指向右上角(1)
  13. /// 角度范围:[-180, 180) 度,0°指向X轴正方向,逆时针为正
  14. /// </summary>
  15. public class RectangleCenterCalculator
  16. {
  17. /// <summary>
  18. /// 矩形中心及方向信息的综合结果
  19. /// </summary>
  20. public class RectangleResult
  21. {
  22. /// <summary>
  23. /// 矩形中心点坐标
  24. /// </summary>
  25. public Vector<double> Center { get; set; }
  26. /// <summary>
  27. /// 矩形长轴方向向量(从左上指向右上的单位向量)
  28. /// </summary>
  29. public Vector<double> MajorAxis { get; set; }
  30. /// <summary>
  31. /// 矩形短轴方向向量(单位向量)
  32. /// </summary>
  33. public Vector<double> MinorAxis { get; set; }
  34. /// <summary>
  35. /// 矩形长度(沿长轴方向,左上到右上的距离)
  36. /// </summary>
  37. public double Length { get; set; }
  38. /// <summary>
  39. /// 矩形宽度(沿短轴方向)
  40. /// </summary>
  41. public double Width { get; set; }
  42. /// <summary>
  43. /// 长轴方向角度[-180, 180)度
  44. /// 从左上指向右上的向量在XY平面的投影角度
  45. /// 0° = 指向X轴正方向,正角度逆时针,负角度顺时针
  46. /// </summary>
  47. public double MajorAxisAngle { get; set; }
  48. /// <summary>
  49. /// 四个角点的坐标(保持输入顺序)
  50. /// </summary>
  51. public List<Vector<double>> Corners { get; set; }
  52. /// <summary>
  53. /// 左上角点坐标
  54. /// </summary>
  55. public Vector<double> TopLeft => Corners != null && Corners.Count >= 1 ? Corners[0] : null;
  56. /// <summary>
  57. /// 右上角点坐标
  58. /// </summary>
  59. public Vector<double> TopRight => Corners != null && Corners.Count >= 2 ? Corners[1] : null;
  60. /// <summary>
  61. /// 右下角点坐标
  62. /// </summary>
  63. public Vector<double> BottomRight => Corners != null && Corners.Count >= 3 ? Corners[2] : null;
  64. /// <summary>
  65. /// 左下角点坐标
  66. /// </summary>
  67. public Vector<double> BottomLeft => Corners != null && Corners.Count >= 4 ? Corners[3] : null;
  68. /// <summary>
  69. /// 矩形的面积
  70. /// </summary>
  71. public double Area { get; set; }
  72. /// <summary>
  73. /// 拟合误差(均方根误差)
  74. /// </summary>
  75. public double FitError { get; set; }
  76. }
  77. /// <summary>
  78. /// 验证角点顺序并进行必要的调整
  79. /// 确保顺序为:左上 → 右上 → 右下 → 左下
  80. /// </summary>
  81. private static List<Vector<double>> ValidateAndOrderCorners(List<Vector<double>> corners)
  82. {
  83. if (corners == null || corners.Count != 4)
  84. throw new ArgumentException("需要4个角点");
  85. return SortCornersByTopBottomHeuristic(corners);
  86. }
  87. /// <summary>
  88. /// 计算从左上指向右上的长轴方向角度
  89. /// </summary>
  90. private static double CalculateMajorAxisAngle(Vector<double> topLeft, Vector<double> topRight)
  91. {
  92. // 计算方向向量(从左上指向右上)
  93. Vector<double> direction = topRight - topLeft;
  94. // 只考虑XY平面上的投影
  95. double x = direction[0];
  96. double y = direction[1];
  97. // 使用Math.Atan2计算角度(弧度)
  98. double angleRad = Math.Atan2(y, x);
  99. // 转换为度,得到有符号角度 [-180, 180)
  100. double angleDeg = angleRad * 180.0 / Math.PI;
  101. // Math.Atan2的结果已经是[-180, 180],但确保一下边界
  102. if (angleDeg >= 180.0)
  103. angleDeg -= 360.0;
  104. else if (angleDeg < -180.0)
  105. angleDeg += 360.0;
  106. return angleDeg;
  107. }
  108. /// <summary>
  109. /// 使用最小二乘法思想计算最优矩形中心点及相关参数(对外统一入口)
  110. /// 推荐使用改进的PCA方法,因为它能利用所有点信息进行优化
  111. /// </summary>
  112. public static RectangleResult CalculateOptimalCenter(List<Vector<double>> corners)
  113. {
  114. // 验证和调整角点顺序
  115. var orderedCorners = ValidateAndOrderCorners(corners);
  116. // 使用改进的PCA方法(考虑已知顺序)
  117. return CalculateByPCAWithKnownOrder(orderedCorners);
  118. }
  119. /// <summary>
  120. /// 方法1:基于已知角点顺序的直接计算(最准确)
  121. /// 直接使用左上和右上点计算长轴,然后计算其他参数
  122. /// </summary>
  123. public static RectangleResult CalculateByDirectMethod(List<Vector<double>> corners)
  124. {
  125. var orderedCorners = ValidateAndOrderCorners(corners);
  126. var topLeft = orderedCorners[0];
  127. var topRight = orderedCorners[1];
  128. var bottomRight = orderedCorners[2];
  129. var bottomLeft = orderedCorners[3];
  130. // 计算长轴(从左上指向右上)
  131. Vector<double> majorAxis = (topRight - topLeft).Normalize(2);
  132. // 计算短轴(从左上指向左下)
  133. Vector<double> minorAxis = (bottomLeft - topLeft).Normalize(2);
  134. // 确保短轴与长轴垂直(处理非正交情况)
  135. minorAxis = (minorAxis - majorAxis * minorAxis.DotProduct(majorAxis)).Normalize(2);
  136. int dim = orderedCorners[0].Count;
  137. // 计算中心点(四个角点的平均)
  138. var center = Vector<double>.Build.DenseOfEnumerable(
  139. Enumerable.Range(0, dim)
  140. .Select(i => orderedCorners.Select(c => c[i]).Average()));
  141. // 计算长度和宽度
  142. double length = Distance(topLeft, topRight);
  143. double width = Distance(topLeft, bottomLeft);
  144. // 计算长轴角度
  145. double angle = CalculateMajorAxisAngle(topLeft, topRight);
  146. // 计算拟合误差
  147. double fitError = CalculateFitErrorDirect(center, majorAxis, minorAxis, length, width, orderedCorners);
  148. return new RectangleResult
  149. {
  150. Center = center,
  151. MajorAxis = majorAxis,
  152. MinorAxis = minorAxis,
  153. Length = length,
  154. Width = width,
  155. MajorAxisAngle = angle,
  156. Corners = orderedCorners,
  157. Area = length * width,
  158. FitError = fitError
  159. };
  160. }
  161. /// <summary>
  162. /// 方法2:使用PCA但考虑已知角点顺序(推荐)
  163. /// 结合PCA的鲁棒性和已知顺序的准确性
  164. /// </summary>
  165. public static RectangleResult CalculateByPCAWithKnownOrder(List<Vector<double>> corners)
  166. {
  167. //打印输入点以调试
  168. //Console.WriteLine($"输入点 [1] : {corners[0][0]:F3},{corners[0][1]:F3}");
  169. //Console.WriteLine($"输入点 [2] : {corners[1][0]:F3},{corners[1][1]:F3}");
  170. //Console.WriteLine($"输入点 [3] : {corners[2][0]:F3},{corners[2][1]:F3}");
  171. //Console.WriteLine($"输入点 [4] : {corners[3][0]:F3},{corners[3][1]:F3}");
  172. var orderedCorners = ValidateAndOrderCorners(corners);
  173. Console.WriteLine($"");
  174. //打印输入点以调试
  175. //Console.WriteLine($"输出点 [1] : {orderedCorners[0][0]:F3},{orderedCorners[0][1]:F3}");
  176. //Console.WriteLine($"输出点 [2] : {orderedCorners[1][0]:F3},{orderedCorners[1][1]:F3}");
  177. //Console.WriteLine($"输出点 [3] : {orderedCorners[2][0]:F3},{orderedCorners[2][1]:F3}");
  178. //Console.WriteLine($"输出点 [4] : {orderedCorners[3][0]:F3},{orderedCorners[3][1]:F3}");
  179. int dim = orderedCorners[0].Count;
  180. // 步骤1:使用所有点进行PCA得到初步估计
  181. var matrix = Matrix<double>.Build.DenseOfRows(orderedCorners);
  182. var mean = Vector<double>.Build.DenseOfEnumerable(
  183. Enumerable.Range(0, dim).Select(i => matrix.Column(i).Average()));
  184. var centered = matrix.Clone();
  185. for (int i = 0; i < 4; i++)
  186. {
  187. centered.SetRow(i, centered.Row(i) - mean);
  188. }
  189. var covariance = centered.Transpose() * centered / 3.0;
  190. var evd = covariance.Evd();
  191. // 获取特征向量
  192. var eigenvectors = evd.EigenVectors;
  193. // 步骤2:使用已知顺序确定长轴方向
  194. var topLeft = orderedCorners[0];
  195. var topRight = orderedCorners[1];
  196. // 计算实际的长轴方向(从左上指向右上)
  197. Vector<double> actualMajorDirection = (topRight - topLeft).Normalize(2);
  198. // 从PCA的特征向量中找到最接近实际方向的那个
  199. Vector<double> bestAxis = eigenvectors.Column(0);
  200. double bestSimilarity = Math.Abs(actualMajorDirection.DotProduct(bestAxis));
  201. for (int i = 1; i < eigenvectors.ColumnCount; i++)
  202. {
  203. var axis = eigenvectors.Column(i);
  204. double similarity = Math.Abs(actualMajorDirection.DotProduct(axis));
  205. if (similarity > bestSimilarity)
  206. {
  207. bestSimilarity = similarity;
  208. bestAxis = axis;
  209. }
  210. }
  211. // 确保PCA轴的方向与实际方向一致(点积为正)
  212. Vector<double> majorAxis = bestAxis;
  213. if (actualMajorDirection.DotProduct(majorAxis) < 0)
  214. {
  215. majorAxis = -majorAxis;
  216. }
  217. // 计算短轴(与长轴垂直)
  218. Vector<double> minorAxis = CalculatePerpendicularAxis(majorAxis);
  219. // 将点投影到轴上计算中心、长度和宽度
  220. var projMajor = orderedCorners.Select(p => (p - mean).DotProduct(majorAxis)).ToList();
  221. var projMinor = orderedCorners.Select(p => (p - mean).DotProduct(minorAxis)).ToList();
  222. double centerMajor = (projMajor.Min() + projMajor.Max()) / 2.0;
  223. double centerMinor = (projMinor.Min() + projMinor.Max()) / 2.0;
  224. var center = mean + majorAxis * centerMajor + minorAxis * centerMinor;
  225. double length = Math.Abs(projMajor.Max() - projMajor.Min());
  226. double width = Math.Abs(projMinor.Max() - projMinor.Min());
  227. // 计算长轴角度(使用实际的左上-右上方向)
  228. double angle = CalculateMajorAxisAngle(topLeft, topRight);
  229. // 计算拟合误差
  230. double fitError = CalculateFitError(center, majorAxis, minorAxis, length, width, orderedCorners);
  231. return new RectangleResult
  232. {
  233. Center = center,
  234. MajorAxis = majorAxis,
  235. MinorAxis = minorAxis,
  236. Length = length,
  237. Width = width,
  238. MajorAxisAngle = angle,
  239. Corners = orderedCorners,
  240. Area = length * width,
  241. FitError = fitError
  242. };
  243. }
  244. /// <summary>
  245. /// 方法3:对角线优化方法(考虑已知顺序)
  246. /// </summary>
  247. public static RectangleResult CalculateByDiagonalOptimization(List<Vector<double>> corners)
  248. {
  249. var orderedCorners = ValidateAndOrderCorners(corners);
  250. var topLeft = orderedCorners[0];
  251. var topRight = orderedCorners[1];
  252. // 直接使用已知顺序计算长轴角度
  253. double angle = CalculateMajorAxisAngle(topLeft, topRight);
  254. // 计算长轴方向向量
  255. Vector<double> majorAxis = (topRight - topLeft).Normalize(2);
  256. Vector<double> minorAxis = CalculatePerpendicularAxis(majorAxis);
  257. // 使用所有对角线组合计算最佳中心
  258. var diagonalCombinations = new[]
  259. {
  260. (orderedCorners[0], orderedCorners[2]), // 左上-右下
  261. (orderedCorners[1], orderedCorners[3]) // 右上-左下
  262. };
  263. var centers = diagonalCombinations.Select(p => (p.Item1 + p.Item2) / 2.0).ToList();
  264. var center = (centers[0] + centers[1]) / 2.0;
  265. // 计算长度和宽度
  266. double length = Distance(topLeft, topRight);
  267. double width = Distance(topLeft, orderedCorners[3]); // 左上-左下
  268. // 计算拟合误差
  269. double fitError = CalculateFitError(center, majorAxis, minorAxis, length, width, orderedCorners);
  270. return new RectangleResult
  271. {
  272. Center = center,
  273. MajorAxis = majorAxis,
  274. MinorAxis = minorAxis,
  275. Length = length,
  276. Width = width,
  277. MajorAxisAngle = angle,
  278. Corners = orderedCorners,
  279. Area = length * width,
  280. FitError = fitError
  281. };
  282. }
  283. /// <summary>
  284. /// 辅助方法:计算与给定向量垂直的单位向量
  285. /// </summary>
  286. private static Vector<double> CalculatePerpendicularAxis(Vector<double> axis)
  287. {
  288. if (axis == null) throw new ArgumentNullException(nameof(axis));
  289. int n = axis.Count;
  290. if (n == 0) throw new ArgumentException("axis must have positive dimension", nameof(axis));
  291. // 2D场景:(-y, x) 为垂直向量
  292. if (n == 2)
  293. {
  294. var perp2 = Vector<double>.Build.DenseOfArray(new[] { -axis[1], axis[0] });
  295. if (perp2.L2Norm() < 1e-12) throw new ArgumentException("axis is zero vector", nameof(axis));
  296. return perp2.Normalize(2);
  297. }
  298. // 通用n维:选一个不与axis共线的标准基向量
  299. int idx = 0;
  300. for (int i = 0; i < n; i++)
  301. {
  302. if (Math.Abs(axis[i]) < 0.9) { idx = i; break; }
  303. }
  304. var temp = Vector<double>.Build.Dense(n, i => i == idx ? 1.0 : 0.0);
  305. var perpendicular = temp - axis * temp.DotProduct(axis);
  306. if (perpendicular.L2Norm() < 1e-12) throw new InvalidOperationException("failed to compute perpendicular vector");
  307. return perpendicular.Normalize(2);
  308. }
  309. /// <summary>
  310. /// 辅助方法:计算两点距离
  311. /// </summary>
  312. private static double Distance(Vector<double> a, Vector<double> b)
  313. {
  314. return (a - b).L2Norm();
  315. }
  316. /// <summary>
  317. /// 辅助方法:计算拟合误差
  318. /// </summary>
  319. private static double CalculateFitError(Vector<double> center, Vector<double> majorAxis, Vector<double> minorAxis, double length, double width, List<Vector<double>> corners)
  320. {
  321. // 生成理想矩形的四个角点
  322. var idealCorners = new List<Vector<double>>
  323. {
  324. center + majorAxis * (length / 2) + minorAxis * (width / 2), // 右上
  325. center + majorAxis * (length / 2) - minorAxis * (width / 2), // 右下
  326. center - majorAxis * (length / 2) - minorAxis * (width / 2), // 左下
  327. center - majorAxis * (length / 2) + minorAxis * (width / 2) // 左上
  328. };
  329. // 注意:理想角点顺序可能与输入不同,需要重新排序匹配
  330. // 这里简单计算所有对应点的最小距离和
  331. double sumSquared = 0;
  332. for (int i = 0; i < 4; i++)
  333. {
  334. // 找到最近的理想角点
  335. double minDist = double.MaxValue;
  336. foreach (var ideal in idealCorners)
  337. {
  338. double dist = Distance(corners[i], ideal);
  339. if (dist < minDist) minDist = dist;
  340. }
  341. sumSquared += minDist * minDist;
  342. }
  343. return Math.Sqrt(sumSquared / 4);
  344. }
  345. /// <summary>
  346. /// 直接计算方法的拟合误差
  347. /// </summary>
  348. private static double CalculateFitErrorDirect(Vector<double> center, Vector<double> majorAxis, Vector<double> minorAxis, double length, double width, List<Vector<double>> corners)
  349. {
  350. // 对于直接方法,我们知道每个角点的理想位置
  351. var idealCorners = new List<Vector<double>>
  352. {
  353. center - majorAxis * (length / 2) + minorAxis * (width / 2), // 左上
  354. center + majorAxis * (length / 2) + minorAxis * (width / 2), // 右上
  355. center + majorAxis * (length / 2) - minorAxis * (width / 2), // 右下
  356. center - majorAxis * (length / 2) - minorAxis * (width / 2) // 左下
  357. };
  358. double sumSquared = 0;
  359. for (int i = 0; i < 4; i++)
  360. {
  361. sumSquared += Math.Pow(Distance(corners[i], idealCorners[i]), 2);
  362. }
  363. return Math.Sqrt(sumSquared / 4);
  364. }
  365. /// <summary>
  366. /// 自动排序角点:左上 → 右上 → 右下 → 左下
  367. /// </summary>
  368. /// <param name="unorderedCorners">任意顺序的四个角点</param>
  369. /// <returns>按标准顺序排序的角点列表</returns>
  370. public static List<Vector<double>> SortCornersToRectangleOrder(List<Vector<double>> unorderedCorners)
  371. {
  372. if (unorderedCorners == null || unorderedCorners.Count != 4)
  373. throw new ArgumentException("需要4个角点");
  374. // 方法1:基于上下分组+水平排序的稳健方法
  375. return SortCornersByTopBottomHeuristic(unorderedCorners);
  376. }
  377. /// <summary>
  378. /// 方法1:基于上下分组与水平排序的稳健方法
  379. /// </summary>
  380. private static List<Vector<double>> SortCornersByTopBottomHeuristic(List<Vector<double>> corners)
  381. {
  382. if (corners == null || corners.Count != 4)
  383. throw new ArgumentException("需要4个角点");
  384. var orderHighY = BuildOrderByTopBottom(corners, topIsHigher: true);
  385. var orderLowY = BuildOrderByTopBottom(corners, topIsHigher: false);
  386. double scoreHigh = RectangleOrderScore(orderHighY);
  387. double scoreLow = RectangleOrderScore(orderLowY);
  388. return scoreHigh <= scoreLow ? orderHighY : orderLowY;
  389. }
  390. private static List<Vector<double>> BuildOrderByTopBottom(List<Vector<double>> corners, bool topIsHigher)
  391. {
  392. var sortedByY = topIsHigher
  393. ? corners.OrderByDescending(p => p[1]).ToList()
  394. : corners.OrderBy(p => p[1]).ToList();
  395. var topCandidates = sortedByY.Take(2).ToList();
  396. var bottomCandidates = sortedByY.Skip(2).Take(2).ToList();
  397. var top = topCandidates.OrderBy(p => p[0]).ToList();
  398. var bottom = bottomCandidates.OrderBy(p => p[0]).ToList();
  399. var topLeft = top[0];
  400. var topRight = top[1];
  401. var bottomLeft = bottom[0];
  402. var bottomRight = bottom[1];
  403. return new List<Vector<double>> { topLeft, topRight, bottomRight, bottomLeft };
  404. }
  405. private static double RectangleOrderScore(List<Vector<double>> ordered)
  406. {
  407. if (ordered == null || ordered.Count != 4)
  408. return double.MaxValue;
  409. var topLeft = ordered[0];
  410. var topRight = ordered[1];
  411. var bottomRight = ordered[2];
  412. var bottomLeft = ordered[3];
  413. double top = Distance(topLeft, topRight);
  414. double bottom = Distance(bottomLeft, bottomRight);
  415. double left = Distance(topLeft, bottomLeft);
  416. double right = Distance(topRight, bottomRight);
  417. double diag1 = Distance(topLeft, bottomRight);
  418. double diag2 = Distance(topRight, bottomLeft);
  419. double parallelScore = Math.Abs(top - bottom) + Math.Abs(left - right) + Math.Abs(diag1 - diag2);
  420. double xOrderPenalty = 0.0;
  421. if (topLeft[0] > topRight[0]) xOrderPenalty += 1000.0;
  422. if (bottomLeft[0] > bottomRight[0]) xOrderPenalty += 1000.0;
  423. return parallelScore + xOrderPenalty;
  424. }
  425. /// <summary>
  426. /// 调整角点顺序为标准矩形顺序(左上→右上→右下→左下)
  427. /// </summary>
  428. private static List<Vector<double>> AdjustToRectangleOrder(List<Vector<double>> corners)
  429. {
  430. return SortCornersByTopBottomHeuristic(corners);
  431. }
  432. }
  433. }