| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- 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
- {
- /// <summary>
- /// FocusAnalyzerControl.xaml 的交互逻辑
- /// </summary>
- 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<DataPoint> _historyData = new List<DataPoint>();
- 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);
- }
- /// <summary>
- /// 开始分析(由外部调用)
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// 停止分析(由外部调用)
- /// </summary>
- public void StopAnalysis()
- {
- try
- {
- IsAnalyzing = false;
- // 完成报告
- FinalReport.EndTime = DateTime.Now;
- FinalReport.FinalResult = CurrentResult;
- FinalReport.HistoryData = new List<DataPoint>(_historyData);
- // 更新UI
- UpdateStatus("分析完成");
- CommandManager.InvalidateRequerySuggested();
- // 触发分析完成事件
- OnAnalysisCompleted();
- }
- catch (Exception ex)
- {
- MessageBox.Show($"停止分析失败: {ex.Message}", "错误",
- MessageBoxButton.OK, MessageBoxImage.Error);
- }
- }
- /// <summary>
- /// 重置分析
- /// </summary>
- 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("已重置");
- }
- /// <summary>
- /// 导出报告
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// 处理新图像帧(由外部调用)
- /// </summary>
- 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}");
- }
- }
- /// <summary>
- /// 执行单次图像分析
- /// </summary>
- 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<string>(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<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
- {
- if (EqualityComparer<T>.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);
- }
- }
- /// <summary>
- /// RelayCommand实现
- /// </summary>
- public class RelayCommand : ICommand
- {
- private readonly Action _execute;
- private readonly Func<bool> _canExecute;
- public event EventHandler CanExecuteChanged
- {
- add => CommandManager.RequerySuggested += value;
- remove => CommandManager.RequerySuggested -= value;
- }
- public RelayCommand(Action execute, Func<bool> 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();
- }
- /// <summary>
- /// 布尔值到文本转换器
- /// </summary>
- 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();
- }
- }
- }
|