From 7447463c1a7339fb4a62f0a8f785bbf5a21c75b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E4=BC=9F?= Date: Fri, 15 May 2026 14:31:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ROI=20=E5=AF=B9=E9=BD=90=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E4=B8=8E=20TM=5FResult=20=E4=BD=8D=E5=A7=BF=E6=89=A9?= =?UTF-8?q?=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Core: Pose2D、Point2D、RoiAlignment、AlignmentRecipe(示教多边形→运行图刚体变换) - Processors: TemplateMatchAlignmentExtensions.ToPose2D / 四角与中心一致性校验 Co-authored-by: Cursor --- .../Alignment/AlignmentRecipe.cs | 21 ++++ XP.ImageProcessing.Core/Alignment/Point2D.cs | 4 + XP.ImageProcessing.Core/Alignment/Pose2D.cs | 18 ++++ .../Alignment/RoiAlignment.cs | 96 +++++++++++++++++++ .../TemplateMatchAlignmentExtensions.cs | 27 ++++++ 5 files changed, 166 insertions(+) create mode 100644 XP.ImageProcessing.Core/Alignment/AlignmentRecipe.cs create mode 100644 XP.ImageProcessing.Core/Alignment/Point2D.cs create mode 100644 XP.ImageProcessing.Core/Alignment/Pose2D.cs create mode 100644 XP.ImageProcessing.Core/Alignment/RoiAlignment.cs create mode 100644 XP.ImageProcessing.Processors/定位识别/TemplateMatchAlignmentExtensions.cs diff --git a/XP.ImageProcessing.Core/Alignment/AlignmentRecipe.cs b/XP.ImageProcessing.Core/Alignment/AlignmentRecipe.cs new file mode 100644 index 0000000..ff33afb --- /dev/null +++ b/XP.ImageProcessing.Core/Alignment/AlignmentRecipe.cs @@ -0,0 +1,21 @@ +namespace XP.ImageProcessing.Core.Alignment; + +/// +/// 示教阶段保存的对齐配方:基准位姿 + 示教图像素坐标下的检测 ROI。 +/// +public sealed class AlignmentRecipe +{ + /// 示教图上的基准位姿(建议示教图自匹配得到,或与模板 ROI 中心 + 角度 0 一致)。 + public Pose2D ReferencePose { get; set; } + + /// 示教图上的 ROI 多边形顶点(至少 3 点)。 + public List RoiPoints { get; set; } = new(); + + /// 将示教 ROI 变换到运行图坐标。 + public Point2D[] TransformRoi(Pose2D measuredPose) + => RoiAlignment.TransformPolygon(RoiPoints, ReferencePose, measuredPose); + + /// 变换为整型顶点,供检测算子注入。 + public (int X, int Y)[] TransformRoiToInt(Pose2D measuredPose) + => RoiAlignment.TransformPolygonToInt(RoiPoints, ReferencePose, measuredPose); +} diff --git a/XP.ImageProcessing.Core/Alignment/Point2D.cs b/XP.ImageProcessing.Core/Alignment/Point2D.cs new file mode 100644 index 0000000..26d1bb1 --- /dev/null +++ b/XP.ImageProcessing.Core/Alignment/Point2D.cs @@ -0,0 +1,4 @@ +namespace XP.ImageProcessing.Core.Alignment; + +/// 图像像素平面上的点(与 WPF/Emgu 解耦)。 +public readonly record struct Point2D(double X, double Y); diff --git a/XP.ImageProcessing.Core/Alignment/Pose2D.cs b/XP.ImageProcessing.Core/Alignment/Pose2D.cs new file mode 100644 index 0000000..5a80ae0 --- /dev/null +++ b/XP.ImageProcessing.Core/Alignment/Pose2D.cs @@ -0,0 +1,18 @@ +namespace XP.ImageProcessing.Core.Alignment; + +/// +/// 图像平面上的刚体位姿:绕 / 旋转 (度)。 +/// 与 TemplateMatchLib 的 CenterX/CenterY/Angle 约定一致。 +/// +public readonly record struct Pose2D(double X, double Y, double AngleDegrees) +{ + /// 示教/标准姿态(角度 0,中心由调用方指定)。 + public static Pose2D IdentityAt(double x, double y) => new(x, y, 0); + + /// + /// 由模板学习 ROI 矩形估计示教位姿中心(pattern 几何中心),角度默认 0。 + /// 更稳妥的做法是在示教图上自匹配得到 。 + /// + public static Pose2D FromTemplateRoiCenter(int roiX, int roiY, int roiWidth, int roiHeight, double angleDegrees = 0) + => new(roiX + roiWidth * 0.5, roiY + roiHeight * 0.5, angleDegrees); +} diff --git a/XP.ImageProcessing.Core/Alignment/RoiAlignment.cs b/XP.ImageProcessing.Core/Alignment/RoiAlignment.cs new file mode 100644 index 0000000..2770573 --- /dev/null +++ b/XP.ImageProcessing.Core/Alignment/RoiAlignment.cs @@ -0,0 +1,96 @@ +namespace XP.ImageProcessing.Core.Alignment; + +/// +/// 将示教图(模板坐标系)上的 ROI 点变换到运行图坐标。 +/// 旋转中心与模板匹配一致:绕 /(pattern 中心)。 +/// +public static class RoiAlignment +{ + /// + /// 刚体变换:示教图点 → 运行图点。 + /// :示教图上的基准位姿; + /// :运行图匹配位姿。 + /// + public static Point2D TransformPoint(Point2D point, Pose2D reference, Pose2D measured) + { + double dTheta = DegreesToRadians(measured.AngleDegrees - reference.AngleDegrees); + double cos = Math.Cos(dTheta); + double sin = Math.Sin(dTheta); + double dx = point.X - reference.X; + double dy = point.Y - reference.Y; + return new Point2D( + measured.X + cos * dx - sin * dy, + measured.Y + sin * dx + cos * dy); + } + + public static Point2D TransformPoint(double x, double y, Pose2D reference, Pose2D measured) + => TransformPoint(new Point2D(x, y), reference, measured); + + /// 变换多边形顶点(顺序不变)。 + public static Point2D[] TransformPolygon(IReadOnlyList templatePoints, Pose2D reference, Pose2D measured) + { + if (templatePoints == null || templatePoints.Count == 0) + return Array.Empty(); + + var result = new Point2D[templatePoints.Count]; + for (int i = 0; i < templatePoints.Count; i++) + result[i] = TransformPoint(templatePoints[i], reference, measured); + return result; + } + + /// 变换后四舍五入为整型顶点,供 BGA 等算子 PolyX/PolyY 注入。 + public static (int X, int Y)[] TransformPolygonToInt( + IReadOnlyList templatePoints, + Pose2D reference, + Pose2D measured) + { + var transformed = TransformPolygon(templatePoints, reference, measured); + var result = new (int X, int Y)[transformed.Length]; + for (int i = 0; i < transformed.Length; i++) + { + result[i] = ( + (int)Math.Round(transformed[i].X, MidpointRounding.AwayFromZero), + (int)Math.Round(transformed[i].Y, MidpointRounding.AwayFromZero)); + } + + return result; + } + + /// 变换轴对齐矩形为四个顶点(左上、右上、右下、左下)。 + public static Point2D[] TransformRect(double x, double y, double width, double height, Pose2D reference, Pose2D measured) + { + var corners = new[] + { + new Point2D(x, y), + new Point2D(x + width, y), + new Point2D(x + width, y + height), + new Point2D(x, y + height) + }; + return TransformPolygon(corners, reference, measured); + } + + /// + /// 校验匹配结果四角质心是否与 Center 一致(用于确认库的中心/角度约定)。 + /// + public static bool IsMatchCenterConsistentWithCorners( + double centerX, + double centerY, + double ltX, + double ltY, + double rtX, + double rtY, + double rbX, + double rbY, + double lbX, + double lbY, + double tolerancePixels = 1.0) + { + double cx = (ltX + rtX + rbX + lbX) * 0.25; + double cy = (ltY + rtY + rbY + lbY) * 0.25; + double dx = cx - centerX; + double dy = cy - centerY; + return dx * dx + dy * dy <= tolerancePixels * tolerancePixels; + } + + private static double DegreesToRadians(double degrees) => degrees * (Math.PI / 180.0); +} diff --git a/XP.ImageProcessing.Processors/定位识别/TemplateMatchAlignmentExtensions.cs b/XP.ImageProcessing.Processors/定位识别/TemplateMatchAlignmentExtensions.cs new file mode 100644 index 0000000..873650f --- /dev/null +++ b/XP.ImageProcessing.Processors/定位识别/TemplateMatchAlignmentExtensions.cs @@ -0,0 +1,27 @@ +using XP.ImageProcessing.Core.Alignment; + +namespace XP.ImageProcessing.Processors; + +/// +/// 将 TemplateMatchLib 匹配结果转换为对齐工具使用的 。 +/// +public static class TemplateMatchAlignmentExtensions +{ + public static Pose2D ToPose2D(this TM_Result result) + => new(result.CenterX, result.CenterY, result.Angle); + + /// 四角质心是否与 Center 一致(容差默认 1 像素)。 + public static bool IsCenterConsistentWithCorners(this TM_Result result, double tolerancePixels = 1.0) + => RoiAlignment.IsMatchCenterConsistentWithCorners( + result.CenterX, + result.CenterY, + result.LtX, + result.LtY, + result.RtX, + result.RtY, + result.RbX, + result.RbY, + result.LbX, + result.LbY, + tolerancePixels); +}