feat: 模板匹配助手窗口与主视口 ROI 清除逻辑

- 新增模板助手/批量测试窗口、事件与 ViewModel,主窗口入口与 App 注册

- 运行匹配或批量测试前发布清除事件;视口通过 VisualTreeHelper 从父 Panel 移除持久虚线 ROI,避免 FindChild 失败时框残留

- TemplateMatchNative 等相关调整

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
李伟
2026-05-15 10:42:20 +08:00
parent 82465e6510
commit e0eec42a2f
10 changed files with 1424 additions and 4 deletions
@@ -30,6 +30,12 @@ public struct TM_Params
/// <summary>是否亚像素估计 (1=是, 0=否)</summary>
public int UseSubPixel;
/// <summary>
/// 开启亚像素且角度容差绝对值超过该值时,托管封装会在调用原生库前关闭亚像素,
/// 以避免部分版本 TemplateMatchLib 在 Debug 下出现 vector 越界断言。
/// </summary>
public const double SubPixelAngleSafetyLimitDegrees = 90.0;
/// <summary>
/// 创建默认参数
/// </summary>
@@ -168,9 +174,33 @@ public sealed class TemplateMatcherHandle : IDisposable
public TM_Result[] Match(IntPtr srcData, int srcWidth, int srcHeight, int srcStep, TM_Params param)
{
ThrowIfDisposed();
var results = new TM_Result[param.MaxCount];
// 与库默认一致并对齐已知崩溃组合: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 param, results, param.MaxCount);
ref p, results, p.MaxCount);
if (count <= 0)
return Array.Empty<TM_Result>();