版本260406

This commit is contained in:
2026-04-06 22:04:05 +08:00
parent 7dc5e73af7
commit 0b150470be
216 changed files with 98993 additions and 33 deletions

View 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>

View 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; }
}