|
|
@@ -1,568 +1,311 @@
|
|
|
-using OpenCvSharp;
|
|
|
+using CSScripting;
|
|
|
+using OpenCvSharp;
|
|
|
+using OpenCvSharp.Extensions;
|
|
|
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.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 partial class FocusAnalyzerControl : UserControl
|
|
|
{
|
|
|
- // 命令
|
|
|
- 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);
|
|
|
- }
|
|
|
+ private FocusAnalyzerViewModel ViewModel;
|
|
|
|
|
|
+ // 鼠标状态
|
|
|
+ private bool _isMouseDown = false;
|
|
|
+ private System.Windows.Point _mouseDownPoint;
|
|
|
|
|
|
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);
|
|
|
+ Loaded += OnLoaded;
|
|
|
}
|
|
|
|
|
|
- private void InitializeProperties()
|
|
|
+ private void OnLoaded(object sender, RoutedEventArgs e)
|
|
|
{
|
|
|
- Parameters = new AnalysisParameters();
|
|
|
- CurrentResult = new AnalysisResult();
|
|
|
- FinalReport = new AnalysisReport();
|
|
|
-
|
|
|
- // 设置默认ROI
|
|
|
- CurrentRoi = null;
|
|
|
+ // 初始化ViewModel
|
|
|
+ if (DataContext == null)
|
|
|
+ DataContext = new FocusAnalyzerViewModel();
|
|
|
+ ViewModel=DataContext as FocusAnalyzerViewModel;
|
|
|
}
|
|
|
|
|
|
- private void InitializePlot()
|
|
|
+ #region 鼠标事件处理
|
|
|
+
|
|
|
+ private void ImageDisplay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
|
{
|
|
|
- PlotModel = new PlotModel
|
|
|
- {
|
|
|
- Title = "清晰度变化曲线",
|
|
|
- TitleFontSize = 12,
|
|
|
- PlotMargins = new OxyThickness(40, 10, 10, 40)
|
|
|
- };
|
|
|
+ if (ViewModel == null) return;
|
|
|
|
|
|
- // 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);
|
|
|
- }
|
|
|
+ _isMouseDown = true;
|
|
|
+ _mouseDownPoint = e.GetPosition(ImageDisplay);
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 开始分析(由外部调用)
|
|
|
- /// </summary>
|
|
|
- public void StartAnalysis()
|
|
|
- {
|
|
|
- try
|
|
|
+ // 处理ROI操作
|
|
|
+ if (ViewModel.CurrentRoi != null)
|
|
|
{
|
|
|
- IsAnalyzing = true;
|
|
|
- _frameCounter = 0;
|
|
|
- _bestSharpness = 0;
|
|
|
- _historyData.Clear();
|
|
|
- _startTime = DateTime.Now;
|
|
|
-
|
|
|
- // 清空图表
|
|
|
- var series = PlotModel.Series[0] as LineSeries;
|
|
|
- series?.Points.Clear();
|
|
|
-
|
|
|
- // 初始化最终报告
|
|
|
- FinalReport = new AnalysisReport
|
|
|
+ // 检查是否点击了ROI
|
|
|
+ if (ViewModel.StartRoiDrag(_mouseDownPoint))
|
|
|
{
|
|
|
- Parameters = Parameters,
|
|
|
- StartTime = _startTime
|
|
|
- };
|
|
|
-
|
|
|
- // 更新UI
|
|
|
- UpdateStatus("分析中...");
|
|
|
- CommandManager.InvalidateRequerySuggested();
|
|
|
+ CaptureMouse();
|
|
|
+ return;
|
|
|
+ }
|
|
|
}
|
|
|
- catch (Exception ex)
|
|
|
+
|
|
|
+ // 开始绘制新ROI
|
|
|
+ if (ViewModel.RoiMode == RoiOperationMode.Draw)
|
|
|
{
|
|
|
- MessageBox.Show($"开始分析失败: {ex.Message}", "错误",
|
|
|
- MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
+ ViewModel.StartRoiDrawing(_mouseDownPoint);
|
|
|
+ CaptureMouse();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 停止分析(由外部调用)
|
|
|
- /// </summary>
|
|
|
- public void StopAnalysis()
|
|
|
+ private void ImageDisplay_MouseMove(object sender, MouseEventArgs e)
|
|
|
{
|
|
|
- try
|
|
|
- {
|
|
|
- IsAnalyzing = false;
|
|
|
-
|
|
|
- // 完成报告
|
|
|
- FinalReport.EndTime = DateTime.Now;
|
|
|
- FinalReport.FinalResult = CurrentResult;
|
|
|
- FinalReport.HistoryData = new List<DataPoint>(_historyData);
|
|
|
+ if (ViewModel == null || !_isMouseDown) return;
|
|
|
|
|
|
- // 更新UI
|
|
|
- UpdateStatus("分析完成");
|
|
|
- CommandManager.InvalidateRequerySuggested();
|
|
|
+ var currentPoint = e.GetPosition(ImageDisplay);
|
|
|
|
|
|
- // 触发分析完成事件
|
|
|
- OnAnalysisCompleted();
|
|
|
+ // 处理ROI拖动
|
|
|
+ if (ViewModel.RoiMode != RoiOperationMode.Draw && ViewModel.CurrentRoi != null)
|
|
|
+ {
|
|
|
+ ViewModel.UpdateRoiDrag(currentPoint);
|
|
|
+ UpdateRoiVisual();
|
|
|
}
|
|
|
- catch (Exception ex)
|
|
|
+ // 处理ROI绘制
|
|
|
+ else if (ViewModel.RoiMode == RoiOperationMode.Draw)
|
|
|
{
|
|
|
- MessageBox.Show($"停止分析失败: {ex.Message}", "错误",
|
|
|
- MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
+ ViewModel.UpdateRoiDrawing(currentPoint);
|
|
|
+ UpdateRoiVisual();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 重置分析
|
|
|
- /// </summary>
|
|
|
- public void ResetAnalysis()
|
|
|
+ private void ImageDisplay_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
|
|
{
|
|
|
- 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);
|
|
|
+ if (ViewModel == null || !_isMouseDown) return;
|
|
|
|
|
|
- // 清空图像
|
|
|
- CurrentImage = null;
|
|
|
+ _isMouseDown = false;
|
|
|
+ ReleaseMouseCapture();
|
|
|
|
|
|
- UpdateStatus("已重置");
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 导出报告
|
|
|
- /// </summary>
|
|
|
- private void ExportReport()
|
|
|
- {
|
|
|
- try
|
|
|
+ if (ViewModel.RoiMode == RoiOperationMode.Draw)
|
|
|
{
|
|
|
- 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);
|
|
|
- }
|
|
|
+ ViewModel.FinishRoiDrawing();
|
|
|
}
|
|
|
- catch (Exception ex)
|
|
|
+ else
|
|
|
{
|
|
|
- MessageBox.Show($"导出失败:{ex.Message}", "错误",
|
|
|
- MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
+ ViewModel.FinishRoiDrag();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 处理新图像帧(由外部调用)
|
|
|
- /// </summary>
|
|
|
- public void ProcessImageFrame(Mat frame)
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region ROI可视化更新
|
|
|
+
|
|
|
+ private void UpdateRoiVisual()
|
|
|
{
|
|
|
- if (!IsAnalyzing || frame == null || frame.Empty())
|
|
|
- return;
|
|
|
+ if (ViewModel?.CurrentRoi == null) return;
|
|
|
|
|
|
- try
|
|
|
- {
|
|
|
- // 更新UI图像
|
|
|
- Application.Current.Dispatcher.Invoke(() =>
|
|
|
- {
|
|
|
- CurrentImage = BitmapSourceConverter.ToBitmapSource(frame);
|
|
|
- });
|
|
|
+ // 更新ROI矩形位置和大小
|
|
|
+ Canvas.SetLeft(RoiRectangle, ViewModel.CurrentRoi.X);
|
|
|
+ Canvas.SetTop(RoiRectangle, ViewModel.CurrentRoi.Y);
|
|
|
+ RoiRectangle.Width = ViewModel.CurrentRoi.Width;
|
|
|
+ RoiRectangle.Height = ViewModel.CurrentRoi.Height;
|
|
|
|
|
|
- // 执行分析
|
|
|
- PerformAnalysis(frame);
|
|
|
- _frameCounter++;
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- Console.WriteLine($"处理图像帧失败: {ex.Message}");
|
|
|
- }
|
|
|
+ // 更新控制点位置
|
|
|
+ UpdateRoiHandles();
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 执行单次图像分析
|
|
|
- /// </summary>
|
|
|
- public void PerformAnalysis(Mat frame)
|
|
|
+ private void UpdateRoiHandles()
|
|
|
{
|
|
|
- if (frame == null || frame.Empty())
|
|
|
- return;
|
|
|
+ if (ViewModel?.CurrentRoi == null) return;
|
|
|
|
|
|
- try
|
|
|
- {
|
|
|
- // 1. 计算清晰度
|
|
|
- double sharpness = FocusAnalysisEngine.CalculateImageSharpness(
|
|
|
- frame, Parameters.FocusMethod, CurrentRoi);
|
|
|
+ var roi = ViewModel.CurrentRoi;
|
|
|
|
|
|
- // 2. 分析棋盘格
|
|
|
- CheckerboardResult checkerboard;
|
|
|
- if (Parameters.AnalysisMode == AnalysisMode.AutoCheckerboard)
|
|
|
- {
|
|
|
- checkerboard = FocusAnalysisEngine.AnalyzeCheckerboardContrast(frame, CurrentRoi);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- checkerboard = new CheckerboardResult();
|
|
|
- }
|
|
|
+ // 更新角点
|
|
|
+ UpdateHandlePosition(TopLeftHandle, roi.X, roi.Y);
|
|
|
+ UpdateHandlePosition(TopRightHandle, roi.X + roi.Width, roi.Y);
|
|
|
+ UpdateHandlePosition(BottomLeftHandle, roi.X, roi.Y + roi.Height);
|
|
|
+ UpdateHandlePosition(BottomRightHandle, roi.X + roi.Width, roi.Y + roi.Height);
|
|
|
|
|
|
- // 3. 计算质量评分
|
|
|
- double qualityScore = FocusAnalysisEngine.CalculateQualityScore(sharpness, checkerboard);
|
|
|
-
|
|
|
- // 4. 生成建议
|
|
|
- var suggestions = FocusAnalysisEngine.GenerateSuggestions(
|
|
|
- sharpness, checkerboard,
|
|
|
- Parameters.SharpnessThreshold,
|
|
|
- Parameters.ContrastThreshold);
|
|
|
-
|
|
|
- // 5. 更新最佳清晰度
|
|
|
- if (sharpness > _bestSharpness)
|
|
|
- {
|
|
|
- _bestSharpness = sharpness;
|
|
|
- }
|
|
|
+ // 更新边中点
|
|
|
+ UpdateHandlePosition(TopHandle, roi.X + roi.Width / 2, roi.Y);
|
|
|
+ UpdateHandlePosition(RightHandle, roi.X + roi.Width, roi.Y + roi.Height / 2);
|
|
|
+ UpdateHandlePosition(BottomHandle, roi.X + roi.Width / 2, roi.Y + roi.Height);
|
|
|
+ UpdateHandlePosition(LeftHandle, roi.X, roi.Y + roi.Height / 2);
|
|
|
|
|
|
- // 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}");
|
|
|
- }
|
|
|
+ // 更新旋转控制点
|
|
|
+ UpdateRotateHandlePosition(roi);
|
|
|
}
|
|
|
|
|
|
- private void UpdatePlot(DataPoint dataPoint)
|
|
|
+ private void UpdateHandlePosition(FrameworkElement handle, double x, double y)
|
|
|
{
|
|
|
- var series = PlotModel.Series[0] as LineSeries;
|
|
|
- if (series != null)
|
|
|
- {
|
|
|
- series.Points.Add(new OxyPlot.DataPoint(dataPoint.FrameIndex, dataPoint.Sharpness));
|
|
|
+ if (handle == null) return;
|
|
|
|
|
|
- // 限制显示的点数(最多200个点)
|
|
|
- if (series.Points.Count > 200)
|
|
|
- {
|
|
|
- series.Points.RemoveAt(0);
|
|
|
- }
|
|
|
-
|
|
|
- PlotModel.InvalidatePlot(true);
|
|
|
- }
|
|
|
+ Canvas.SetLeft(handle, x - handle.Width / 2);
|
|
|
+ Canvas.SetTop(handle, y - handle.Height / 2);
|
|
|
}
|
|
|
|
|
|
- private void UpdateStatusText()
|
|
|
+ private void UpdateRotateHandlePosition(RotatedRect roi)
|
|
|
{
|
|
|
- StatusText.Text = $"分析中... 第 {_frameCounter + 1} 帧";
|
|
|
- FrameCountText.Text = $"帧数: {_frameCounter + 1}";
|
|
|
- BestSharpnessText.Text = $"最佳: {_bestSharpness:F2}";
|
|
|
- }
|
|
|
-
|
|
|
- private void UpdateStatus(string status)
|
|
|
- {
|
|
|
- StatusText.Text = status;
|
|
|
- }
|
|
|
+ if (RotateHandle == null) return;
|
|
|
|
|
|
- // ROI绘制相关
|
|
|
- private bool _isDrawingRoi = false;
|
|
|
- private System.Windows.Point _roiStartPoint;
|
|
|
+ // 旋转控制点在矩形顶部中心上方
|
|
|
+ double centerX = roi.X + roi.Width / 2;
|
|
|
+ double centerY = roi.Y + roi.Height / 2;
|
|
|
|
|
|
- private void ImageDisplay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
|
- {
|
|
|
- if (CurrentImage == null) return;
|
|
|
+ // 计算旋转后的位置
|
|
|
+ double angleRad = roi.Angle * Math.PI / 180;
|
|
|
+ double radius = Math.Max(roi.Width, roi.Height) / 2 + 20; // 距离中心20像素
|
|
|
|
|
|
- _roiStartPoint = e.GetPosition(ImageDisplay);
|
|
|
- _isDrawingRoi = true;
|
|
|
+ double x = centerX + radius * Math.Sin(angleRad);
|
|
|
+ double y = centerY - radius * Math.Cos(angleRad);
|
|
|
|
|
|
- RoiRectangle.Visibility = Visibility.Visible;
|
|
|
- Canvas.SetLeft(RoiRectangle, _roiStartPoint.X);
|
|
|
- Canvas.SetTop(RoiRectangle, _roiStartPoint.Y);
|
|
|
- RoiRectangle.Width = 0;
|
|
|
- RoiRectangle.Height = 0;
|
|
|
+ // 设置位置
|
|
|
+ RotateHandle.RenderTransform = new RotateTransform(roi.Angle);
|
|
|
+ Canvas.SetLeft(RotateHandle, x - 5); // 5是三角形宽度的一半
|
|
|
+ Canvas.SetTop(RotateHandle, y - 5); // 5是三角形高度的一半
|
|
|
}
|
|
|
|
|
|
- private void ImageDisplay_MouseMove(object sender, MouseEventArgs e)
|
|
|
- {
|
|
|
- if (!_isDrawingRoi) return;
|
|
|
+ #endregion
|
|
|
|
|
|
- var currentPoint = e.GetPosition(ImageDisplay);
|
|
|
+ #region 依赖属性
|
|
|
|
|
|
- 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);
|
|
|
+ // CurrentImage依赖属性
|
|
|
+ public static readonly DependencyProperty CurrentImageProperty =
|
|
|
+ DependencyProperty.Register("CurrentImage", typeof(ImageSource), typeof(FocusAnalyzerControl),
|
|
|
+ new PropertyMetadata(null, OnCurrentImageChanged));
|
|
|
|
|
|
- Canvas.SetLeft(RoiRectangle, left);
|
|
|
- Canvas.SetTop(RoiRectangle, top);
|
|
|
- RoiRectangle.Width = width;
|
|
|
- RoiRectangle.Height = height;
|
|
|
+ public ImageSource CurrentImage
|
|
|
+ {
|
|
|
+ get => (ImageSource)GetValue(CurrentImageProperty);
|
|
|
+ set => SetValue(CurrentImageProperty, value);
|
|
|
}
|
|
|
|
|
|
- private void ImageDisplay_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
|
|
+ private static void OnCurrentImageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
{
|
|
|
- if (!_isDrawingRoi) return;
|
|
|
+ var control = d as FocusAnalyzerControl;
|
|
|
+ if (control.ViewModel != null)
|
|
|
+ control.ViewModel.CurrentImage = e.NewValue as ImageSource;
|
|
|
+ }
|
|
|
|
|
|
- _isDrawingRoi = false;
|
|
|
- var endPoint = e.GetPosition(ImageDisplay);
|
|
|
+ // Parameters依赖属性
|
|
|
+ public static readonly DependencyProperty ParametersProperty =
|
|
|
+ DependencyProperty.Register("Parameters", typeof(AnalysisParameters), typeof(FocusAnalyzerControl),
|
|
|
+ new PropertyMetadata(new AnalysisParameters(), OnParametersChanged));
|
|
|
|
|
|
- // 转换为图像坐标
|
|
|
- if (CurrentImage is BitmapSource bitmapSource)
|
|
|
- {
|
|
|
- double scaleX = bitmapSource.PixelWidth / ImageDisplay.ActualWidth;
|
|
|
- double scaleY = bitmapSource.PixelHeight / ImageDisplay.ActualHeight;
|
|
|
+ public AnalysisParameters Parameters
|
|
|
+ {
|
|
|
+ get => (AnalysisParameters)GetValue(ParametersProperty);
|
|
|
+ set => SetValue(ParametersProperty, value);
|
|
|
+ }
|
|
|
|
|
|
- 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);
|
|
|
+ private static void OnParametersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ var control = d as FocusAnalyzerControl;
|
|
|
+ if (control.ViewModel != null)
|
|
|
+ control.ViewModel.Parameters = e.NewValue as AnalysisParameters;
|
|
|
+ }
|
|
|
|
|
|
- // 确保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));
|
|
|
+ // CurrentResult依赖属性
|
|
|
+ public static readonly DependencyProperty CurrentResultProperty =
|
|
|
+ DependencyProperty.Register("CurrentResult", typeof(AnalysisResult), typeof(FocusAnalyzerControl),
|
|
|
+ new PropertyMetadata(new AnalysisResult(), OnCurrentResultChanged));
|
|
|
|
|
|
- CurrentRoi = new Rect(x, y, width, height);
|
|
|
- }
|
|
|
+ public AnalysisResult CurrentResult
|
|
|
+ {
|
|
|
+ get => (AnalysisResult)GetValue(CurrentResultProperty);
|
|
|
+ set => SetValue(CurrentResultProperty, value);
|
|
|
}
|
|
|
|
|
|
- private void ImageDisplay_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
|
|
+ private static void OnCurrentResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
{
|
|
|
- // 清除ROI
|
|
|
- CurrentRoi = null;
|
|
|
- RoiRectangle.Visibility = Visibility.Collapsed;
|
|
|
+ var control = d as FocusAnalyzerControl;
|
|
|
+ if (control.ViewModel != null)
|
|
|
+ control.ViewModel.CurrentResult = e.NewValue as AnalysisResult;
|
|
|
}
|
|
|
|
|
|
- // INotifyPropertyChanged 实现
|
|
|
- public event PropertyChangedEventHandler PropertyChanged;
|
|
|
+ // StartAnalysisCommand依赖属性
|
|
|
+ public static readonly DependencyProperty StartAnalysisCommandProperty =
|
|
|
+ DependencyProperty.Register("StartAnalysisCommand", typeof(ICommand), typeof(FocusAnalyzerControl),
|
|
|
+ new PropertyMetadata(null, OnStartAnalysisCommandChanged));
|
|
|
|
|
|
- protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
|
|
+ public ICommand StartAnalysisCommand
|
|
|
{
|
|
|
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
+ get => (ICommand)GetValue(StartAnalysisCommandProperty);
|
|
|
+ set => SetValue(StartAnalysisCommandProperty, value);
|
|
|
}
|
|
|
|
|
|
- protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
|
|
|
+ private static void OnStartAnalysisCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
{
|
|
|
- if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
|
|
- field = value;
|
|
|
- OnPropertyChanged(propertyName);
|
|
|
- return true;
|
|
|
+ var control = d as FocusAnalyzerControl;
|
|
|
+ if (control.ViewModel != null)
|
|
|
+ control.ViewModel.StartAnalysisCommand = e.NewValue as ICommand;
|
|
|
}
|
|
|
|
|
|
- // 事件
|
|
|
- public event EventHandler AnalysisCompleted;
|
|
|
+ // 其他命令和属性的依赖属性...
|
|
|
|
|
|
- protected virtual void OnAnalysisCompleted()
|
|
|
- {
|
|
|
- AnalysisCompleted?.Invoke(this, EventArgs.Empty);
|
|
|
- }
|
|
|
- }
|
|
|
+ #endregion
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// RelayCommand实现
|
|
|
- /// </summary>
|
|
|
- public class RelayCommand : ICommand
|
|
|
- {
|
|
|
- private readonly Action _execute;
|
|
|
- private readonly Func<bool> _canExecute;
|
|
|
+ #region 公共方法
|
|
|
|
|
|
- public event EventHandler CanExecuteChanged
|
|
|
+ /// <summary>
|
|
|
+ /// 处理图像帧
|
|
|
+ /// </summary>
|
|
|
+ public async Task ProcessImageFrameAsync(OpenCvSharp.Mat frame)
|
|
|
{
|
|
|
- add => CommandManager.RequerySuggested += value;
|
|
|
- remove => CommandManager.RequerySuggested -= value;
|
|
|
+ if (ViewModel != null)
|
|
|
+ await ViewModel.ProcessImageFrameAsync(frame);
|
|
|
}
|
|
|
|
|
|
- public RelayCommand(Action execute, Func<bool> canExecute = null)
|
|
|
+ /// <summary>
|
|
|
+ /// 执行单次分析
|
|
|
+ /// </summary>
|
|
|
+ public void PerformAnalysis(OpenCvSharp.Mat frame)
|
|
|
{
|
|
|
- _execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
|
|
- _canExecute = canExecute;
|
|
|
+ ViewModel?.PerformAnalysis(frame);
|
|
|
}
|
|
|
|
|
|
- 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)
|
|
|
+ /// <summary>
|
|
|
+ /// 获取当前ROI区域
|
|
|
+ /// </summary>
|
|
|
+ public OpenCvSharp.Rect? GetCurrentRoiRect()
|
|
|
{
|
|
|
- if (value is bool boolValue && parameter is string param)
|
|
|
- {
|
|
|
- var options = param.Split('|');
|
|
|
- return boolValue ? options[0] : options[1];
|
|
|
- }
|
|
|
- return string.Empty;
|
|
|
+ if (ViewModel?.CurrentRoi == null)
|
|
|
+ return null;
|
|
|
+
|
|
|
+ return new OpenCvSharp.Rect(
|
|
|
+ (int)ViewModel.CurrentRoi.X,
|
|
|
+ (int)ViewModel.CurrentRoi.Y,
|
|
|
+ (int)ViewModel.CurrentRoi.Width,
|
|
|
+ (int)ViewModel.CurrentRoi.Height);
|
|
|
}
|
|
|
|
|
|
- public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
|
|
+ /// <summary>
|
|
|
+ /// 获取旋转ROI
|
|
|
+ /// </summary>
|
|
|
+ public OpenCvSharp.RotatedRect? GetCurrentRotatedRoi()
|
|
|
{
|
|
|
- throw new NotImplementedException();
|
|
|
+ if (ViewModel?.CurrentRoi == null)
|
|
|
+ return null;
|
|
|
+
|
|
|
+ return ViewModel.CurrentRoi.ToCvRotatedRect();
|
|
|
}
|
|
|
+
|
|
|
+ #endregion
|
|
|
}
|
|
|
-}
|
|
|
+}
|