ソースを参照

新增矩形中心点与方向辅助类及相关优化

新增 RectangleCenterCalculator 辅助类,支持多种方法根据四角点计算矩形中心、长轴方向、角度、长宽、面积及拟合误差,适用于三维空间(主要考虑XY平面)。在 Common.cs 增加二维距离计算方法。Management.cs 读取产品编码时增加地址格式化,提升兼容性。项目文件同步添加新类编译项。
孝锋 徐 8 ヶ月 前
コミット
012b6cb41b

+ 15 - 0
TeamAAS-VM/Core/Common.cs

@@ -106,5 +106,20 @@ namespace TeamAAS_VP.Core
             double angleRad = CalculateAngleBetweenPoints(p1, p2);
             return angleRad * (180.0 / Math.PI);
         }
+
+        /// <summary>
+        /// 使用 Math 类计算两点距离
+        /// </summary>
+        /// <param name="x1"></param>
+        /// <param name="y1"></param>
+        /// <param name="x2"></param>
+        /// <param name="y2"></param>
+        /// <returns></returns>
+        public static double CalculateDistance2D(double x1, double y1, double x2, double y2)
+        {
+            double dx = x2 - x1;
+            double dy = y2 - y1;
+            return Math.Sqrt(dx * dx + dy * dy);
+        }
     }
 }

+ 1 - 1
TeamAAS-VM/Core/Management.cs

@@ -1086,7 +1086,7 @@ namespace TeamAAS_VP.Core
                 }
 
                 //获取产品编码
-                _productService.CurrentProductCode = plc.ReadNode<string>(addressConfig.In_Code.Address);
+                _productService.CurrentProductCode = plc.ReadNode<string>(string.Format(addressConfig.In_Code.Address, 0));
                 //如果产品编码为空,则以当前的时间戳作为编码
                 if (string.IsNullOrEmpty(_productService.CurrentProductCode))
                 {

+ 414 - 0
TeamAAS-VM/Core/RectangleCenterCalculator.cs

@@ -0,0 +1,414 @@
+using MathNet.Numerics.LinearAlgebra;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Core
+{
+    /// <summary>
+    /// 提供多种方法用于计算由四个角点定义的矩形中心点、长轴角度以及相关矩形参数的辅助类。
+    /// 角点顺序约定:左上(0) → 右上(1) → 右下(2) → 左下(3)
+    /// 长轴方向:从左上角(0)指向右上角(1)
+    /// 角度范围:[-180, 180) 度,0°指向X轴正方向,逆时针为正
+    /// </summary>
+    public class RectangleCenterCalculator
+    {
+        /// <summary>
+        /// 矩形中心及方向信息的综合结果
+        /// </summary>
+        public class RectangleResult
+        {
+            /// <summary>
+            /// 矩形中心点坐标
+            /// </summary>
+            public Vector<double> Center { get; set; }
+
+            /// <summary>
+            /// 矩形长轴方向向量(从左上指向右上的单位向量)
+            /// </summary>
+            public Vector<double> MajorAxis { get; set; }
+
+            /// <summary>
+            /// 矩形短轴方向向量(单位向量)
+            /// </summary>
+            public Vector<double> MinorAxis { get; set; }
+
+            /// <summary>
+            /// 矩形长度(沿长轴方向,左上到右上的距离)
+            /// </summary>
+            public double Length { get; set; }
+
+            /// <summary>
+            /// 矩形宽度(沿短轴方向)
+            /// </summary>
+            public double Width { get; set; }
+
+            /// <summary>
+            /// 长轴方向角度[-180, 180)度
+            /// 从左上指向右上的向量在XY平面的投影角度
+            /// 0° = 指向X轴正方向,正角度逆时针,负角度顺时针
+            /// </summary>
+            public double MajorAxisAngle { get; set; }
+
+            /// <summary>
+            /// 四个角点的坐标(保持输入顺序)
+            /// </summary>
+            public List<Vector<double>> Corners { get; set; }
+
+            /// <summary>
+            /// 左上角点坐标
+            /// </summary>
+            public Vector<double> TopLeft => Corners != null && Corners.Count >= 1 ? Corners[0] : null;
+
+            /// <summary>
+            /// 右上角点坐标
+            /// </summary>
+            public Vector<double> TopRight => Corners != null && Corners.Count >= 2 ? Corners[1] : null;
+
+            /// <summary>
+            /// 右下角点坐标
+            /// </summary>
+            public Vector<double> BottomRight => Corners != null && Corners.Count >= 3 ? Corners[2] : null;
+
+            /// <summary>
+            /// 左下角点坐标
+            /// </summary>
+            public Vector<double> BottomLeft => Corners != null && Corners.Count >= 4 ? Corners[3] : null;
+
+            /// <summary>
+            /// 矩形的面积
+            /// </summary>
+            public double Area { get; set; }
+
+            /// <summary>
+            /// 拟合误差(均方根误差)
+            /// </summary>
+            public double FitError { get; set; }
+        }
+
+        /// <summary>
+        /// 验证角点顺序并进行必要的调整
+        /// 确保顺序为:左上 → 右上 → 右下 → 左下
+        /// </summary>
+        private static List<Vector<double>> ValidateAndOrderCorners(List<Vector<double>> corners)
+        {
+            if (corners == null || corners.Count != 4)
+                throw new ArgumentException("需要4个角点");
+
+            // 这里可以添加自动排序逻辑,但既然您已知顺序,直接返回
+            // 如果未来需要自动排序,可以使用以下逻辑:
+            // 1. 找到最左边的两个点作为左边界
+            // 2. 根据Y坐标区分左上和左下
+            // 3. 计算中心点,确定其他点位置
+
+            return corners; // 假设输入已经按正确顺序
+        }
+
+        /// <summary>
+        /// 计算从左上指向右上的长轴方向角度
+        /// </summary>
+        private static double CalculateMajorAxisAngle(Vector<double> topLeft, Vector<double> topRight)
+        {
+            // 计算方向向量(从左上指向右上)
+            Vector<double> 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;
+        }
+
+        /// <summary>
+        /// 使用最小二乘法思想计算最优矩形中心点及相关参数(对外统一入口)
+        /// 推荐使用改进的PCA方法,因为它能利用所有点信息进行优化
+        /// </summary>
+        public static RectangleResult CalculateOptimalCenter(List<Vector<double>> corners)
+        {
+            // 验证和调整角点顺序
+            var orderedCorners = ValidateAndOrderCorners(corners);
+
+            // 使用改进的PCA方法(考虑已知顺序)
+            return CalculateByPCAWithKnownOrder(orderedCorners);
+        }
+
+        /// <summary>
+        /// 方法1:基于已知角点顺序的直接计算(最准确)
+        /// 直接使用左上和右上点计算长轴,然后计算其他参数
+        /// </summary>
+        public static RectangleResult CalculateByDirectMethod(List<Vector<double>> corners)
+        {
+            var orderedCorners = ValidateAndOrderCorners(corners);
+
+            var topLeft = orderedCorners[0];
+            var topRight = orderedCorners[1];
+            var bottomRight = orderedCorners[2];
+            var bottomLeft = orderedCorners[3];
+
+            // 计算长轴(从左上指向右上)
+            Vector<double> majorAxis = (topRight - topLeft).Normalize(2);
+
+            // 计算短轴(从左上指向左下)
+            Vector<double> minorAxis = (bottomLeft - topLeft).Normalize(2);
+
+            // 确保短轴与长轴垂直(处理非正交情况)
+            minorAxis = (minorAxis - majorAxis * minorAxis.DotProduct(majorAxis)).Normalize(2);
+
+            // 计算中心点(四个角点的平均)
+            var center = Vector<double>.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
+            };
+        }
+
+        /// <summary>
+        /// 方法2:使用PCA但考虑已知角点顺序(推荐)
+        /// 结合PCA的鲁棒性和已知顺序的准确性
+        /// </summary>
+        public static RectangleResult CalculateByPCAWithKnownOrder(List<Vector<double>> corners)
+        {
+            var orderedCorners = ValidateAndOrderCorners(corners);
+
+            // 步骤1:使用所有点进行PCA得到初步估计
+            var matrix = Matrix<double>.Build.DenseOfRows(orderedCorners);
+            var mean = Vector<double>.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<double> actualMajorDirection = (topRight - topLeft).Normalize(2);
+
+            // 从PCA的特征向量中找到最接近实际方向的那个
+            Vector<double> 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<double> majorAxis = bestAxis;
+            if (actualMajorDirection.DotProduct(majorAxis) < 0)
+            {
+                majorAxis = -majorAxis;
+            }
+
+            // 计算短轴(与长轴垂直)
+            Vector<double> 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
+            };
+        }
+
+        /// <summary>
+        /// 方法3:对角线优化方法(考虑已知顺序)
+        /// </summary>
+        public static RectangleResult CalculateByDiagonalOptimization(List<Vector<double>> corners)
+        {
+            var orderedCorners = ValidateAndOrderCorners(corners);
+            var topLeft = orderedCorners[0];
+            var topRight = orderedCorners[1];
+
+            // 直接使用已知顺序计算长轴角度
+            double angle = CalculateMajorAxisAngle(topLeft, topRight);
+
+            // 计算长轴方向向量
+            Vector<double> majorAxis = (topRight - topLeft).Normalize(2);
+            Vector<double> 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
+            };
+        }
+
+        /// <summary>
+        /// 辅助方法:计算与给定向量垂直的单位向量
+        /// </summary>
+        private static Vector<double> CalculatePerpendicularAxis(Vector<double> axis)
+        {
+            // 找一个不与axis共线的向量
+            Vector<double> temp;
+            if (Math.Abs(axis[0]) < 0.9)
+                temp = Vector<double>.Build.Dense(new[] { 1.0, 0.0, 0.0 });
+            else
+                temp = Vector<double>.Build.Dense(new[] { 0.0, 1.0, 0.0 });
+
+            var perpendicular = temp - axis * temp.DotProduct(axis);
+            return perpendicular.Normalize(2);
+        }
+
+        /// <summary>
+        /// 辅助方法:计算两点距离
+        /// </summary>
+        private static double Distance(Vector<double> a, Vector<double> b)
+        {
+            return (a - b).L2Norm();
+        }
+
+        /// <summary>
+        /// 辅助方法:计算拟合误差
+        /// </summary>
+        private static double CalculateFitError(Vector<double> center, Vector<double> majorAxis, Vector<double> minorAxis, double length, double width, List<Vector<double>> corners)
+        {
+            // 生成理想矩形的四个角点
+            var idealCorners = new List<Vector<double>>
+        {
+            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);
+        }
+
+        /// <summary>
+        /// 直接计算方法的拟合误差
+        /// </summary>
+        private static double CalculateFitErrorDirect(Vector<double> center, Vector<double> majorAxis, Vector<double> minorAxis, double length, double width, List<Vector<double>> corners)
+        {
+            // 对于直接方法,我们知道每个角点的理想位置
+            var idealCorners = new List<Vector<double>>
+        {
+            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);
+        }
+    }
+}

+ 1 - 0
TeamAAS-VM/TeamAAS-VP.csproj

@@ -529,6 +529,7 @@
     <Compile Include="Controls\PlcRobotManual.xaml.cs">
       <DependentUpon>PlcRobotManual.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Core\RectangleCenterCalculator.cs" />
     <Compile Include="Events\MainTabSwitchNotification.cs" />
     <Compile Include="Models\Feeder\ScrewFeederInfo.cs" />
     <Compile Include="Models\ScrewFeederBatchRecord.cs" />