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();
}
}
}