Files
XplorePlane/XP.ImageProcessing.Processors/定位识别/RotatedTemplateMatchingProcessor.cs
T

272 lines
11 KiB
C#

// ============================================================================
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
// 文件名: RotatedTemplateMatchingProcessor.cs
// 描述: 旋转多目标模板匹配(金字塔、SIMD、亚像素)
// 功能:
// - 调用 TemplateMatchLib.dll 实现高性能旋转模板匹配
// - 支持多目标检测和重叠过滤
// - 支持图像金字塔加速
// - 支持 SIMD 加速和亚像素精度
// - 输出匹配结果(中心坐标、角度、分数、四角坐标)
// ============================================================================
using System;
using Emgu.CV;
using Emgu.CV.Structure;
using XP.ImageProcessing.Core;
using Serilog;
namespace XP.ImageProcessing.Processors;
/// <summary>
/// 旋转多目标模板匹配(定位识别),基于 TemplateMatchLib 原生库。
/// </summary>
public class RotatedTemplateMatchingProcessor : ImageProcessorBase
{
private static readonly ILogger _logger = Log.ForContext<RotatedTemplateMatchingProcessor>();
public RotatedTemplateMatchingProcessor()
{
Name = LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_Name");
Description = LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_Description");
}
protected override void InitializeParameters()
{
Parameters.Add("TemplatePath", new ProcessorParameter(
"TemplatePath",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_TemplatePath"),
typeof(string),
string.Empty,
null,
null,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_TemplatePath_Desc")));
Parameters.Add("MatchThreshold", new ProcessorParameter(
"MatchThreshold",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_MatchThreshold"),
typeof(double),
0.75,
0.0,
1.0,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_MatchThreshold_Desc")));
Parameters.Add("MaxMatchCount", new ProcessorParameter(
"MaxMatchCount",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_MaxMatchCount"),
typeof(int),
1,
1,
100,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_MaxMatchCount_Desc")));
Parameters.Add("ToleranceAngle", new ProcessorParameter(
"ToleranceAngle",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_ToleranceAngle"),
typeof(double),
0.0,
0.0,
180.0,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_ToleranceAngle_Desc")));
Parameters.Add("MaxOverlap", new ProcessorParameter(
"MaxOverlap",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_MaxOverlap"),
typeof(double),
0.3,
0.0,
1.0,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_MaxOverlap_Desc")));
Parameters.Add("MinReduceArea", new ProcessorParameter(
"MinReduceArea",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_MinReduceArea"),
typeof(int),
256,
64,
4096,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_MinReduceArea_Desc")));
Parameters.Add("UseSIMD", new ProcessorParameter(
"UseSIMD",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_UseSIMD"),
typeof(bool),
true,
null,
null,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_UseSIMD_Desc")));
Parameters.Add("UseSubPixel", new ProcessorParameter(
"UseSubPixel",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_UseSubPixel"),
typeof(bool),
false,
null,
null,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_UseSubPixel_Desc")));
Parameters.Add("DrawResults", new ProcessorParameter(
"DrawResults",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_DrawResults"),
typeof(bool),
true,
null,
null,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_DrawResults_Desc")));
Parameters.Add("DrawThickness", new ProcessorParameter(
"DrawThickness",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_DrawThickness"),
typeof(int),
1,
1,
8,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_DrawThickness_Desc")));
Parameters.Add("ModelPath", new ProcessorParameter(
"ModelPath",
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_ModelPath"),
typeof(string),
string.Empty,
null,
null,
LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_ModelPath_Desc")));
}
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
{
var path = (GetParameter<string>("TemplatePath") ?? string.Empty).Trim();
var modelPath = (GetParameter<string>("ModelPath") ?? string.Empty).Trim();
var threshold = GetParameter<double>("MatchThreshold");
var maxCount = GetParameter<int>("MaxMatchCount");
var toleranceAngle = GetParameter<double>("ToleranceAngle");
var maxOverlap = GetParameter<double>("MaxOverlap");
var minReduceArea = GetParameter<int>("MinReduceArea");
var useSIMD = GetParameter<bool>("UseSIMD");
var useSubPixel = GetParameter<bool>("UseSubPixel");
OutputData.Clear();
var output = inputImage.Clone();
// 模板路径和模型路径都为空时报错
bool hasModel = !string.IsNullOrEmpty(modelPath) && System.IO.File.Exists(modelPath);
bool hasTemplate = !string.IsNullOrEmpty(path) && System.IO.File.Exists(path);
if (!hasModel && !hasTemplate)
{
_logger.Warning("RotatedTemplateMatching: no template or model file found");
OutputData["Matched"] = false;
OutputData["MatchCount"] = 0;
OutputData["Message"] = LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_Msg_TemplateNotFound");
return output;
}
try
{
using var matcher = new TemplateMatcherHandle();
// 优先加载模型文件,否则从模板图片学习
bool modelLoaded = false;
if (hasModel)
{
modelLoaded = matcher.LoadModel(modelPath);
if (modelLoaded)
_logger.Debug("RotatedTemplateMatching: loaded model from {Path}", modelPath);
}
if (!modelLoaded)
{
if (!hasTemplate)
{
OutputData["Matched"] = false;
OutputData["MatchCount"] = 0;
OutputData["Message"] = LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_Msg_TemplateNotFound");
return output;
}
if (!matcher.LearnPatternFromFile(path))
{
OutputData["Matched"] = false;
OutputData["MatchCount"] = 0;
OutputData["Message"] = LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_Msg_TemplateLearnFailed");
return output;
}
// 学习成功后自动保存模型
if (!string.IsNullOrEmpty(modelPath))
{
var dir = System.IO.Path.GetDirectoryName(modelPath);
if (!string.IsNullOrEmpty(dir) && !System.IO.Directory.Exists(dir))
System.IO.Directory.CreateDirectory(dir);
if (matcher.SaveModel(modelPath))
_logger.Information("RotatedTemplateMatching: model saved to {Path}", modelPath);
else
_logger.Warning("RotatedTemplateMatching: failed to save model to {Path}", modelPath);
}
}
var param = new TM_Params
{
Score = threshold,
ToleranceAngle = toleranceAngle,
MaxOverlap = maxOverlap,
MaxCount = maxCount,
MinReduceArea = minReduceArea,
UseSIMD = useSIMD ? 1 : 0,
UseSubPixel = useSubPixel ? 1 : 0
};
IntPtr srcData = inputImage.Mat.DataPointer;
int srcWidth = inputImage.Width;
int srcHeight = inputImage.Height;
int srcStep = (int)inputImage.Mat.Step;
var results = matcher.Match(srcData, srcWidth, srcHeight, srcStep, param);
OutputData["Matched"] = results.Length > 0;
OutputData["MatchCount"] = results.Length;
OutputData["MatchTime"] = matcher.LastMatchTime;
for (int i = 0; i < results.Length; i++)
{
var r = results[i];
string prefix = results.Length == 1 ? "" : $"[{i}]";
OutputData[$"CenterX{prefix}"] = r.CenterX;
OutputData[$"CenterY{prefix}"] = r.CenterY;
OutputData[$"Angle{prefix}"] = r.Angle;
OutputData[$"Score{prefix}"] = r.Score;
OutputData[$"LtX{prefix}"] = r.LtX;
OutputData[$"LtY{prefix}"] = r.LtY;
OutputData[$"RtX{prefix}"] = r.RtX;
OutputData[$"RtY{prefix}"] = r.RtY;
OutputData[$"RbX{prefix}"] = r.RbX;
OutputData[$"RbY{prefix}"] = r.RbY;
OutputData[$"LbX{prefix}"] = r.LbX;
OutputData[$"LbY{prefix}"] = r.LbY;
}
_logger.Debug("RotatedTemplateMatching: Found {Count} matches in {Time:F1}ms",
results.Length, matcher.LastMatchTime);
return output;
}
catch (DllNotFoundException ex)
{
_logger.Error(ex, "RotatedTemplateMatching: TemplateMatchLib.dll not found");
OutputData["Matched"] = false;
OutputData["MatchCount"] = 0;
OutputData["Message"] = LocalizationHelper.GetString("RotatedTemplateMatchingProcessor_Msg_DllNotFound");
return output;
}
catch (Exception ex)
{
_logger.Error(ex, "RotatedTemplateMatching: unexpected error");
OutputData["Matched"] = false;
OutputData["MatchCount"] = 0;
OutputData["Message"] = ex.Message;
return output;
}
}
}