485 lines
14 KiB
C#
485 lines
14 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using OrpaonVision.SiteApp.Controls;
|
||
using OrpaonVision.SiteApp.Runtime.Contracts;
|
||
using OrpaonVision.SiteApp.Runtime.Services;
|
||
using System.ComponentModel;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Imaging;
|
||
|
||
namespace OrpaonVision.SiteApp.ViewModels;
|
||
|
||
/// <summary>
|
||
/// 优化的运行端主窗口 ViewModel。
|
||
/// </summary>
|
||
public sealed class OptimizedMainWindowViewModel : INotifyPropertyChanged
|
||
{
|
||
private readonly ICameraService _cameraService;
|
||
private readonly IInferenceService _inferenceService;
|
||
private readonly IRuleEngineService _ruleEngineService;
|
||
private readonly IRuntimeStateMachineService _runtimeStateMachineService;
|
||
private readonly IImageProcessingService _imageProcessingService;
|
||
private readonly ILogger<OptimizedMainWindowViewModel> _logger;
|
||
|
||
private string _statusText = "准备就绪。";
|
||
private Brush _statusBrush = Brushes.DarkGreen;
|
||
private string _layerText = "1/1";
|
||
private string _inferenceText = "-";
|
||
private string _decisionText = "-";
|
||
private BitmapSource? _currentImage;
|
||
private List<DetectionBox> _detectionBoxes = new();
|
||
private ImageDisplayMode _displayMode = ImageDisplayMode.Fit;
|
||
private bool _showDetectionBoxes = true;
|
||
private bool _isAutoRunning = false;
|
||
private int _frameRate = 1;
|
||
private int _totalFrames = 0;
|
||
private int _successFrames = 0;
|
||
private int _failedFrames = 0;
|
||
private DateTime _lastFrameTime = DateTime.MinValue;
|
||
private double _actualFrameRate = 0;
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public OptimizedMainWindowViewModel(
|
||
ICameraService cameraService,
|
||
IInferenceService inferenceService,
|
||
IRuleEngineService ruleEngineService,
|
||
IRuntimeStateMachineService runtimeStateMachineService,
|
||
IImageProcessingService imageProcessingService,
|
||
ILogger<OptimizedMainWindowViewModel> logger)
|
||
{
|
||
_cameraService = cameraService;
|
||
_inferenceService = inferenceService;
|
||
_ruleEngineService = ruleEngineService;
|
||
_runtimeStateMachineService = runtimeStateMachineService;
|
||
_imageProcessingService = imageProcessingService;
|
||
_logger = logger;
|
||
|
||
// 初始化占位图像
|
||
_currentImage = _imageProcessingService.CreatePlaceholderImage(640, 480, "等待图像...");
|
||
|
||
RefreshSnapshot();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 状态文本。
|
||
/// </summary>
|
||
public string StatusText
|
||
{
|
||
get => _statusText;
|
||
private set => SetProperty(ref _statusText, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 状态颜色。
|
||
/// </summary>
|
||
public Brush StatusBrush
|
||
{
|
||
get => _statusBrush;
|
||
private set => SetProperty(ref _statusBrush, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前层显示文本。
|
||
/// </summary>
|
||
public string LayerText
|
||
{
|
||
get => _layerText;
|
||
private set => SetProperty(ref _layerText, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 最近一次推理结果。
|
||
/// </summary>
|
||
public string InferenceText
|
||
{
|
||
get => _inferenceText;
|
||
private set => SetProperty(ref _inferenceText, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 最近一次规则判定结果。
|
||
/// </summary>
|
||
public string DecisionText
|
||
{
|
||
get => _decisionText;
|
||
private set => SetProperty(ref _decisionText, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前图像。
|
||
/// </summary>
|
||
public BitmapSource? CurrentImage
|
||
{
|
||
get => _currentImage;
|
||
private set => SetProperty(ref _currentImage, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测框列表。
|
||
/// </summary>
|
||
public List<DetectionBox> DetectionBoxes
|
||
{
|
||
get => _detectionBoxes;
|
||
private set => SetProperty(ref _detectionBoxes, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 图像显示模式。
|
||
/// </summary>
|
||
public ImageDisplayMode DisplayMode
|
||
{
|
||
get => _displayMode;
|
||
set => SetProperty(ref _displayMode, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否显示检测框。
|
||
/// </summary>
|
||
public bool ShowDetectionBoxes
|
||
{
|
||
get => _showDetectionBoxes;
|
||
set => SetProperty(ref _showDetectionBoxes, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否自动运行。
|
||
/// </summary>
|
||
public bool IsAutoRunning
|
||
{
|
||
get => _isAutoRunning;
|
||
private set => SetProperty(ref _isAutoRunning, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 帧率设置。
|
||
/// </summary>
|
||
public int FrameRate
|
||
{
|
||
get => _frameRate;
|
||
set => SetProperty(ref _frameRate, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 总帧数。
|
||
/// </summary>
|
||
public int TotalFrames
|
||
{
|
||
get => _totalFrames;
|
||
private set => SetProperty(ref _totalFrames, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 成功帧数。
|
||
/// </summary>
|
||
public int SuccessFrames
|
||
{
|
||
get => _successFrames;
|
||
private set => SetProperty(ref _successFrames, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 失败帧数。
|
||
/// </summary>
|
||
public int FailedFrames
|
||
{
|
||
get => _failedFrames;
|
||
private set => SetProperty(ref _failedFrames, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 实际帧率。
|
||
/// </summary>
|
||
public double ActualFrameRate
|
||
{
|
||
get => _actualFrameRate;
|
||
private set => SetProperty(ref _actualFrameRate, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 成功率。
|
||
/// </summary>
|
||
public double SuccessRate
|
||
{
|
||
get => TotalFrames > 0 ? (double)SuccessFrames / TotalFrames * 100 : 0;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public event PropertyChangedEventHandler? PropertyChanged;
|
||
|
||
/// <summary>
|
||
/// 执行一轮采集-推理-规则判定。
|
||
/// </summary>
|
||
public void RunOneCycle()
|
||
{
|
||
try
|
||
{
|
||
var snapshot = _runtimeStateMachineService.GetSnapshot();
|
||
|
||
// 采集图像
|
||
var captureResult = _cameraService.CaptureFrame();
|
||
if (!captureResult.Succeeded || captureResult.Data is null)
|
||
{
|
||
SetError("相机采图失败。", captureResult.Message);
|
||
_failedFrames++;
|
||
UpdateStatistics();
|
||
return;
|
||
}
|
||
|
||
// 转换图像
|
||
var imageResult = _imageProcessingService.ConvertFrameToBitmapSource(captureResult.Data);
|
||
if (!imageResult.Succeeded || imageResult.Data is null)
|
||
{
|
||
SetError("图像转换失败。", imageResult.Message);
|
||
_failedFrames++;
|
||
UpdateStatistics();
|
||
return;
|
||
}
|
||
|
||
CurrentImage = imageResult.Data;
|
||
|
||
// 推理
|
||
var predictResult = _inferenceService.Predict(captureResult.Data);
|
||
if (!predictResult.Succeeded || predictResult.Data is null)
|
||
{
|
||
SetError("模型推理失败。", predictResult.Message);
|
||
_failedFrames++;
|
||
UpdateStatistics();
|
||
return;
|
||
}
|
||
|
||
var inference = predictResult.Data;
|
||
InferenceText = $"{inference.Label} (置信度: {inference.Confidence:P0})";
|
||
|
||
// 转换检测结果
|
||
var detectionResult = _imageProcessingService.ConvertInferenceToDetectionBoxes(inference);
|
||
if (detectionResult.Succeeded)
|
||
{
|
||
DetectionBoxes = detectionResult.Data ?? new List<DetectionBox>();
|
||
}
|
||
|
||
// 规则判定
|
||
var decisionResult = _ruleEngineService.Evaluate(snapshot.CurrentLayer, inference);
|
||
if (!decisionResult.Succeeded || decisionResult.Data is null)
|
||
{
|
||
SetError("规则判定失败。", decisionResult.Message);
|
||
_failedFrames++;
|
||
UpdateStatistics();
|
||
return;
|
||
}
|
||
|
||
var decision = decisionResult.Data;
|
||
DecisionText = $"{decision.Code} - {decision.Message}";
|
||
|
||
StatusBrush = decision.IsPass ? Brushes.DarkGreen : Brushes.OrangeRed;
|
||
StatusText = decision.IsPass ? "当前层判定通过。" : "当前层判定 NG,请人工复核。";
|
||
|
||
_successFrames++;
|
||
UpdateStatistics();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "执行一轮检测失败");
|
||
SetError("执行检测失败。", ex.Message);
|
||
_failedFrames++;
|
||
UpdateStatistics();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 推进到下一层。
|
||
/// </summary>
|
||
public void MoveToNextLayer()
|
||
{
|
||
try
|
||
{
|
||
var moveResult = _runtimeStateMachineService.MoveToNextLayer();
|
||
if (!moveResult.Succeeded)
|
||
{
|
||
SetError("状态机切层失败。", moveResult.Message);
|
||
return;
|
||
}
|
||
|
||
RefreshSnapshot();
|
||
StatusBrush = Brushes.DarkGreen;
|
||
StatusText = moveResult.Message;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "推进到下一层失败");
|
||
SetError("推进下一层失败。", ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置运行状态。
|
||
/// </summary>
|
||
public void ResetRuntime()
|
||
{
|
||
try
|
||
{
|
||
_runtimeStateMachineService.Reset();
|
||
InferenceText = "-";
|
||
DecisionText = "-";
|
||
DetectionBoxes = new List<DetectionBox>();
|
||
CurrentImage = _imageProcessingService.CreatePlaceholderImage(640, 480, "等待图像...");
|
||
RefreshSnapshot();
|
||
|
||
// 重置统计
|
||
_totalFrames = 0;
|
||
_successFrames = 0;
|
||
_failedFrames = 0;
|
||
_actualFrameRate = 0;
|
||
UpdateStatistics();
|
||
|
||
StatusBrush = Brushes.DarkGreen;
|
||
StatusText = "运行状态已重置。";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "重置运行状态失败");
|
||
SetError("重置状态失败。", ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换自动运行。
|
||
/// </summary>
|
||
public void ToggleAutoRun()
|
||
{
|
||
IsAutoRunning = !IsAutoRunning;
|
||
|
||
if (IsAutoRunning)
|
||
{
|
||
StatusBrush = Brushes.Blue;
|
||
StatusText = "自动运行已启动。";
|
||
_logger.LogInformation("自动运行已启动,帧率: {FrameRate}", FrameRate);
|
||
}
|
||
else
|
||
{
|
||
StatusBrush = Brushes.DarkGreen;
|
||
StatusText = "自动运行已停止。";
|
||
_logger.LogInformation("自动运行已停止");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新图像显示模式。
|
||
/// </summary>
|
||
public void UpdateDisplayMode(ImageDisplayMode mode)
|
||
{
|
||
DisplayMode = mode;
|
||
_logger.LogInformation("图像显示模式已更新: {Mode}", mode);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换检测框显示。
|
||
/// </summary>
|
||
public void ToggleDetectionBoxes()
|
||
{
|
||
ShowDetectionBoxes = !ShowDetectionBoxes;
|
||
_logger.LogInformation("检测框显示已切换: {Show}", ShowDetectionBoxes);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用图像滤镜。
|
||
/// </summary>
|
||
public void ApplyImageFilter(ImageFilter filter)
|
||
{
|
||
if (CurrentImage == null)
|
||
{
|
||
StatusText = "没有可应用滤镜的图像。";
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
var filterResult = _imageProcessingService.ApplyFilter(CurrentImage, filter);
|
||
if (filterResult.Succeeded && filterResult.Data != null)
|
||
{
|
||
CurrentImage = filterResult.Data;
|
||
StatusText = $"已应用滤镜: {filter}";
|
||
}
|
||
else
|
||
{
|
||
SetError("应用滤镜失败。", filterResult.Message);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "应用图像滤镜失败");
|
||
SetError("应用滤镜失败。", ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存当前图像。
|
||
/// </summary>
|
||
public void SaveCurrentImage()
|
||
{
|
||
if (CurrentImage == null)
|
||
{
|
||
StatusText = "没有可保存的图像。";
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 这里可以实现图像保存功能
|
||
StatusText = "图像保存功能待实现。";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "保存图像失败");
|
||
SetError("保存图像失败。", ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新统计信息。
|
||
/// </summary>
|
||
private void UpdateStatistics()
|
||
{
|
||
TotalFrames = _totalFrames;
|
||
SuccessFrames = _successFrames;
|
||
FailedFrames = _failedFrames;
|
||
|
||
// 计算实际帧率
|
||
if (_lastFrameTime != DateTime.MinValue)
|
||
{
|
||
var elapsed = DateTime.Now - _lastFrameTime;
|
||
if (elapsed.TotalSeconds > 0)
|
||
{
|
||
_actualFrameRate = 1.0 / elapsed.TotalSeconds;
|
||
}
|
||
}
|
||
_lastFrameTime = DateTime.Now;
|
||
|
||
ActualFrameRate = Math.Round(_actualFrameRate, 2);
|
||
}
|
||
|
||
private void RefreshSnapshot()
|
||
{
|
||
var snapshot = _runtimeStateMachineService.GetSnapshot();
|
||
LayerText = $"{snapshot.CurrentLayer}/{snapshot.TotalLayers}";
|
||
}
|
||
|
||
private void SetError(string title, string message)
|
||
{
|
||
StatusBrush = Brushes.OrangeRed;
|
||
StatusText = $"{title} {message}";
|
||
}
|
||
|
||
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||
{
|
||
if (EqualityComparer<T>.Default.Equals(field, value))
|
||
{
|
||
return;
|
||
}
|
||
|
||
field = value;
|
||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||
}
|
||
}
|