版本260406
This commit is contained in:
15
OrpaonVision.SiteApp/Controls/RealTimeImageDisplay.xaml
Normal file
15
OrpaonVision.SiteApp/Controls/RealTimeImageDisplay.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="OrpaonVision.SiteApp.Controls.RealTimeImageDisplay"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="400">
|
||||
<UserControl.Resources>
|
||||
<Style TargetType="UserControl">
|
||||
<Setter Property="Background" Value="#F5F5F5"/>
|
||||
<Setter Property="BorderBrush" Value="#CCCCCC"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
</UserControl>
|
||||
410
OrpaonVision.SiteApp/Controls/RealTimeImageDisplay.xaml.cs
Normal file
410
OrpaonVision.SiteApp/Controls/RealTimeImageDisplay.xaml.cs
Normal file
@@ -0,0 +1,410 @@
|
||||
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; }
|
||||
}
|
||||
Reference in New Issue
Block a user