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
@@ -166,6 +166,24 @@ namespace XplorePlane.Views
{
RenderBackgroundDefectResult(payload.RoiRect, payload.Detections, isBlackBackground: true);
}, Prism.Events.ThreadOption.UIThread);
ea2?.GetEvent<TemplateMatchEnterRoiModeEvent>().Subscribe(() =>
{
_bgDefectDrawing = false;
_bgDefectRoiMode = BackgroundDefectRoiMode.TemplateAssistant;
RegisterBackgroundDefectRoiMouseHandlers();
SetStatus("模板助手:请在图像上拖拽框选模板区域");
}, Prism.Events.ThreadOption.UIThread);
ea2?.GetEvent<TemplateMatchPreviewResultEvent>().Subscribe(payload =>
{
RenderTemplateMatchPreview(payload);
}, Prism.Events.ThreadOption.UIThread);
ea2?.GetEvent<TemplateMatchClearRoiOverlayEvent>().Subscribe(() =>
{
RemoveTemplateAssistantPersistRoi();
}, Prism.Events.ThreadOption.UIThread);
}
catch { }
}
@@ -365,15 +383,21 @@ namespace XplorePlane.Views
_bgDefectPreview = null;
}
ClearBackgroundDefectOverlays(canvas);
ClearTemplateMatchOverlays(canvas);
RemoveTemplateAssistantPersistRoi();
}
else
{
_bgDefectOverlays.Clear();
_tmMatchOverlays.Clear();
RemoveTemplateAssistantPersistRoi();
}
_bgDefectDrawing = false;
_bgDefectRoiMode = BackgroundDefectRoiMode.None;
try { RoiCanvas.ReleaseMouseCapture(); } catch { /* 未捕获则无影响 */ }
SetStatus("已清除所有测量白底/黑底检测结果");
SetStatus("已清除所有测量白底/黑底检测、模板匹配试跑叠加及模板助手 ROI");
}
private void SaveOriginalImage_Click(object sender, RoutedEventArgs e)
@@ -432,7 +456,8 @@ namespace XplorePlane.Views
{
None,
WhiteBackground,
BlackBackground
BlackBackground,
TemplateAssistant
}
private BackgroundDefectRoiMode _bgDefectRoiMode;
@@ -440,6 +465,8 @@ namespace XplorePlane.Views
private System.Windows.Point _bgDefectStart;
private System.Windows.Shapes.Rectangle _bgDefectPreview;
private readonly System.Collections.Generic.List<System.Windows.UIElement> _bgDefectOverlays = new();
private readonly System.Collections.Generic.List<System.Windows.UIElement> _tmMatchOverlays = new();
private System.Windows.Shapes.Rectangle _templateAssistantRoiPersist;
private bool _bgDefectMouseHandlersRegistered;
private void RegisterBackgroundDefectRoiMouseHandlers()
@@ -525,6 +552,25 @@ namespace XplorePlane.Views
if (w < 10 || h < 10) return; // 太小忽略
// 模板助手:在画布上保留 ROI 矩形(与试跑匹配叠加分开管理)
if (completedMode == BackgroundDefectRoiMode.TemplateAssistant)
{
RemoveTemplateAssistantPersistRoi();
_templateAssistantRoiPersist = new System.Windows.Shapes.Rectangle
{
Stroke = System.Windows.Media.Brushes.DeepSkyBlue,
StrokeThickness = 1.5,
StrokeDashArray = new System.Windows.Media.DoubleCollection { 4, 2 },
Fill = System.Windows.Media.Brushes.Transparent,
Width = Math.Max(1, w),
Height = Math.Max(1, h),
IsHitTestVisible = false
};
System.Windows.Controls.Canvas.SetLeft(_templateAssistantRoiPersist, x);
System.Windows.Controls.Canvas.SetTop(_templateAssistantRoiPersist, y);
canvas.Children.Add(_templateAssistantRoiPersist);
}
// 发布ROI绘制完成事件
try
{
@@ -534,12 +580,106 @@ namespace XplorePlane.Views
ea?.GetEvent<WhiteBackgroundRoiDrawnEvent>().Publish(rect);
else if (completedMode == BackgroundDefectRoiMode.BlackBackground)
ea?.GetEvent<BlackBackgroundRoiDrawnEvent>().Publish(rect);
else if (completedMode == BackgroundDefectRoiMode.TemplateAssistant)
ea?.GetEvent<TemplateMatchRoiDrawnEvent>().Publish(rect);
}
catch { }
e.Handled = true;
}
private void ClearTemplateMatchOverlays(System.Windows.Controls.Canvas canvas)
{
if (canvas != null)
{
foreach (var el in _tmMatchOverlays)
canvas.Children.Remove(el);
}
_tmMatchOverlays.Clear();
}
private void RemoveTemplateAssistantPersistRoi()
{
if (_templateAssistantRoiPersist == null) return;
var rect = _templateAssistantRoiPersist;
_templateAssistantRoiPersist = null;
if (VisualTreeHelper.GetParent(rect) is Panel p)
p.Children.Remove(rect);
}
private void RenderTemplateMatchPreview(TemplateMatchPreviewPayload payload)
{
var canvas = FindChildByName<System.Windows.Controls.Canvas>(RoiCanvas, "mainCanvas");
if (canvas == null) return;
ClearTemplateMatchOverlays(canvas);
if (payload?.Hits == null || payload.Hits.Count == 0)
return;
var stroke = new SolidColorBrush(Color.FromRgb(255, 140, 0));
stroke.Freeze();
const int crossHalf = 8;
foreach (var h in payload.Hits)
{
var poly = new System.Windows.Shapes.Polygon
{
Stroke = stroke,
StrokeThickness = 2,
Fill = Brushes.Transparent,
IsHitTestVisible = false,
Points = new PointCollection
{
new System.Windows.Point(h.LtX, h.LtY),
new System.Windows.Point(h.RtX, h.RtY),
new System.Windows.Point(h.RbX, h.RbY),
new System.Windows.Point(h.LbX, h.LbY)
}
};
canvas.Children.Add(poly);
_tmMatchOverlays.Add(poly);
var cx = h.CenterX;
var cy = h.CenterY;
var hLine = new System.Windows.Shapes.Line
{
X1 = cx - crossHalf,
Y1 = cy,
X2 = cx + crossHalf,
Y2 = cy,
Stroke = stroke,
StrokeThickness = 1.5,
IsHitTestVisible = false
};
var vLine = new System.Windows.Shapes.Line
{
X1 = cx,
Y1 = cy - crossHalf,
X2 = cx,
Y2 = cy + crossHalf,
Stroke = stroke,
StrokeThickness = 1.5,
IsHitTestVisible = false
};
canvas.Children.Add(hLine);
canvas.Children.Add(vLine);
_tmMatchOverlays.Add(hLine);
_tmMatchOverlays.Add(vLine);
var tb = new System.Windows.Controls.TextBlock
{
Text = $"{h.Score:F2}",
Foreground = stroke,
FontSize = 10,
IsHitTestVisible = false
};
System.Windows.Controls.Canvas.SetLeft(tb, cx + crossHalf + 2);
System.Windows.Controls.Canvas.SetTop(tb, cy - 8);
canvas.Children.Add(tb);
_tmMatchOverlays.Add(tb);
}
}
private void RenderBackgroundDefectResult(
System.Drawing.Rectangle roiRect,
System.Collections.Generic.IReadOnlyList<BackgroundDefectDetectionItem> detections,