using OpenCvSharp; using OpenCvSharp.WpfExtensions; using OxyPlot; using OxyPlot.Axes; using OxyPlot.Series; using Prism.Commands; using Prism.Mvvm; using SqlSugar; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Threading; using TeamAAS_VP.Core; using TeamAAS_VP.Enums; using TeamAAS_VP.Models; using TeamAAS_VP.Services; namespace TeamAAS_VP.Controls { /// /// FocusAnalyzer用户控件ViewModel /// public class FocusAnalyzerViewModel : BindableBase { private readonly FileDialogService _fileDialogService; private readonly ImageAnalysisService _imageAnalysisService; private readonly CheckerboardDetectionService _checkerboardService; private readonly DispatcherTimer _analysisTimer; private string _title = "棋盘格清晰度分析工具"; private BitmapSource _currentImage; private Mat _currentMat; private AnalysisParameters _parameters; private AnalysisResult _currentResult; private RoiModel _currentRoi; private ChessboardInfo _chessboardInfo; private RoiOperationMode _roiMode; private string _statusText; private bool _isAnalyzing; private double _bestSharpness; private int _frameCount; private PlotModel _plotModel; private List _resultHistory; // function to convert ROI from canvas coords to image pixel Rect (set by view) public Func RoiToImageRectConverter { get; set; } public string Title { get => _title; set => SetProperty(ref _title, value); } public BitmapSource CurrentImage { get => _currentImage; set => SetProperty(ref _currentImage, value); } public AnalysisParameters Parameters { get => _parameters; set => SetProperty(ref _parameters, value); } public AnalysisResult CurrentResult { get => _currentResult; set => SetProperty(ref _currentResult, value); } public RoiModel CurrentRoi { get => _currentRoi; set => SetProperty(ref _currentRoi, value); } public ChessboardInfo ChessboardInfo { get => _chessboardInfo; set => SetProperty(ref _chessboardInfo, value); } public RoiOperationMode RoiMode { get => _roiMode; set => SetProperty(ref _roiMode, value); } public string StatusText { get => _statusText; set => SetProperty(ref _statusText, value); } public bool IsAnalyzing { get => _isAnalyzing; set => SetProperty(ref _isAnalyzing, value); } public PlotModel PlotModel { get => _plotModel; set => SetProperty(ref _plotModel, value); } public string BestSharpnessText => _bestSharpness > 0 ? $"{_bestSharpness:F2}" : "N/A"; public string FrameCountText => $"帧数: {_frameCount}"; public ObservableCollection FocusMethods { get; } public ObservableCollection AnalysisModes { get; } public DelegateCommand LoadImageCommand { get; } public DelegateCommand StartAnalysisCommand { get; } public DelegateCommand StopAnalysisCommand { get; } public DelegateCommand ResetCommand { get; } public DelegateCommand ExportCommand { get; } public DelegateCommand ClearRoiCommand { get; } public DelegateCommand DetectChessboardCommand { get; } public FocusAnalyzerViewModel() { _fileDialogService = new FileDialogService(); _imageAnalysisService = new ImageAnalysisService(); _checkerboardService = new CheckerboardDetectionService(); _parameters = new AnalysisParameters(); _currentResult = new AnalysisResult(); _currentRoi = new RoiModel(); _resultHistory = new List(); _roiMode = RoiOperationMode.Draw; // 默认为绘制模式 FocusMethods = new ObservableCollection(Enum.GetValues(typeof(FocusMethod)).Cast()); AnalysisModes = new ObservableCollection(Enum.GetValues(typeof(AnalysisMode)).Cast()); LoadImageCommand = new DelegateCommand(OnLoadImage); StartAnalysisCommand = new DelegateCommand(OnStartAnalysis, CanStartAnalysis).ObservesProperty(() => CurrentImage); StopAnalysisCommand = new DelegateCommand(OnStopAnalysis, () => IsAnalyzing).ObservesProperty(() => IsAnalyzing); ResetCommand = new DelegateCommand(OnReset); ExportCommand = new DelegateCommand(OnExport, () => _resultHistory.Count > 0); ClearRoiCommand = new DelegateCommand(OnClearRoi); DetectChessboardCommand = new DelegateCommand(OnDetectChessboard, () => _currentMat != null); InitializePlot(); _analysisTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; _analysisTimer.Tick += OnAnalysisTick; StatusText = "就绪"; } private void OnLoadImage() { try { string filePath = _fileDialogService.OpenImageFile(); if (string.IsNullOrEmpty(filePath)) return; // 释放之前的图像 _currentMat?.Dispose(); // 加载图像 _currentMat = Cv2.ImRead(filePath, ImreadModes.Color); if (_currentMat == null || _currentMat.Empty()) { StatusText = "无法加载图像"; return; } // 转换为WPF图像 CurrentImage = _currentMat.ToWriteableBitmap(); // 自动检测棋盘格 OnDetectChessboard(); // 如果启用了实时分析,立即分析 if (Parameters.EnableRealtimeAnalysis) { AnalyzeCurrentImage(); } StatusText = $"图像已加载: {_currentMat.Width}x{_currentMat.Height}"; } catch (Exception ex) { StatusText = $"加载图像失败: {ex.Message}"; } } private void OnDetectChessboard() { if (_currentMat == null || _currentMat.Empty()) return; try { StatusText = "正在检测棋盘格..."; Task.Run(() => { ChessboardInfo info = null; if (RoiToImageRectConverter != null && CurrentRoi?.IsValid == true) { var imgRoi = RoiToImageRectConverter(CurrentRoi); if (imgRoi.HasValue) { using (var sub = new Mat(_currentMat, imgRoi.Value)) { info = _checkerboardService.DetectChessboard(sub); } if (info != null && info.IsDetected) { for (int i = 0; i < info.Corners.Length; i++) { info.Corners[i].X += imgRoi.Value.X; info.Corners[i].Y += imgRoi.Value.Y; } var br = info.BoundingRect; info.BoundingRect = new OpenCvSharp.Rect(br.X + imgRoi.Value.X, br.Y + imgRoi.Value.Y, br.Width, br.Height); } } } if (info == null || !info.IsDetected) { info = _checkerboardService.DetectChessboard(_currentMat); } System.Windows.Application.Current.Dispatcher.Invoke(() => { ChessboardInfo = info; if (info.IsDetected) { StatusText = $"检测成功: {info.CornersWidth}x{info.CornersHeight} 角点"; // 绘制棋盘格 var displayMat = _currentMat.Clone(); _checkerboardService.DrawChessboard(displayMat, info); CurrentImage = displayMat.ToWriteableBitmap(); displayMat.Dispose(); } else { StatusText = "未检测到棋盘格"; } }); }); } catch (Exception ex) { StatusText = $"检测失败: {ex.Message}"; } } private bool CanStartAnalysis() { return CurrentImage != null && !IsAnalyzing; } private void OnStartAnalysis() { IsAnalyzing = true; _frameCount = 0; _bestSharpness = 0; _resultHistory.Clear(); StatusText = "分析中..."; if (Parameters.EnableRealtimeAnalysis) { _analysisTimer.Start(); } else { AnalyzeCurrentImage(); IsAnalyzing = false; StatusText = "分析完成"; } } private void OnStopAnalysis() { _analysisTimer.Stop(); IsAnalyzing = false; StatusText = "已停止"; } private void OnAnalysisTick(object sender, EventArgs e) { AnalyzeCurrentImage(); } private void AnalyzeCurrentImage() { if (_currentMat == null || _currentMat.Empty()) return; try { Rect? roi = null; // 根据分析模式确定分析区域 switch (Parameters.AnalysisMode) { case AnalysisMode.RoiOnly: if (CurrentRoi.IsValid) { if (RoiToImageRectConverter != null) { roi = RoiToImageRectConverter(CurrentRoi); } else { roi = new Rect( (int)CurrentRoi.X, (int)CurrentRoi.Y, (int)CurrentRoi.Width, (int)CurrentRoi.Height); } } break; case AnalysisMode.ChessboardOnly: if (ChessboardInfo?.IsDetected == true) { roi = ChessboardInfo.BoundingRect; } break; } // 执行分析 var result = _imageAnalysisService.AnalyzeImage(_currentMat, Parameters.FocusMethod, roi); CurrentResult = result; _resultHistory.Add(result); _frameCount++; // 更新最佳清晰度 if (result.Sharpness > _bestSharpness) { _bestSharpness = result.Sharpness; RaisePropertyChanged(nameof(BestSharpnessText)); } RaisePropertyChanged(nameof(FrameCountText)); // 更新图表 UpdatePlot(); StatusText = $"清晰度: {result.Sharpness:F2}, 对比度: {result.Contrast:F2}"; } catch (Exception ex) { StatusText = $"分析错误: {ex.Message}"; } } private void OnReset() { OnStopAnalysis(); _frameCount = 0; _bestSharpness = 0; _resultHistory.Clear(); CurrentResult = new AnalysisResult(); InitializePlot(); StatusText = "已重置"; RaisePropertyChanged(nameof(BestSharpnessText)); RaisePropertyChanged(nameof(FrameCountText)); } private void OnExport() { try { string filePath = _fileDialogService.SaveFile( $"分析报告_{DateTime.Now:yyyyMMdd_HHmmss}.csv", "CSV文件|*.csv|所有文件|*.*"); if (string.IsNullOrEmpty(filePath)) return; using (var writer = new System.IO.StreamWriter(filePath)) { writer.WriteLine("时间,清晰度,对比度,质量评分"); foreach (var result in _resultHistory) { writer.WriteLine($"{result.Timestamp:yyyy-MM-dd HH:mm:ss.fff},{result.Sharpness:F2},{result.Contrast:F2},{result.QualityScore:F2}"); } } StatusText = $"报告已导出: {filePath}"; } catch (Exception ex) { StatusText = $"导出失败: {ex.Message}"; } } private void OnClearRoi() { CurrentRoi = new RoiModel(); StatusText = "ROI已清除"; } private void InitializePlot() { PlotModel = new PlotModel { Title = "清晰度趋势", Background = OxyColors.White }; PlotModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "帧数", MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot, MajorGridlineColor = OxyColor.FromRgb(230, 230, 230) }); PlotModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "清晰度", MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot, MajorGridlineColor = OxyColor.FromRgb(230, 230, 230) }); var series = new LineSeries { Title = "清晰度", Color = OxyColor.FromRgb(0, 122, 204), StrokeThickness = 2, MarkerType = MarkerType.Circle, MarkerSize = 3, MarkerFill = OxyColor.FromRgb(0, 122, 204) }; PlotModel.Series.Add(series); } private void UpdatePlot() { if (PlotModel.Series.Count == 0) return; var series = PlotModel.Series[0] as LineSeries; if (series == null) return; series.Points.Clear(); for (int i = 0; i < _resultHistory.Count; i++) { series.Points.Add(new OxyPlot.DataPoint(i + 1, _resultHistory[i].Sharpness)); } PlotModel.InvalidatePlot(true); } } }