411 lines
11 KiB
C#
411 lines
11 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using System.Windows.Shapes;
|
|
using System.Windows.Media.Animation;
|
|
|
|
namespace OrpaonVision.SiteApp.Controls;
|
|
|
|
/// <summary>
|
|
/// 实时图像显示控件。
|
|
/// </summary>
|
|
public partial class RealTimeImageDisplay : UserControl
|
|
{
|
|
/// <summary>
|
|
/// 图像源依赖属性。
|
|
/// </summary>
|
|
public static readonly DependencyProperty ImageSourceProperty =
|
|
DependencyProperty.Register(nameof(ImageSource), typeof(BitmapSource), typeof(RealTimeImageDisplay),
|
|
new PropertyMetadata(null, OnImageSourceChanged));
|
|
|
|
/// <summary>
|
|
/// 显示模式依赖属性。
|
|
/// </summary>
|
|
public static readonly DependencyProperty DisplayModeProperty =
|
|
DependencyProperty.Register(nameof(DisplayMode), typeof(ImageDisplayMode), typeof(RealTimeImageDisplay),
|
|
new PropertyMetadata(ImageDisplayMode.Fit, OnDisplayModeChanged));
|
|
|
|
/// <summary>
|
|
/// 显示检测框依赖属性。
|
|
/// </summary>
|
|
public static readonly DependencyProperty ShowDetectionBoxesProperty =
|
|
DependencyProperty.Register(nameof(ShowDetectionBoxes), typeof(bool), typeof(RealTimeImageDisplay),
|
|
new PropertyMetadata(true, OnShowDetectionBoxesChanged));
|
|
|
|
/// <summary>
|
|
/// 检测结果依赖属性。
|
|
/// </summary>
|
|
public static readonly DependencyProperty DetectionResultsProperty =
|
|
DependencyProperty.Register(nameof(DetectionResults), typeof(List<DetectionBox>), typeof(RealTimeImageDisplay),
|
|
new PropertyMetadata(null, OnDetectionResultsChanged));
|
|
|
|
/// <summary>
|
|
/// 构造函数。
|
|
/// </summary>
|
|
public RealTimeImageDisplay()
|
|
{
|
|
InitializeComponent();
|
|
InitializeCanvas();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 图像源。
|
|
/// </summary>
|
|
public BitmapSource? ImageSource
|
|
{
|
|
get => (BitmapSource?)GetValue(ImageSourceProperty);
|
|
set => SetValue(ImageSourceProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 显示模式。
|
|
/// </summary>
|
|
public ImageDisplayMode DisplayMode
|
|
{
|
|
get => (ImageDisplayMode)GetValue(DisplayModeProperty);
|
|
set => SetValue(DisplayModeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 是否显示检测框。
|
|
/// </summary>
|
|
public bool ShowDetectionBoxes
|
|
{
|
|
get => (bool)GetValue(ShowDetectionBoxesProperty);
|
|
set => SetValue(ShowDetectionBoxesProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检测结果。
|
|
/// </summary>
|
|
public List<DetectionBox>? DetectionResults
|
|
{
|
|
get => (List<DetectionBox>?)GetValue(DetectionResultsProperty);
|
|
set => SetValue(DetectionResultsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 图像控件。
|
|
/// </summary>
|
|
private Image _imageControl = null!;
|
|
|
|
/// <summary>
|
|
/// 检测框画布。
|
|
/// </summary>
|
|
private Canvas _detectionCanvas = null!;
|
|
|
|
/// <summary>
|
|
/// 当前图像尺寸。
|
|
/// </summary>
|
|
private Size _currentImageSize = Size.Empty;
|
|
|
|
/// <summary>
|
|
/// 显示区域尺寸。
|
|
/// </summary>
|
|
private Size _displaySize = Size.Empty;
|
|
|
|
/// <summary>
|
|
/// 缩放比例。
|
|
/// </summary>
|
|
private double _scaleX = 1.0;
|
|
private double _scaleY = 1.0;
|
|
|
|
private static void OnImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if (d is RealTimeImageDisplay control)
|
|
{
|
|
control.OnImageSourceChanged((BitmapSource?)e.NewValue);
|
|
}
|
|
}
|
|
|
|
private static void OnDisplayModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if (d is RealTimeImageDisplay control)
|
|
{
|
|
control.OnDisplayModeChanged((ImageDisplayMode)e.NewValue);
|
|
}
|
|
}
|
|
|
|
private static void OnShowDetectionBoxesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if (d is RealTimeImageDisplay control)
|
|
{
|
|
control.OnShowDetectionBoxesChanged((bool)e.NewValue);
|
|
}
|
|
}
|
|
|
|
private static void OnDetectionResultsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if (d is RealTimeImageDisplay control)
|
|
{
|
|
control.OnDetectionResultsChanged((List<DetectionBox>?)e.NewValue);
|
|
}
|
|
}
|
|
|
|
private void OnImageSourceChanged(BitmapSource? newImageSource)
|
|
{
|
|
if (newImageSource != null)
|
|
{
|
|
_currentImageSize = new Size(newImageSource.PixelWidth, newImageSource.PixelHeight);
|
|
_imageControl.Source = newImageSource;
|
|
UpdateImageLayout();
|
|
UpdateDetectionBoxes();
|
|
}
|
|
else
|
|
{
|
|
_imageControl.Source = null;
|
|
_currentImageSize = Size.Empty;
|
|
ClearDetectionBoxes();
|
|
}
|
|
}
|
|
|
|
private void OnDisplayModeChanged(ImageDisplayMode newMode)
|
|
{
|
|
UpdateImageLayout();
|
|
}
|
|
|
|
private void OnShowDetectionBoxesChanged(bool showBoxes)
|
|
{
|
|
_detectionCanvas.Visibility = showBoxes ? Visibility.Visible : Visibility.Collapsed;
|
|
}
|
|
|
|
private void OnDetectionResultsChanged(List<DetectionBox>? newResults)
|
|
{
|
|
UpdateDetectionBoxes();
|
|
}
|
|
|
|
private void InitializeCanvas()
|
|
{
|
|
_imageControl = new Image
|
|
{
|
|
Stretch = Stretch.Uniform,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
VerticalAlignment = VerticalAlignment.Center
|
|
};
|
|
|
|
_detectionCanvas = new Canvas
|
|
{
|
|
IsHitTestVisible = false,
|
|
Background = Brushes.Transparent
|
|
};
|
|
|
|
var grid = new Grid();
|
|
grid.Children.Add(_imageControl);
|
|
grid.Children.Add(_detectionCanvas);
|
|
|
|
Content = grid;
|
|
}
|
|
|
|
private void UpdateImageLayout()
|
|
{
|
|
if (_currentImageSize.IsEmpty)
|
|
return;
|
|
|
|
_displaySize = new Size(ActualWidth, ActualHeight);
|
|
if (_displaySize.Width <= 0 || _displaySize.Height <= 0)
|
|
return;
|
|
|
|
switch (DisplayMode)
|
|
{
|
|
case ImageDisplayMode.Fit:
|
|
UpdateFitLayout();
|
|
break;
|
|
case ImageDisplayMode.Fill:
|
|
UpdateFillLayout();
|
|
break;
|
|
case ImageDisplayMode.Original:
|
|
UpdateOriginalLayout();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void UpdateFitLayout()
|
|
{
|
|
var imageRatio = _currentImageSize.Width / _currentImageSize.Height;
|
|
var displayRatio = _displaySize.Width / _displaySize.Height;
|
|
|
|
if (imageRatio > displayRatio)
|
|
{
|
|
// 以宽度为准
|
|
_scaleX = _displaySize.Width / _currentImageSize.Width;
|
|
_scaleY = _scaleX;
|
|
}
|
|
else
|
|
{
|
|
// 以高度为准
|
|
_scaleY = _displaySize.Height / _currentImageSize.Height;
|
|
_scaleX = _scaleY;
|
|
}
|
|
|
|
_imageControl.Stretch = Stretch.Uniform;
|
|
}
|
|
|
|
private void UpdateFillLayout()
|
|
{
|
|
_scaleX = _displaySize.Width / _currentImageSize.Width;
|
|
_scaleY = _displaySize.Height / _currentImageSize.Height;
|
|
_imageControl.Stretch = Stretch.Fill;
|
|
}
|
|
|
|
private void UpdateOriginalLayout()
|
|
{
|
|
_scaleX = 1.0;
|
|
_scaleY = 1.0;
|
|
_imageControl.Stretch = Stretch.None;
|
|
}
|
|
|
|
private void UpdateDetectionBoxes()
|
|
{
|
|
if (!ShowDetectionBoxes || DetectionResults == null || DetectionResults.Count == 0)
|
|
{
|
|
ClearDetectionBoxes();
|
|
return;
|
|
}
|
|
|
|
_detectionCanvas.Children.Clear();
|
|
|
|
foreach (var detection in DetectionResults)
|
|
{
|
|
var rect = CreateDetectionRectangle(detection);
|
|
_detectionCanvas.Children.Add(rect);
|
|
}
|
|
}
|
|
|
|
private Rectangle CreateDetectionRectangle(DetectionBox detection)
|
|
{
|
|
var color = GetDetectionColor(detection.ClassName);
|
|
var brush = new SolidColorBrush(color);
|
|
|
|
var rect = new Rectangle
|
|
{
|
|
Stroke = brush,
|
|
StrokeThickness = 2,
|
|
Fill = new SolidColorBrush(color)
|
|
{
|
|
Opacity = 0.1
|
|
}
|
|
};
|
|
|
|
// 计算缩放后的位置和尺寸
|
|
var x = detection.X * _scaleX;
|
|
var y = detection.Y * _scaleY;
|
|
var width = detection.Width * _scaleX;
|
|
var height = detection.Height * _scaleY;
|
|
|
|
// 居中显示
|
|
var offsetX = (_displaySize.Width - _currentImageSize.Width * _scaleX) / 2;
|
|
var offsetY = (_displaySize.Height - _currentImageSize.Height * _scaleY) / 2;
|
|
|
|
Canvas.SetLeft(rect, x + offsetX);
|
|
Canvas.SetTop(rect, y + offsetY);
|
|
Canvas.SetZIndex(rect, 1);
|
|
|
|
// 添加标签
|
|
var labelColor = new SolidColorBrush(color);
|
|
var label = new TextBlock
|
|
{
|
|
Text = $"{detection.ClassName} ({detection.Confidence:P0})",
|
|
Foreground = labelColor,
|
|
FontSize = 12,
|
|
FontWeight = FontWeights.Bold,
|
|
Background = Brushes.White,
|
|
Padding = new Thickness(4, 2, 4, 2)
|
|
};
|
|
|
|
Canvas.SetLeft(label, x + offsetX);
|
|
Canvas.SetTop(label, y + offsetY - 20);
|
|
Canvas.SetZIndex(label, 2);
|
|
|
|
_detectionCanvas.Children.Add(label);
|
|
|
|
return rect;
|
|
}
|
|
|
|
private void ClearDetectionBoxes()
|
|
{
|
|
_detectionCanvas.Children.Clear();
|
|
}
|
|
|
|
private static Color GetDetectionColor(string className)
|
|
{
|
|
return className.ToLowerInvariant() switch
|
|
{
|
|
"ng" or "defect" => Color.FromRgb(255, 0, 0), // 红色
|
|
"ok" or "good" => Color.FromRgb(0, 255, 0), // 绿色
|
|
"warning" => Color.FromRgb(255, 165, 0), // 橙色
|
|
_ => Color.FromRgb(0, 0, 255) // 蓝色
|
|
};
|
|
}
|
|
|
|
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
|
{
|
|
base.OnRenderSizeChanged(sizeInfo);
|
|
_displaySize = new Size(sizeInfo.NewSize.Width, sizeInfo.NewSize.Height);
|
|
UpdateImageLayout();
|
|
UpdateDetectionBoxes();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 图像显示模式。
|
|
/// </summary>
|
|
public enum ImageDisplayMode
|
|
{
|
|
/// <summary>
|
|
/// 适应显示(保持比例)。
|
|
/// </summary>
|
|
Fit,
|
|
|
|
/// <summary>
|
|
/// 填充显示(可能变形)。
|
|
/// </summary>
|
|
Fill,
|
|
|
|
/// <summary>
|
|
/// 原始尺寸显示。
|
|
/// </summary>
|
|
Original
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检测框信息。
|
|
/// </summary>
|
|
public sealed class DetectionBox
|
|
{
|
|
/// <summary>
|
|
/// 类别名称。
|
|
/// </summary>
|
|
public string ClassName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// 置信度。
|
|
/// </summary>
|
|
public double Confidence { get; set; }
|
|
|
|
/// <summary>
|
|
/// X坐标。
|
|
/// </summary>
|
|
public double X { get; set; }
|
|
|
|
/// <summary>
|
|
/// Y坐标。
|
|
/// </summary>
|
|
public double Y { get; set; }
|
|
|
|
/// <summary>
|
|
/// 宽度。
|
|
/// </summary>
|
|
public double Width { get; set; }
|
|
|
|
/// <summary>
|
|
/// 高度。
|
|
/// </summary>
|
|
public double Height { get; set; }
|
|
|
|
/// <summary>
|
|
/// 标签信息。
|
|
/// </summary>
|
|
public string? Label { get; set; }
|
|
}
|