Files
XplorePlane/XplorePlane/Views/Main/ViewportPanelView.xaml.cs
T
李伟 e0eec42a2f feat: 模板匹配助手窗口与主视口 ROI 清除逻辑
- 新增模板助手/批量测试窗口、事件与 ViewModel,主窗口入口与 App 注册

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

- TemplateMatchNative 等相关调整

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-15 10:42:20 +08:00

799 lines
33 KiB
C#

using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
using Prism.Ioc;
using XP.ImageProcessing.RoiControl.Controls;
using XplorePlane.Events;
using XplorePlane.ViewModels;
namespace XplorePlane.Views
{
public partial class ViewportPanelView : UserControl
{
private MainViewModel _mainVm;
private MainViewModel GetMainVm()
{
if (_mainVm == null)
{
try { _mainVm = ContainerLocator.Current?.Resolve<MainViewModel>(); } catch { }
}
return _mainVm;
}
private void SetStatus(string msg)
{
var vm = GetMainVm();
if (vm != null) vm.StatusMessage = msg;
}
public ViewportPanelView()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
// 动态创建右键菜单,支持条件性阻止弹出
var menu = new System.Windows.Controls.ContextMenu();
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "放大" });
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "缩小" });
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "适应窗口" });
menu.Items.Add(new System.Windows.Controls.Separator());
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "保存原始图像" });
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "保存结果图像" });
menu.Items.Add(new System.Windows.Controls.Separator());
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "清除所有测量" });
((System.Windows.Controls.MenuItem)menu.Items[0]).Click += ZoomIn_Click;
((System.Windows.Controls.MenuItem)menu.Items[1]).Click += ZoomOut_Click;
((System.Windows.Controls.MenuItem)menu.Items[2]).Click += ResetView_Click;
((System.Windows.Controls.MenuItem)menu.Items[4]).Click += SaveOriginalImage_Click;
((System.Windows.Controls.MenuItem)menu.Items[5]).Click += SaveResultImage_Click;
((System.Windows.Controls.MenuItem)menu.Items[7]).Click += ClearAllMeasurements_Click;
RoiCanvas.ContextMenu = menu;
RoiCanvas.ContextMenuOpening += (s, e) =>
{
if (RoiCanvas.SuppressContextMenu)
{
RoiCanvas.SuppressContextMenu = false;
e.Handled = true;
}
};
// 测量事件 → 更新主界面状态栏
RoiCanvas.MeasureCompleted += (s, e) =>
{
if (e is MeasureCompletedEventArgs args)
{
string typeLabel = args.MeasureType switch
{
"PointToLine" => "点线距",
"Angle" => "角度",
"FillRate" => "填锡率",
"BgaVoid" => "BGA空隙",
"BubbleVoid" => "气泡空隙",
_ => "点点距"
};
string valueText = args.MeasureType switch
{
"Angle" => $"{args.Distance:F2}°",
"FillRate" => $"{args.Distance:F1}%",
"BgaVoid" => $"{args.Distance:F1}%",
"BubbleVoid" => $"{args.Distance:F1}%",
_ => $"{args.Distance:F2} px"
};
SetStatus($"{typeLabel}: {valueText} | 共 {args.TotalCount} 条测量");
}
};
RoiCanvas.MeasureStatusChanged += (s, e) =>
{
if (e is MeasureStatusEventArgs args)
SetStatus(args.Message);
};
// 十字辅助线:直接订阅 Prism 事件
try
{
var ea = ContainerLocator.Current?.Resolve<Prism.Events.IEventAggregator>();
ea?.GetEvent<ToggleCrosshairEvent>().Subscribe(() =>
{
RoiCanvas.ShowCrosshair = !RoiCanvas.ShowCrosshair;
}, Prism.Events.ThreadOption.UIThread);
// 测量模式:直接订阅 Prism 事件
ea?.GetEvent<MeasurementToolEvent>().Subscribe(mode =>
{
RoiCanvas.CurrentMeasureMode = mode switch
{
MeasurementToolMode.PointDistance => XP.ImageProcessing.RoiControl.Models.MeasureMode.PointDistance,
MeasurementToolMode.PointLineDistance => XP.ImageProcessing.RoiControl.Models.MeasureMode.PointToLine,
MeasurementToolMode.Angle => XP.ImageProcessing.RoiControl.Models.MeasureMode.Angle,
MeasurementToolMode.ThroughHoleFillRate => XP.ImageProcessing.RoiControl.Models.MeasureMode.FillRate,
MeasurementToolMode.BgaVoid => XP.ImageProcessing.RoiControl.Models.MeasureMode.BgaVoid,
MeasurementToolMode.BubbleMeasure => XP.ImageProcessing.RoiControl.Models.MeasureMode.BubbleMeasure,
_ => XP.ImageProcessing.RoiControl.Models.MeasureMode.None
};
}, Prism.Events.ThreadOption.UIThread);
}
catch { }
// 光标信息:从 RoiCanvas.CursorInfo 同步到 MainViewModel
var cursorInfoDesc = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(
PolygonRoiCanvas.CursorInfoProperty, typeof(PolygonRoiCanvas));
cursorInfoDesc?.AddValueChanged(RoiCanvas, (s, e) =>
{
var vm = GetMainVm();
if (vm != null) vm.CursorInfoText = RoiCanvas.CursorInfo;
});
// 行灰度分布
try
{
var ea2 = ContainerLocator.Current?.Resolve<Prism.Events.IEventAggregator>();
ea2?.GetEvent<ToggleLineProfileEvent>().Subscribe(() =>
{
ToggleLineProfile();
}, Prism.Events.ThreadOption.UIThread);
// 白底检测:进入ROI绘制模式
ea2?.GetEvent<WhiteBackgroundDetectionEvent>().Subscribe(() =>
{
_bgDefectDrawing = false;
_bgDefectRoiMode = BackgroundDefectRoiMode.WhiteBackground;
RegisterBackgroundDefectRoiMouseHandlers();
}, Prism.Events.ThreadOption.UIThread);
// 黑底检测:进入ROI绘制模式
ea2?.GetEvent<BlackBackgroundDetectionEvent>().Subscribe(() =>
{
_bgDefectDrawing = false;
_bgDefectRoiMode = BackgroundDefectRoiMode.BlackBackground;
RegisterBackgroundDefectRoiMouseHandlers();
}, Prism.Events.ThreadOption.UIThread);
// 白底检测:渲染结果(红色标识)
ea2?.GetEvent<WhiteBackgroundResultEvent>().Subscribe(payload =>
{
RenderBackgroundDefectResult(payload.RoiRect, payload.Detections, isBlackBackground: false);
}, Prism.Events.ThreadOption.UIThread);
// 黑底检测:渲染结果(绿色标识)
ea2?.GetEvent<BlackBackgroundResultEvent>().Subscribe(payload =>
{
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 { }
}
#region
private bool _lineProfileEnabled;
private System.Windows.Shapes.Line _profileRefLine; // 透明命中区域
private System.Windows.Shapes.Line _profileRefLineVisible; // 1px红线显示
private System.Windows.Shapes.Polyline _profileCurve;
private double _profileLineY;
private bool _profileDragging;
private void ToggleLineProfile()
{
_lineProfileEnabled = !_lineProfileEnabled;
var canvas = FindChildByName<System.Windows.Controls.Canvas>(RoiCanvas, "mainCanvas");
if (canvas == null) return;
if (_lineProfileEnabled)
{
// 参考线默认在图像中间
_profileLineY = RoiCanvas.CanvasHeight / 2;
// 创建参考线(红色水平线,可拖动)
// 用透明粗线作为命中区域,叠加1px红线显示
_profileRefLine = new System.Windows.Shapes.Line
{
X1 = 0,
Y1 = _profileLineY,
X2 = RoiCanvas.CanvasWidth,
Y2 = _profileLineY,
Stroke = System.Windows.Media.Brushes.Transparent,
StrokeThickness = 7, // 上下3px命中区域
IsHitTestVisible = true,
Cursor = System.Windows.Input.Cursors.SizeNS
};
_profileRefLineVisible = new System.Windows.Shapes.Line
{
X1 = 0,
Y1 = _profileLineY,
X2 = RoiCanvas.CanvasWidth,
Y2 = _profileLineY,
Stroke = System.Windows.Media.Brushes.Red,
StrokeThickness = 1,
IsHitTestVisible = false
};
_profileRefLine.MouseLeftButtonDown += ProfileLine_MouseDown;
_profileRefLine.MouseMove += ProfileLine_MouseMove;
_profileRefLine.MouseLeftButtonUp += ProfileLine_MouseUp;
canvas.Children.Add(_profileRefLineVisible);
canvas.Children.Add(_profileRefLine);
// 创建灰度折线(固定显示在图像中间位置)
_profileCurve = new System.Windows.Shapes.Polyline
{
Stroke = System.Windows.Media.Brushes.Red,
StrokeThickness = 1,
IsHitTestVisible = false
};
canvas.Children.Add(_profileCurve);
UpdateLineProfile();
SetStatus("行灰度分布:拖动红线改变采样行,再次点击按钮关闭");
}
else
{
if (_profileRefLine != null)
{
canvas.Children.Remove(_profileRefLine);
_profileRefLine = null;
}
if (_profileRefLineVisible != null)
{
canvas.Children.Remove(_profileRefLineVisible);
_profileRefLineVisible = null;
}
if (_profileCurve != null)
{
canvas.Children.Remove(_profileCurve);
_profileCurve = null;
}
SetStatus("行灰度分布已关闭");
}
}
private void ProfileLine_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_profileDragging = true;
_profileRefLine?.CaptureMouse();
e.Handled = true;
}
private void ProfileLine_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (!_profileDragging || _profileRefLine == null) return;
var canvas = FindChildByName<System.Windows.Controls.Canvas>(RoiCanvas, "mainCanvas");
if (canvas == null) return;
var pos = e.GetPosition(canvas);
_profileLineY = Math.Clamp(pos.Y, 0, RoiCanvas.CanvasHeight - 1);
_profileRefLine.Y1 = _profileLineY;
_profileRefLine.Y2 = _profileLineY;
_profileRefLineVisible.Y1 = _profileLineY;
_profileRefLineVisible.Y2 = _profileLineY;
UpdateLineProfile();
}
private void ProfileLine_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_profileDragging = false;
_profileRefLine?.ReleaseMouseCapture();
e.Handled = true;
}
private void UpdateLineProfile()
{
if (_profileCurve == null) return;
// 从当前显示图像获取像素数据
var viewportVm = DataContext as ViewportPanelViewModel;
var imageSource = viewportVm?.ImageSource as System.Windows.Media.Imaging.BitmapSource;
if (imageSource == null) return;
int imgWidth = imageSource.PixelWidth;
int imgHeight = imageSource.PixelHeight;
int row = (int)Math.Clamp(_profileLineY, 0, imgHeight - 1);
// 转为 Gray8 获取行像素
System.Windows.Media.Imaging.BitmapSource gray8;
if (imageSource.Format != System.Windows.Media.PixelFormats.Gray8)
gray8 = new System.Windows.Media.Imaging.FormatConvertedBitmap(
imageSource, System.Windows.Media.PixelFormats.Gray8, null, 0);
else
gray8 = imageSource;
byte[] rowPixels = new byte[imgWidth];
int stride = imgWidth;
gray8.CopyPixels(new System.Windows.Int32Rect(0, row, imgWidth, 1), rowPixels, stride, 0);
// 构建折线点集:折线固定显示在图像垂直中间位置
// 参考线位置决定采样哪一行,折线位置固定在画布中间
double canvasH = RoiCanvas.CanvasHeight;
double curveCenter = canvasH / 2.0; // 折线基线固定在图像中间
double displayHeight = canvasH * 0.25; // 折线振幅为画布高度的25%
var points = new System.Windows.Media.PointCollection(imgWidth);
for (int x = 0; x < imgWidth; x++)
{
double normalizedGray = rowPixels[x] / 255.0;
double y = curveCenter - normalizedGray * displayHeight;
points.Add(new System.Windows.Point(x, y));
}
_profileCurve.Points = points;
SetStatus($"行灰度分布 | Y={row} | 均值={rowPixels.Select(b => (double)b).Average():F1} | 最大={rowPixels.Max()} | 最小={rowPixels.Min()}");
}
#endregion
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is INotifyPropertyChanged oldVm)
oldVm.PropertyChanged -= OnVmPropertyChanged;
if (e.NewValue is INotifyPropertyChanged newVm)
newVm.PropertyChanged += OnVmPropertyChanged;
}
private void OnVmPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// 测量模式和十字线通过 Prism 事件直接驱动,不再依赖 PropertyChanged
}
#region
private void ZoomIn_Click(object sender, RoutedEventArgs e) => RoiCanvas.ZoomScale = Math.Min(10.0, RoiCanvas.ZoomScale * 1.2);
private void ZoomOut_Click(object sender, RoutedEventArgs e) => RoiCanvas.ZoomScale = Math.Max(0.1, RoiCanvas.ZoomScale / 1.2);
private void ResetView_Click(object sender, RoutedEventArgs e) => RoiCanvas.ResetView();
private void ClearAllMeasurements_Click(object sender, RoutedEventArgs e)
{
RoiCanvas.ClearMeasurements();
RoiCanvas.ROIItems?.Clear();
RoiCanvas.SelectedROI = null;
if (DataContext is ViewportPanelViewModel vm)
vm.ResetMeasurementState();
var canvas = FindChildByName<System.Windows.Controls.Canvas>(RoiCanvas, "mainCanvas");
if (canvas != null)
{
if (_bgDefectPreview != null)
{
canvas.Children.Remove(_bgDefectPreview);
_bgDefectPreview = null;
}
ClearBackgroundDefectOverlays(canvas);
ClearTemplateMatchOverlays(canvas);
RemoveTemplateAssistantPersistRoi();
}
else
{
_bgDefectOverlays.Clear();
_tmMatchOverlays.Clear();
RemoveTemplateAssistantPersistRoi();
}
_bgDefectDrawing = false;
_bgDefectRoiMode = BackgroundDefectRoiMode.None;
try { RoiCanvas.ReleaseMouseCapture(); } catch { /* 未捕获则无影响 */ }
SetStatus("已清除所有测量、白底/黑底检测、模板匹配试跑叠加及模板助手 ROI");
}
private void SaveOriginalImage_Click(object sender, RoutedEventArgs e)
{
if (DataContext is not ViewportPanelViewModel vm || vm.ImageSource is not BitmapSource bitmap)
{
MessageBox.Show("No image available to save", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
SaveBitmapToFile(bitmap, "保存原始图像");
}
private void SaveResultImage_Click(object sender, RoutedEventArgs e)
{
var target = FindChildByName<Canvas>(RoiCanvas, "mainCanvas");
if (target == null)
{
MessageBox.Show("No image available to save", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var width = (int)target.ActualWidth;
var height = (int)target.ActualHeight;
if (width == 0 || height == 0) return;
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(target);
SaveBitmapToFile(rtb, "保存结果图像");
}
private static void SaveBitmapToFile(BitmapSource bitmap, string title)
{
var dialog = new SaveFileDialog
{
Title = title,
Filter = "PNG 图像|*.png|BMP 图像|*.bmp|JPEG 图像|*.jpg",
DefaultExt = ".png"
};
if (dialog.ShowDialog() != true) return;
BitmapEncoder encoder = Path.GetExtension(dialog.FileName).ToLower() switch
{
".bmp" => new BmpBitmapEncoder(),
".jpg" or ".jpeg" => new JpegBitmapEncoder(),
_ => new PngBitmapEncoder()
};
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using var fs = new FileStream(dialog.FileName, FileMode.Create);
encoder.Save(fs);
}
#endregion
#region /
private enum BackgroundDefectRoiMode
{
None,
WhiteBackground,
BlackBackground,
TemplateAssistant
}
private BackgroundDefectRoiMode _bgDefectRoiMode;
private bool _bgDefectDrawing;
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()
{
if (_bgDefectMouseHandlersRegistered) return;
RoiCanvas.PreviewMouseLeftButtonDown += OnMainCanvasPreviewMouseDown;
RoiCanvas.PreviewMouseMove += OnMainCanvasPreviewMouseMove;
RoiCanvas.PreviewMouseLeftButtonUp += OnMainCanvasPreviewMouseUp;
_bgDefectMouseHandlersRegistered = true;
}
// 需要在 mainCanvas 的 MouseDown/Move/Up 中处理
// 由于 PolygonRoiCanvas 内部已经处理了鼠标事件,我们通过 PreviewMouse 事件来拦截
private void OnMainCanvasPreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (_bgDefectRoiMode == BackgroundDefectRoiMode.None || e.LeftButton != System.Windows.Input.MouseButtonState.Pressed) return;
var canvas = FindChildByName<System.Windows.Controls.Canvas>(RoiCanvas, "mainCanvas");
if (canvas == null) return;
_bgDefectStart = e.GetPosition(canvas);
_bgDefectDrawing = true;
// 创建预览矩形(不清除之前的检测结果)
_bgDefectPreview = new System.Windows.Shapes.Rectangle
{
Stroke = System.Windows.Media.Brushes.Blue,
StrokeThickness = 1,
StrokeDashArray = new System.Windows.Media.DoubleCollection { 4, 2 }
};
System.Windows.Controls.Canvas.SetLeft(_bgDefectPreview, _bgDefectStart.X);
System.Windows.Controls.Canvas.SetTop(_bgDefectPreview, _bgDefectStart.Y);
canvas.Children.Add(_bgDefectPreview);
RoiCanvas.CaptureMouse();
e.Handled = true;
}
private void OnMainCanvasPreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (!_bgDefectDrawing || _bgDefectPreview == null) return;
var canvas = FindChildByName<System.Windows.Controls.Canvas>(RoiCanvas, "mainCanvas");
if (canvas == null) return;
var current = e.GetPosition(canvas);
double x = Math.Min(_bgDefectStart.X, current.X);
double y = Math.Min(_bgDefectStart.Y, current.Y);
double w = Math.Abs(current.X - _bgDefectStart.X);
double h = Math.Abs(current.Y - _bgDefectStart.Y);
System.Windows.Controls.Canvas.SetLeft(_bgDefectPreview, x);
System.Windows.Controls.Canvas.SetTop(_bgDefectPreview, y);
_bgDefectPreview.Width = Math.Max(1, w);
_bgDefectPreview.Height = Math.Max(1, h);
}
private void OnMainCanvasPreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (!_bgDefectDrawing) return;
_bgDefectDrawing = false;
var completedMode = _bgDefectRoiMode;
_bgDefectRoiMode = BackgroundDefectRoiMode.None;
RoiCanvas.ReleaseMouseCapture();
var canvas = FindChildByName<System.Windows.Controls.Canvas>(RoiCanvas, "mainCanvas");
if (canvas == null) return;
var end = e.GetPosition(canvas);
int x = (int)Math.Min(_bgDefectStart.X, end.X);
int y = (int)Math.Min(_bgDefectStart.Y, end.Y);
int w = (int)Math.Abs(end.X - _bgDefectStart.X);
int h = (int)Math.Abs(end.Y - _bgDefectStart.Y);
// 移除预览矩形
if (_bgDefectPreview != null)
{
canvas.Children.Remove(_bgDefectPreview);
_bgDefectPreview = null;
}
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
{
var ea = ContainerLocator.Current?.Resolve<Prism.Events.IEventAggregator>();
var rect = new System.Windows.Int32Rect(x, y, w, h);
if (completedMode == BackgroundDefectRoiMode.WhiteBackground)
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,
bool isBlackBackground)
{
var canvas = FindChildByName<System.Windows.Controls.Canvas>(RoiCanvas, "mainCanvas");
if (canvas == null || detections == null) return;
// 绘制ROI矩形(蓝色实线,两种模式一致)
var roiShape = new System.Windows.Shapes.Rectangle
{
Stroke = System.Windows.Media.Brushes.Blue,
StrokeThickness = 1,
Width = roiRect.Width,
Height = roiRect.Height,
IsHitTestVisible = false
};
System.Windows.Controls.Canvas.SetLeft(roiShape, roiRect.X);
System.Windows.Controls.Canvas.SetTop(roiShape, roiRect.Y);
canvas.Children.Add(roiShape);
_bgDefectOverlays.Add(roiShape);
var defectBrush = isBlackBackground
? System.Windows.Media.Brushes.LimeGreen
: System.Windows.Media.Brushes.Red;
const int labelPadRightOfRoi = 4;
const double labelLineHeight = 15;
int validCount = detections.Count(d => d.Contour != null && d.Contour.Count >= 2);
double roiMidY = roiRect.Y + roiRect.Height * 0.5;
double labelLeft = roiRect.X + roiRect.Width + labelPadRightOfRoi;
double labelStartY = roiMidY - validCount * labelLineHeight * 0.5;
int labelRow = 0;
foreach (var d in detections)
{
if (d.Contour == null || d.Contour.Count < 2) continue;
var fig = new PathFigure
{
StartPoint = new System.Windows.Point(d.Contour[0].X, d.Contour[0].Y),
IsClosed = true
};
if (d.Contour.Count > 1)
{
fig.Segments.Add(new PolyLineSegment(
d.Contour.Skip(1).Select(p => new System.Windows.Point(p.X, p.Y)), true));
}
var geom = new PathGeometry();
geom.Figures.Add(fig);
var contourPath = new System.Windows.Shapes.Path
{
Data = geom,
Stroke = defectBrush,
StrokeThickness = 1,
Fill = System.Windows.Media.Brushes.Transparent,
IsHitTestVisible = false
};
canvas.Children.Add(contourPath);
_bgDefectOverlays.Add(contourPath);
var chordLine = new System.Windows.Shapes.Line
{
X1 = d.ChordP1.X,
Y1 = d.ChordP1.Y,
X2 = d.ChordP2.X,
Y2 = d.ChordP2.Y,
Stroke = defectBrush,
StrokeThickness = 1.5,
IsHitTestVisible = false
};
canvas.Children.Add(chordLine);
_bgDefectOverlays.Add(chordLine);
double um = d.SizeMicrometers;
string label = um >= 1000 ? $"{um / 1000:F2} mm" : $"{um:F0} μm";
var text = new System.Windows.Controls.TextBlock
{
Text = label,
Foreground = defectBrush,
FontSize = 11,
IsHitTestVisible = false
};
System.Windows.Controls.Canvas.SetLeft(text, labelLeft);
System.Windows.Controls.Canvas.SetTop(text, labelStartY + labelRow * labelLineHeight);
canvas.Children.Add(text);
_bgDefectOverlays.Add(text);
labelRow++;
}
}
private void ClearBackgroundDefectOverlays(System.Windows.Controls.Canvas canvas)
{
foreach (var el in _bgDefectOverlays)
canvas.Children.Remove(el);
_bgDefectOverlays.Clear();
}
#endregion
private static T FindChildByName<T>(DependencyObject parent, string name) where T : FrameworkElement
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T t && t.Name == name) return t;
var result = FindChildByName<T>(child, name);
if (result != null) return result;
}
return null;
}
}
}