| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
- 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
- {
- /// <summary>
- /// FocusAnalyzer用户控件ViewModel
- /// </summary>
- 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<object> 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<ErrorEventArgs> ErrorOccurred;
- #endregion
- #region 私有字段
- private int _frameCounter = 0;
- private double _bestSharpness = 0;
- private DateTime _startTime;
- private ObservableCollection<Models.DataPoint> _historyData = new ObservableCollection<Models.DataPoint>();
- // 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<object>(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 公共方法
- /// <summary>
- /// 处理图像帧
- /// </summary>
- 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));
- }
- }
- /// <summary>
- /// 执行单次分析
- /// </summary>
- 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绘制方法
- /// <summary>
- /// 开始ROI绘制
- /// </summary>
- public void StartRoiDrawing(System.Windows.Point startPoint)
- {
- if (RoiMode != RoiOperationMode.Draw)
- return;
- _roiStartPoint = startPoint;
- CurrentRoi = new RotatedRect(startPoint.X, startPoint.Y, 0, 0, 0);
- }
- /// <summary>
- /// 更新ROI绘制
- /// </summary>
- 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;
- }
- /// <summary>
- /// 完成ROI绘制
- /// </summary>
- public void FinishRoiDrawing()
- {
- OnRoiChanged();
- }
- /// <summary>
- /// 开始ROI拖动
- /// </summary>
- 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;
- }
- /// <summary>
- /// 更新ROI拖动
- /// </summary>
- 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;
- }
- }
- /// <summary>
- /// 结束ROI拖动
- /// </summary>
- public void FinishRoiDrag()
- {
- _currentDragHandle = DragHandleType.None;
- _roiBeforeDrag = null;
- OnRoiChanged();
- }
- /// <summary>
- /// 获取指定点的控制点类型
- /// </summary>
- 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<string>(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;
- }
- }
- }
|