using OpenCvSharp; using OpenCvSharp.WpfExtensions; using OxyPlot; using OxyPlot.Axes; using OxyPlot.Series; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using TeamAAS_VP.Core; using TeamAAS_VP.Enums; using TeamAAS_VP.Models; using OpenCvSharp.Extensions; using Rect = OpenCvSharp.Rect; using DataPoint = TeamAAS_VP.Models.DataPoint; namespace TeamAAS_VP.Controls { /// /// FocusAnalyzerControl.xaml 的交互逻辑 /// public partial class FocusAnalyzerControl : UserControl, INotifyPropertyChanged { // 命令 public ICommand StartCommand { get; set; } public ICommand StopCommand { get; set; } public ICommand ResetCommand { get; set; } public ICommand ExportCommand { get; set; } // 可绑定属性 private ImageSource _currentImage; private AnalysisParameters _parameters; private AnalysisResult _currentResult; private AnalysisReport _finalReport; private bool _isAnalyzing; private Rect? _currentRoi; private PlotModel _plotModel; // 内部状态 private List _historyData = new List(); private int _frameCounter = 0; private double _bestSharpness = 0; private DateTime _startTime; // 枚举值列表(用于ComboBox绑定) public Array FocusMethods => Enum.GetValues(typeof(FocusMethod)); public Array AnalysisModes => Enum.GetValues(typeof(AnalysisMode)); public ImageSource CurrentImage { get => _currentImage; set => SetField(ref _currentImage, value); } public AnalysisParameters Parameters { get => _parameters; set => SetField(ref _parameters, value); } public AnalysisResult CurrentResult { get => _currentResult; set => SetField(ref _currentResult, value); } public AnalysisReport FinalReport { get => _finalReport; set => SetField(ref _finalReport, value); } public bool IsAnalyzing { get => _isAnalyzing; set => SetField(ref _isAnalyzing, value); } public Rect? CurrentRoi { get => _currentRoi; set => SetField(ref _currentRoi, value); } public PlotModel PlotModel { get => _plotModel; set => SetField(ref _plotModel, value); } public FocusAnalyzerControl() { InitializeComponent(); InitializeCommands(); InitializeProperties(); InitializePlot(); DataContext = this; } private void InitializeCommands() { StartCommand = new RelayCommand( execute: StartAnalysis, canExecute: () => !IsAnalyzing); StopCommand = new RelayCommand( execute: StopAnalysis, canExecute: () => IsAnalyzing); ResetCommand = new RelayCommand( execute: ResetAnalysis); ExportCommand = new RelayCommand( execute: ExportReport); } private void InitializeProperties() { Parameters = new AnalysisParameters(); CurrentResult = new AnalysisResult(); FinalReport = new AnalysisReport(); // 设置默认ROI CurrentRoi = null; } private void InitializePlot() { PlotModel = new PlotModel { Title = "清晰度变化曲线", TitleFontSize = 12, PlotMargins = new OxyThickness(40, 10, 10, 40) }; // X轴 PlotModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "帧序列", MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot, MajorGridlineColor = OxyColor.FromArgb(40, 0, 0, 0), MinorGridlineColor = OxyColor.FromArgb(20, 0, 0, 0) }); // Y轴 PlotModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "清晰度", MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot, MajorGridlineColor = OxyColor.FromArgb(40, 0, 0, 0), MinorGridlineColor = OxyColor.FromArgb(20, 0, 0, 0) }); // 清晰度曲线 var sharpnessSeries = new LineSeries { Title = "清晰度", Color = OxyColors.DodgerBlue, StrokeThickness = 2, MarkerType = MarkerType.Circle, MarkerSize = 3, MarkerFill = OxyColors.DodgerBlue, MarkerStroke = OxyColors.White, MarkerStrokeThickness = 1 }; PlotModel.Series.Add(sharpnessSeries); } /// /// 开始分析(由外部调用) /// public void StartAnalysis() { try { IsAnalyzing = true; _frameCounter = 0; _bestSharpness = 0; _historyData.Clear(); _startTime = DateTime.Now; // 清空图表 var series = PlotModel.Series[0] as LineSeries; series?.Points.Clear(); // 初始化最终报告 FinalReport = new AnalysisReport { Parameters = Parameters, StartTime = _startTime }; // 更新UI UpdateStatus("分析中..."); CommandManager.InvalidateRequerySuggested(); } catch (Exception ex) { MessageBox.Show($"开始分析失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 停止分析(由外部调用) /// public void StopAnalysis() { try { IsAnalyzing = false; // 完成报告 FinalReport.EndTime = DateTime.Now; FinalReport.FinalResult = CurrentResult; FinalReport.HistoryData = new List(_historyData); // 更新UI UpdateStatus("分析完成"); CommandManager.InvalidateRequerySuggested(); // 触发分析完成事件 OnAnalysisCompleted(); } catch (Exception ex) { MessageBox.Show($"停止分析失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 重置分析 /// public void ResetAnalysis() { Parameters.ResetToDefaults(); CurrentResult = new AnalysisResult(); FinalReport = new AnalysisReport(); _historyData.Clear(); _frameCounter = 0; _bestSharpness = 0; // 清空图表 var series = PlotModel.Series[0] as LineSeries; series?.Points.Clear(); PlotModel.InvalidatePlot(true); // 清空图像 CurrentImage = null; UpdateStatus("已重置"); } /// /// 导出报告 /// private void ExportReport() { try { var saveDialog = new Microsoft.Win32.SaveFileDialog { Filter = "文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*", FileName = $"相机分析报告_{DateTime.Now:yyyyMMdd_HHmmss}.txt" }; if (saveDialog.ShowDialog() == true) { var report = FinalReport.GenerateTextReport(); System.IO.File.WriteAllText(saveDialog.FileName, report); MessageBox.Show($"报告已保存到:{saveDialog.FileName}", "成功", MessageBoxButton.OK, MessageBoxImage.Information); } } catch (Exception ex) { MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 处理新图像帧(由外部调用) /// public void ProcessImageFrame(Mat frame) { if (!IsAnalyzing || frame == null || frame.Empty()) return; try { // 更新UI图像 Application.Current.Dispatcher.Invoke(() => { CurrentImage = BitmapSourceConverter.ToBitmapSource(frame); }); // 执行分析 PerformAnalysis(frame); _frameCounter++; } catch (Exception ex) { Console.WriteLine($"处理图像帧失败: {ex.Message}"); } } /// /// 执行单次图像分析 /// public void PerformAnalysis(Mat frame) { if (frame == null || frame.Empty()) return; try { // 1. 计算清晰度 double sharpness = FocusAnalysisEngine.CalculateImageSharpness( frame, Parameters.FocusMethod, CurrentRoi); // 2. 分析棋盘格 CheckerboardResult checkerboard; if (Parameters.AnalysisMode == AnalysisMode.AutoCheckerboard) { checkerboard = FocusAnalysisEngine.AnalyzeCheckerboardContrast(frame, CurrentRoi); } else { checkerboard = new CheckerboardResult(); } // 3. 计算质量评分 double qualityScore = FocusAnalysisEngine.CalculateQualityScore(sharpness, checkerboard); // 4. 生成建议 var suggestions = FocusAnalysisEngine.GenerateSuggestions( sharpness, checkerboard, Parameters.SharpnessThreshold, Parameters.ContrastThreshold); // 5. 更新最佳清晰度 if (sharpness > _bestSharpness) { _bestSharpness = sharpness; } // 6. 更新结果 Application.Current.Dispatcher.Invoke(() => { 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); // 更新历史数据 var dataPoint = new DataPoint(_frameCounter, sharpness, checkerboard.Detected ? checkerboard.Contrast : 0); _historyData.Add(dataPoint); // 更新图表 UpdatePlot(dataPoint); // 更新状态文本 UpdateStatusText(); }); } catch (Exception ex) { Console.WriteLine($"图像分析失败: {ex.Message}"); } } private void UpdatePlot(DataPoint dataPoint) { var series = PlotModel.Series[0] as LineSeries; if (series != null) { series.Points.Add(new OxyPlot.DataPoint(dataPoint.FrameIndex, dataPoint.Sharpness)); // 限制显示的点数(最多200个点) if (series.Points.Count > 200) { series.Points.RemoveAt(0); } PlotModel.InvalidatePlot(true); } } private void UpdateStatusText() { StatusText.Text = $"分析中... 第 {_frameCounter + 1} 帧"; FrameCountText.Text = $"帧数: {_frameCounter + 1}"; BestSharpnessText.Text = $"最佳: {_bestSharpness:F2}"; } private void UpdateStatus(string status) { StatusText.Text = status; } // ROI绘制相关 private bool _isDrawingRoi = false; private System.Windows.Point _roiStartPoint; private void ImageDisplay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (CurrentImage == null) return; _roiStartPoint = e.GetPosition(ImageDisplay); _isDrawingRoi = true; RoiRectangle.Visibility = Visibility.Visible; Canvas.SetLeft(RoiRectangle, _roiStartPoint.X); Canvas.SetTop(RoiRectangle, _roiStartPoint.Y); RoiRectangle.Width = 0; RoiRectangle.Height = 0; } private void ImageDisplay_MouseMove(object sender, MouseEventArgs e) { if (!_isDrawingRoi) return; var currentPoint = e.GetPosition(ImageDisplay); double left = Math.Min(_roiStartPoint.X, currentPoint.X); double top = Math.Min(_roiStartPoint.Y, currentPoint.Y); double width = Math.Abs(currentPoint.X - _roiStartPoint.X); double height = Math.Abs(currentPoint.Y - _roiStartPoint.Y); Canvas.SetLeft(RoiRectangle, left); Canvas.SetTop(RoiRectangle, top); RoiRectangle.Width = width; RoiRectangle.Height = height; } private void ImageDisplay_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (!_isDrawingRoi) return; _isDrawingRoi = false; var endPoint = e.GetPosition(ImageDisplay); // 转换为图像坐标 if (CurrentImage is BitmapSource bitmapSource) { double scaleX = bitmapSource.PixelWidth / ImageDisplay.ActualWidth; double scaleY = bitmapSource.PixelHeight / ImageDisplay.ActualHeight; int x = (int)(Math.Min(_roiStartPoint.X, endPoint.X) * scaleX); int y = (int)(Math.Min(_roiStartPoint.Y, endPoint.Y) * scaleY); int width = (int)(Math.Abs(endPoint.X - _roiStartPoint.X) * scaleX); int height = (int)(Math.Abs(endPoint.Y - _roiStartPoint.Y) * scaleY); // 确保ROI在图像范围内 x = Math.Max(0, Math.Min(x, bitmapSource.PixelWidth - 1)); y = Math.Max(0, Math.Min(y, bitmapSource.PixelHeight - 1)); width = Math.Max(1, Math.Min(width, bitmapSource.PixelWidth - x)); height = Math.Max(1, Math.Min(height, bitmapSource.PixelHeight - y)); CurrentRoi = new Rect(x, y, width, height); } } private void ImageDisplay_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { // 清除ROI CurrentRoi = null; RoiRectangle.Visibility = Visibility.Collapsed; } // INotifyPropertyChanged 实现 public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } // 事件 public event EventHandler AnalysisCompleted; protected virtual void OnAnalysisCompleted() { AnalysisCompleted?.Invoke(this, EventArgs.Empty); } } /// /// RelayCommand实现 /// public class RelayCommand : ICommand { private readonly Action _execute; private readonly Func _canExecute; public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } public RelayCommand(Action execute, Func canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true; public void Execute(object parameter) => _execute(); } /// /// 布尔值到文本转换器 /// public class BoolToTextConverter : System.Windows.Data.IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value is bool boolValue && parameter is string param) { var options = param.Split('|'); return boolValue ? options[0] : options[1]; } return string.Empty; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } }