using OpenCvSharp; using OpenCvSharp.WpfExtensions; using OxyPlot; using OxyPlot.Axes; using OxyPlot.Series; using Prism.Commands; using Prism.Mvvm; 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 TeamAAS_VP.Core; using TeamAAS_VP.Enums; using TeamAAS_VP.Models; namespace TeamAAS_VP.Controls { /// /// FocusAnalyzer用户控件ViewModel /// public class FocusAnalyzerViewModel : BindableBase { #region 可绑定属性 // 图像源 private ImageSource _currentImage; public ImageSource CurrentImage { get => _currentImage; set => SetProperty(ref _currentImage, value); } // 分析参数 private AnalysisParameters _parameters = new AnalysisParameters(); public AnalysisParameters Parameters { get => _parameters; set => SetProperty(ref _parameters, value); } // 当前结果 private AnalysisResult _currentResult = new AnalysisResult(); public AnalysisResult CurrentResult { get => _currentResult; set => SetProperty(ref _currentResult, value); } // 最终报告 private AnalysisReport _finalReport = new AnalysisReport(); public AnalysisReport FinalReport { get => _finalReport; set => SetProperty(ref _finalReport, value); } // 分析状态 private bool _isAnalyzing; public bool IsAnalyzing { get => _isAnalyzing; set => SetProperty(ref _isAnalyzing, value); } // ROI区域 private RotatedRect _currentRoi; public RotatedRect CurrentRoi { get => _currentRoi; set => SetProperty(ref _currentRoi, value); } // 是否显示ROI private bool _showRoi = true; public bool ShowRoi { get => _showRoi; set => SetProperty(ref _showRoi, value); } // ROI操作模式 private RoiOperationMode _roiMode = RoiOperationMode.Draw; public RoiOperationMode RoiMode { get => _roiMode; set => SetProperty(ref _roiMode, value); } // 图表模型 private PlotModel _plotModel; public PlotModel PlotModel { get => _plotModel; set => SetProperty(ref _plotModel, value); } // 状态文本 private string _statusText = "就绪"; public string StatusText { get => _statusText; set => SetProperty(ref _statusText, value); } // 帧数文本 private string _frameCountText = "帧数: 0"; public string FrameCountText { get => _frameCountText; set => SetProperty(ref _frameCountText, value); } // 最佳清晰度文本 private string _bestSharpnessText = "最佳: 0.00"; public string BestSharpnessText { get => _bestSharpnessText; set => SetProperty(ref _bestSharpnessText, value); } // 枚举列表 public Array FocusMethods => Enum.GetValues(typeof(FocusMethod)); public Array AnalysisModes => Enum.GetValues(typeof(AnalysisMode)); #endregion #region 命令 // 控制命令(绑定到外部) public ICommand StartAnalysisCommand { get; set; } public ICommand StopAnalysisCommand { get; set; } public ICommand ResetCommand { get; set; } public ICommand ExportCommand { get; set; } // 内部命令 public DelegateCommand SetRoiModeCommand { get; private set; } public DelegateCommand ClearRoiCommand { get; private set; } #endregion #region 事件 // 控件事件(供外部订阅) public event EventHandler AnalysisStarted; public event EventHandler AnalysisStopped; public event EventHandler RoiChanged; public event EventHandler ErrorOccurred; #endregion #region 私有字段 private int _frameCounter = 0; private double _bestSharpness = 0; private DateTime _startTime; private ObservableCollection _historyData = new ObservableCollection(); // ROI绘制状态 private System.Windows.Point _roiStartPoint; private System.Windows.Point _roiDragStartPoint; private RotatedRect _roiBeforeDrag; private DragHandleType _currentDragHandle = DragHandleType.None; #endregion #region 构造函数 public FocusAnalyzerViewModel() { InitializeCommands(); InitializeProperties(); InitializePlot(); } #endregion #region 初始化 private void InitializeCommands() { // ROI模式切换命令 SetRoiModeCommand = new DelegateCommand(mode => { RoiMode = (RoiOperationMode)mode; }); // 清除ROI命令 ClearRoiCommand = new DelegateCommand(() => { CurrentRoi = null; OnRoiChanged(); }); } private void InitializeProperties() { Parameters = new AnalysisParameters(); CurrentResult = new AnalysisResult(); FinalReport = new AnalysisReport(); // 初始化默认ROI CurrentRoi = new RotatedRect(100, 100, 200, 200, 0); } private void InitializePlot() { PlotModel = new PlotModel { Title = "清晰度变化曲线", TitleFontSize = 12, PlotMargins = new OxyThickness(40, 10, 10, 40) }; PlotModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "帧序列", MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot }); PlotModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "清晰度", MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot }); PlotModel.Series.Add(new LineSeries { Title = "清晰度", Color = OxyColors.DodgerBlue, StrokeThickness = 2 }); } #endregion #region 公共方法 /// /// 处理图像帧 /// public async Task ProcessImageFrameAsync(Mat frame) { if (!IsAnalyzing || frame == null || frame.Empty()) return; try { // 更新图像 await App.Current.Dispatcher.InvokeAsync(() => { CurrentImage = BitmapSourceConverter.ToBitmapSource(frame); }); // 执行分析 await Task.Run(() => PerformAnalysis(frame)); _frameCounter++; // 更新UI UpdateStatusText(); } catch (Exception ex) { OnErrorOccurred(new ErrorEventArgs($"处理图像帧失败: {ex.Message}", ex)); } } /// /// 执行单次分析 /// public void PerformAnalysis(Mat frame) { if (frame == null || frame.Empty()) return; try { // 将RotatedRect转换为Rect(简化处理) Rect? roiRect = null; if (CurrentRoi != null) { roiRect = new Rect((int)CurrentRoi.X, (int)CurrentRoi.Y, (int)CurrentRoi.Width, (int)CurrentRoi.Height); } // 计算清晰度 double sharpness = FocusAnalysisEngine.CalculateImageSharpness( frame, Parameters.FocusMethod, roiRect); // 分析棋盘格 CheckerboardResult checkerboard; if (Parameters.AnalysisMode == AnalysisMode.AutoCheckerboard) { checkerboard = FocusAnalysisEngine.AnalyzeCheckerboardContrast(frame, roiRect); } else { checkerboard = new CheckerboardResult(); } // 更新结果 App.Current.Dispatcher.Invoke(() => { UpdateAnalysisResult(sharpness, checkerboard); UpdateHistoryData(sharpness, checkerboard); UpdatePlot(); }); } catch (Exception ex) { OnErrorOccurred(new ErrorEventArgs($"图像分析失败: {ex.Message}", ex)); } } #endregion #region ROI绘制方法 /// /// 开始ROI绘制 /// public void StartRoiDrawing(System.Windows.Point startPoint) { if (RoiMode != RoiOperationMode.Draw) return; _roiStartPoint = startPoint; CurrentRoi = new RotatedRect(startPoint.X, startPoint.Y, 0, 0, 0); } /// /// 更新ROI绘制 /// public void UpdateRoiDrawing(System.Windows.Point currentPoint) { if (RoiMode != RoiOperationMode.Draw || CurrentRoi == null) return; double x = Math.Min(_roiStartPoint.X, currentPoint.X); double y = Math.Min(_roiStartPoint.Y, currentPoint.Y); double width = Math.Abs(currentPoint.X - _roiStartPoint.X); double height = Math.Abs(currentPoint.Y - _roiStartPoint.Y); CurrentRoi.X = x; CurrentRoi.Y = y; CurrentRoi.Width = width; CurrentRoi.Height = height; } /// /// 完成ROI绘制 /// public void FinishRoiDrawing() { OnRoiChanged(); } /// /// 开始ROI拖动 /// public bool StartRoiDrag(System.Windows.Point point) { if (CurrentRoi == null || !CurrentRoi.ContainsPoint(point)) return false; // 检查是否点击了控制点 _currentDragHandle = GetDragHandleAtPoint(point); if (_currentDragHandle != DragHandleType.None) { _roiDragStartPoint = point; _roiBeforeDrag = new RotatedRect( CurrentRoi.X, CurrentRoi.Y, CurrentRoi.Width, CurrentRoi.Height, CurrentRoi.Angle); return true; } return false; } /// /// 更新ROI拖动 /// public void UpdateRoiDrag(System.Windows.Point currentPoint) { if (CurrentRoi == null || _roiBeforeDrag == null) return; double deltaX = currentPoint.X - _roiDragStartPoint.X; double deltaY = currentPoint.Y - _roiDragStartPoint.Y; switch (_currentDragHandle) { case DragHandleType.TopLeft: CurrentRoi.X = _roiBeforeDrag.X + deltaX; CurrentRoi.Y = _roiBeforeDrag.Y + deltaY; CurrentRoi.Width = _roiBeforeDrag.Width - deltaX; CurrentRoi.Height = _roiBeforeDrag.Height - deltaY; break; case DragHandleType.TopRight: CurrentRoi.Y = _roiBeforeDrag.Y + deltaY; CurrentRoi.Width = _roiBeforeDrag.Width + deltaX; CurrentRoi.Height = _roiBeforeDrag.Height - deltaY; break; case DragHandleType.BottomLeft: CurrentRoi.X = _roiBeforeDrag.X + deltaX; CurrentRoi.Width = _roiBeforeDrag.Width - deltaX; CurrentRoi.Height = _roiBeforeDrag.Height + deltaY; break; case DragHandleType.BottomRight: CurrentRoi.Width = _roiBeforeDrag.Width + deltaX; CurrentRoi.Height = _roiBeforeDrag.Height + deltaY; break; case DragHandleType.Rotate: // 计算旋转角度 var center = new System.Windows.Point( _roiBeforeDrag.CenterX, _roiBeforeDrag.CenterY); double startAngle = Math.Atan2( _roiDragStartPoint.Y - center.Y, _roiDragStartPoint.X - center.X); double currentAngle = Math.Atan2( currentPoint.Y - center.Y, currentPoint.X - center.X); double angleDelta = (currentAngle - startAngle) * 180 / Math.PI; CurrentRoi.Angle = (_roiBeforeDrag.Angle + angleDelta) % 360; break; case DragHandleType.None: // 移动整个ROI CurrentRoi.X = _roiBeforeDrag.X + deltaX; CurrentRoi.Y = _roiBeforeDrag.Y + deltaY; break; } } /// /// 结束ROI拖动 /// public void FinishRoiDrag() { _currentDragHandle = DragHandleType.None; _roiBeforeDrag = null; OnRoiChanged(); } /// /// 获取指定点的控制点类型 /// private DragHandleType GetDragHandleAtPoint(System.Windows.Point point) { if (CurrentRoi == null) return DragHandleType.None; const double handleSize = 8; var center = new System.Windows.Point(CurrentRoi.CenterX, CurrentRoi.CenterY); // 检查旋转控制点(在矩形顶部中心上方) var rotateHandle = new System.Windows.Point(center.X, CurrentRoi.Y - 20); if (Distance(point, rotateHandle) <= handleSize) return DragHandleType.Rotate; // 检查角点 var corners = new[] { new System.Windows.Point(CurrentRoi.X, CurrentRoi.Y), // 左上 new System.Windows.Point(CurrentRoi.X + CurrentRoi.Width, CurrentRoi.Y), // 右上 new System.Windows.Point(CurrentRoi.X, CurrentRoi.Y + CurrentRoi.Height), // 左下 new System.Windows.Point(CurrentRoi.X + CurrentRoi.Width, CurrentRoi.Y + CurrentRoi.Height) // 右下 }; // 替换 switch 表达式为 switch 语句以兼容 C# 7.3 for (int i = 0; i < corners.Length; i++) { if (Distance(point, corners[i]) <= handleSize) { switch (i) { case 0: return DragHandleType.TopLeft; case 1: return DragHandleType.TopRight; case 2: return DragHandleType.BottomLeft; case 3: return DragHandleType.BottomRight; default: return DragHandleType.None; } } } return DragHandleType.None; } private double Distance(System.Windows.Point p1, System.Windows.Point p2) { return Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2)); } #endregion #region 私有方法 private void UpdateAnalysisResult(double sharpness, CheckerboardResult checkerboard) { double qualityScore = FocusAnalysisEngine.CalculateQualityScore(sharpness, checkerboard); var suggestions = FocusAnalysisEngine.GenerateSuggestions( sharpness, checkerboard, Parameters.SharpnessThreshold, Parameters.ContrastThreshold); if (sharpness > _bestSharpness) _bestSharpness = sharpness; CurrentResult.Sharpness = sharpness; CurrentResult.Checkerboard = checkerboard; CurrentResult.QualityScore = qualityScore; CurrentResult.FrameCount = _frameCounter + 1; CurrentResult.BestSharpness = _bestSharpness; CurrentResult.AnalysisTime = DateTime.Now; CurrentResult.Suggestions = new ObservableCollection(suggestions); } private void UpdateHistoryData(double sharpness, CheckerboardResult checkerboard) { var dataPoint = new Models.DataPoint(_frameCounter, sharpness, checkerboard.Detected ? checkerboard.Contrast : 0); _historyData.Add(dataPoint); // 添加到最终报告 FinalReport.HistoryData.Add(dataPoint); } private void UpdatePlot() { if (PlotModel.Series[0] is LineSeries series) { series.Points.Clear(); for (int i = 0; i < _historyData.Count; i++) { series.Points.Add(new OxyPlot.DataPoint(i, _historyData[i].Sharpness)); } // 限制显示点数 if (series.Points.Count > 200) { series.Points.RemoveRange(0, series.Points.Count - 200); } PlotModel.InvalidatePlot(true); } } private void UpdateStatusText() { StatusText = $"分析中... 第 {_frameCounter + 1} 帧"; FrameCountText = $"帧数: {_frameCounter + 1}"; BestSharpnessText = $"最佳: {_bestSharpness:F2}"; } #endregion #region 事件触发方法 protected virtual void OnAnalysisStarted() { AnalysisStarted?.Invoke(this, EventArgs.Empty); } protected virtual void OnAnalysisStopped() { AnalysisStopped?.Invoke(this, EventArgs.Empty); } protected virtual void OnRoiChanged() { RoiChanged?.Invoke(this, EventArgs.Empty); } protected virtual void OnErrorOccurred(ErrorEventArgs e) { ErrorOccurred?.Invoke(this, e); } #endregion } public class ErrorEventArgs : EventArgs { public string Message { get; } public Exception Exception { get; } public ErrorEventArgs(string message, Exception exception = null) { Message = message; Exception = exception; } } }