// ============================================================================ // 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(); } }