新增 ROI 对齐基础能力并打通到算子与 UI。
统一补齐对齐核心工具类、RoiAlignment 算子、模板匹配对齐扩展和多语言资源,便于在检测前稳定完成示教 ROI 到运行图的变换。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
// ============================================================================
|
||||
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
||||
// 文件名: RoiAlignmentProcessor.cs
|
||||
// 描述: 示教多边形 ROI 刚体对齐(平移+旋转)
|
||||
//
|
||||
// 流水线约定:
|
||||
// 输入: 示教 Poly* + ReferencePose(Ref*) + 上一步模板匹配的 MeasuredPose(Measured*)
|
||||
// 输出: OutputData 中含变换后的 PolyCount/PolyX/PolyY/RoiMode,可拷贝到 VoidMeasurement 等算子
|
||||
// 或: 流水线直接调用 XP.ImageProcessing.Core.Alignment.RoiAlignmentApplier
|
||||
// ============================================================================
|
||||
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.Structure;
|
||||
using XP.ImageProcessing.Core;
|
||||
using XP.ImageProcessing.Core.Alignment;
|
||||
using Serilog;
|
||||
|
||||
namespace XP.ImageProcessing.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// 将示教图上的多边形 ROI 变换到当前图(不修改图像,仅输出对齐后的 ROI 参数)。
|
||||
/// </summary>
|
||||
public class RoiAlignmentProcessor : ImageProcessorBase
|
||||
{
|
||||
private static readonly ILogger _logger = Log.ForContext<RoiAlignmentProcessor>();
|
||||
|
||||
public RoiAlignmentProcessor()
|
||||
{
|
||||
Name = LocalizationHelper.GetString("RoiAlignmentProcessor_Name");
|
||||
Description = LocalizationHelper.GetString("RoiAlignmentProcessor_Description");
|
||||
}
|
||||
|
||||
protected override void InitializeParameters()
|
||||
{
|
||||
Parameters.Add("RefCenterX", new ProcessorParameter(
|
||||
"RefCenterX",
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_RefCenterX"),
|
||||
typeof(double), 0.0, null, null,
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_RefCenterX_Desc")));
|
||||
|
||||
Parameters.Add("RefCenterY", new ProcessorParameter(
|
||||
"RefCenterY",
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_RefCenterY"),
|
||||
typeof(double), 0.0, null, null,
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_RefCenterY_Desc")));
|
||||
|
||||
Parameters.Add("RefAngle", new ProcessorParameter(
|
||||
"RefAngle",
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_RefAngle"),
|
||||
typeof(double), 0.0, -180.0, 180.0,
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_RefAngle_Desc")));
|
||||
|
||||
Parameters.Add("MeasuredCenterX", new ProcessorParameter(
|
||||
"MeasuredCenterX",
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_MeasuredCenterX"),
|
||||
typeof(double), 0.0, null, null,
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_MeasuredCenterX_Desc")));
|
||||
|
||||
Parameters.Add("MeasuredCenterY", new ProcessorParameter(
|
||||
"MeasuredCenterY",
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_MeasuredCenterY"),
|
||||
typeof(double), 0.0, null, null,
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_MeasuredCenterY_Desc")));
|
||||
|
||||
Parameters.Add("MeasuredAngle", new ProcessorParameter(
|
||||
"MeasuredAngle",
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_MeasuredAngle"),
|
||||
typeof(double), 0.0, -180.0, 180.0,
|
||||
LocalizationHelper.GetString("RoiAlignmentProcessor_MeasuredAngle_Desc")));
|
||||
|
||||
Parameters.Add(RoiPolygonParameterNames.PolyCount, new ProcessorParameter(
|
||||
RoiPolygonParameterNames.PolyCount,
|
||||
RoiPolygonParameterNames.PolyCount,
|
||||
typeof(int), 0, null, null,
|
||||
"") { IsVisible = false });
|
||||
|
||||
for (int i = 0; i < RoiPolygonParameterNames.MaxPoints; i++)
|
||||
{
|
||||
Parameters.Add(RoiPolygonParameterNames.PolyX(i), new ProcessorParameter(
|
||||
RoiPolygonParameterNames.PolyX(i),
|
||||
RoiPolygonParameterNames.PolyX(i),
|
||||
typeof(int), 0, null, null,
|
||||
"") { IsVisible = false });
|
||||
|
||||
Parameters.Add(RoiPolygonParameterNames.PolyY(i), new ProcessorParameter(
|
||||
RoiPolygonParameterNames.PolyY(i),
|
||||
RoiPolygonParameterNames.PolyY(i),
|
||||
typeof(int), 0, null, null,
|
||||
"") { IsVisible = false });
|
||||
}
|
||||
}
|
||||
|
||||
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
|
||||
{
|
||||
OutputData.Clear();
|
||||
|
||||
var reference = new Pose2D(
|
||||
GetParameter<double>("RefCenterX"),
|
||||
GetParameter<double>("RefCenterY"),
|
||||
GetParameter<double>("RefAngle"));
|
||||
|
||||
var measured = new Pose2D(
|
||||
GetParameter<double>("MeasuredCenterX"),
|
||||
GetParameter<double>("MeasuredCenterY"),
|
||||
GetParameter<double>("MeasuredAngle"));
|
||||
|
||||
var paramDict = Parameters.ToDictionary(p => p.Key, p => p.Value.Value);
|
||||
var teachPoints = RoiAlignmentApplier.ReadTeachPolygon(paramDict);
|
||||
|
||||
var result = RoiAlignmentApplier.Apply(reference, teachPoints, measured);
|
||||
RoiAlignmentApplier.WriteToOutputData(OutputData, result);
|
||||
OutputData[RoiAlignmentOutputKeys.Success] = result.Success;
|
||||
if (!string.IsNullOrEmpty(result.ErrorMessage))
|
||||
OutputData[RoiAlignmentOutputKeys.Message] = result.ErrorMessage!;
|
||||
|
||||
OutputData["ResultText"] = OutputData[RoiAlignmentOutputKeys.ResultText] = result.Success
|
||||
? $"ROI aligned: {result.TransformedPointsInt.Count} pts"
|
||||
: $"ROI align failed: {result.ErrorMessage}";
|
||||
|
||||
if (result.Success)
|
||||
_logger.Debug("RoiAlignment: {Count} points, ref=({Rx:F1},{Ry:F1},{Ra:F1}) meas=({Mx:F1},{My:F1},{Ma:F1})",
|
||||
result.TransformedPointsInt.Count,
|
||||
reference.X, reference.Y, reference.AngleDegrees,
|
||||
measured.X, measured.Y, measured.AngleDegrees);
|
||||
else
|
||||
_logger.Warning("RoiAlignment failed: {Msg}", result.ErrorMessage);
|
||||
|
||||
return inputImage.Clone();
|
||||
}
|
||||
}
|
||||
@@ -24,4 +24,19 @@ public static class TemplateMatchAlignmentExtensions
|
||||
result.LbX,
|
||||
result.LbY,
|
||||
tolerancePixels);
|
||||
|
||||
/// <summary>从 RotatedTemplateMatching 的 OutputData 读取测量位姿。</summary>
|
||||
public static bool TryReadMeasuredPose(
|
||||
IReadOnlyDictionary<string, object>? outputData,
|
||||
out Pose2D measuredPose,
|
||||
out string? errorMessage,
|
||||
int matchIndex = 0)
|
||||
=> TemplateMatchOutputReader.TryReadMeasuredPose(outputData, out measuredPose, out errorMessage, matchIndex);
|
||||
|
||||
/// <summary>用匹配结果 + 示教配方一步得到对齐后的 ROI。</summary>
|
||||
public static RoiAlignmentResult AlignRecipe(
|
||||
AlignmentRecipe recipe,
|
||||
IReadOnlyDictionary<string, object>? templateMatchOutput,
|
||||
int matchIndex = 0)
|
||||
=> RoiAlignmentApplier.ApplyFromTemplateMatchOutput(recipe, templateMatchOutput, matchIndex);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user