// ============================================================================
// TemplateMatchNative.cs
// C++ DLL P/Invoke 封装层
// 提供对 TemplateMatchLib.dll 的托管调用接口
// ============================================================================
using System;
using System.Runtime.InteropServices;
namespace XP.ImageProcessing.Processors;
///
/// 匹配参数(与C++ TM_Params对应)
///
[StructLayout(LayoutKind.Sequential)]
public struct TM_Params
{
/// 匹配阈值 (0~1)
public double Score;
/// 角度容差 (度),0表示不旋转
public double ToleranceAngle;
/// 最大重叠比例 (0~1)
public double MaxOverlap;
/// 最大匹配数
public int MaxCount;
/// 金字塔最小面积,默认256
public int MinReduceArea;
/// 是否使用SIMD加速 (1=是, 0=否)
public int UseSIMD;
/// 是否亚像素估计 (1=是, 0=否)
public int UseSubPixel;
///
/// 开启亚像素且角度容差绝对值超过该值时,托管封装会在调用原生库前关闭亚像素,
/// 以避免部分版本 TemplateMatchLib 在 Debug 下出现 vector 越界断言。
///
public const double SubPixelAngleSafetyLimitDegrees = 90.0;
///
/// 创建默认参数
///
public static TM_Params Default => new TM_Params
{
Score = 0.75,
ToleranceAngle = 0,
MaxOverlap = 0.3,
MaxCount = 1,
MinReduceArea = 256,
UseSIMD = 1,
UseSubPixel = 0
};
}
///
/// 单个匹配结果(与C++ TM_Result对应)
///
[StructLayout(LayoutKind.Sequential)]
public struct TM_Result
{
/// 匹配中心X
public double CenterX;
/// 匹配中心Y
public double CenterY;
/// 匹配角度 (度)
public double Angle;
/// 匹配分数
public double Score;
/// 左上角X
public double LtX;
/// 左上角Y
public double LtY;
/// 右上角X
public double RtX;
/// 右上角Y
public double RtY;
/// 右下角X
public double RbX;
/// 右下角Y
public double RbY;
/// 左下角X
public double LbX;
/// 左下角Y
public double LbY;
}
///
/// TemplateMatchLib.dll P/Invoke 接口
///
public static class TemplateMatchNative
{
private const string DllName = "TemplateMatchLib.dll";
/// 创建匹配器实例
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr TM_Create();
/// 销毁匹配器实例
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void TM_Destroy(IntPtr handle);
/// 从内存数据学习模板
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int TM_LearnPattern(IntPtr handle,
IntPtr templateData, int width, int height, int step);
/// 从文件学习模板
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int TM_LearnPatternFromFile(IntPtr handle,
[MarshalAs(UnmanagedType.LPStr)] string filePath);
/// 执行模板匹配
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int TM_Match(IntPtr handle,
IntPtr srcData, int srcWidth, int srcHeight, int srcStep,
ref TM_Params param,
[Out] TM_Result[] results, int maxResults);
/// 获取上次匹配耗时(毫秒)
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern double TM_GetLastMatchTime(IntPtr handle);
/// 获取模板信息
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int TM_GetTemplateInfo(IntPtr handle,
out int width, out int height, out int pyramidLayers);
/// 保存训练好的模型到文件
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int TM_SaveModel(IntPtr handle,
[MarshalAs(UnmanagedType.LPStr)] string filePath);
/// 从文件加载已训练的模型
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int TM_LoadModel(IntPtr handle,
[MarshalAs(UnmanagedType.LPStr)] string filePath);
}
///
/// 模板匹配器托管封装(自动管理非托管资源)
///
public sealed class TemplateMatcherHandle : IDisposable
{
private IntPtr _handle;
private bool _disposed;
public TemplateMatcherHandle()
{
_handle = TemplateMatchNative.TM_Create();
if (_handle == IntPtr.Zero)
throw new InvalidOperationException("Failed to create TemplateMatcher instance");
}
///
/// 从文件学习模板
///
public bool LearnPatternFromFile(string filePath)
{
ThrowIfDisposed();
return TemplateMatchNative.TM_LearnPatternFromFile(_handle, filePath) == 0;
}
///
/// 从EmguCV Image学习模板
///
public bool LearnPattern(IntPtr data, int width, int height, int step)
{
ThrowIfDisposed();
return TemplateMatchNative.TM_LearnPattern(_handle, data, width, height, step) == 0;
}
///
/// 执行匹配
///
public TM_Result[] Match(IntPtr srcData, int srcWidth, int srcHeight, int srcStep, TM_Params param)
{
ThrowIfDisposed();
// 与库默认一致并对齐已知崩溃组合:Debug 下亚像素 + 大角度容差易触发 vector 越界断言;
// 金字塔最小面积过小也可能与内部层级假设不一致。
int tw = 0, th = 0, _pyramidLayers = 0;
_ = GetTemplateInfo(out tw, out th, out _pyramidLayers);
int templatePixels = Math.Max(0, tw) * Math.Max(0, th);
int maxCount = Math.Clamp(param.MaxCount, 1, 100);
int minReduce = (int)Math.Clamp(param.MinReduceArea, 64, 4096);
if (templatePixels >= 512)
minReduce = Math.Max(256, minReduce);
if (templatePixels > 0)
minReduce = Math.Min(minReduce, templatePixels);
minReduce = Math.Max(64, minReduce);
int useSubPixel = param.UseSubPixel;
if (useSubPixel != 0 && Math.Abs(param.ToleranceAngle) > TM_Params.SubPixelAngleSafetyLimitDegrees)
useSubPixel = 0;
var p = param;
p.MaxCount = maxCount;
p.MinReduceArea = minReduce;
p.UseSubPixel = useSubPixel;
var results = new TM_Result[p.MaxCount];
int count = TemplateMatchNative.TM_Match(_handle, srcData, srcWidth, srcHeight, srcStep,
ref p, results, p.MaxCount);
if (count <= 0)
return Array.Empty();
if (count < results.Length)
Array.Resize(ref results, count);
return results;
}
///
/// 获取上次匹配耗时(毫秒)
///
public double LastMatchTime
{
get
{
ThrowIfDisposed();
return TemplateMatchNative.TM_GetLastMatchTime(_handle);
}
}
///
/// 获取模板信息
///
public bool GetTemplateInfo(out int width, out int height, out int pyramidLayers)
{
ThrowIfDisposed();
return TemplateMatchNative.TM_GetTemplateInfo(_handle, out width, out height, out pyramidLayers) == 0;
}
///
/// 保存训练好的模型到文件
///
/// 模型文件路径(建议扩展名 .tmmodel)
/// 是否成功
public bool SaveModel(string filePath)
{
ThrowIfDisposed();
return TemplateMatchNative.TM_SaveModel(_handle, filePath) == 0;
}
///
/// 从文件加载已训练的模型(跳过LearnPattern)
///
/// 模型文件路径
/// 是否成功
public bool LoadModel(string filePath)
{
ThrowIfDisposed();
return TemplateMatchNative.TM_LoadModel(_handle, filePath) == 0;
}
private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(nameof(TemplateMatcherHandle));
}
public void Dispose()
{
if (!_disposed)
{
if (_handle != IntPtr.Zero)
{
TemplateMatchNative.TM_Destroy(_handle);
_handle = IntPtr.Zero;
}
_disposed = true;
}
}
~TemplateMatcherHandle()
{
Dispose();
}
}