FocusAnalyzerViewModel.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. using OpenCvSharp;
  2. using OpenCvSharp.WpfExtensions;
  3. using OxyPlot;
  4. using OxyPlot.Axes;
  5. using OxyPlot.Series;
  6. using Prism.Commands;
  7. using Prism.Mvvm;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Collections.ObjectModel;
  11. using System.Linq;
  12. using System.Text;
  13. using System.Threading.Tasks;
  14. using System.Windows.Input;
  15. using System.Windows.Media;
  16. using TeamAAS_VP.Core;
  17. using TeamAAS_VP.Enums;
  18. using TeamAAS_VP.Models;
  19. namespace TeamAAS_VP.Controls
  20. {
  21. /// <summary>
  22. /// FocusAnalyzer用户控件ViewModel
  23. /// </summary>
  24. public class FocusAnalyzerViewModel : BindableBase
  25. {
  26. #region 可绑定属性
  27. // 图像源
  28. private ImageSource _currentImage;
  29. public ImageSource CurrentImage
  30. {
  31. get => _currentImage;
  32. set => SetProperty(ref _currentImage, value);
  33. }
  34. // 分析参数
  35. private AnalysisParameters _parameters = new AnalysisParameters();
  36. public AnalysisParameters Parameters
  37. {
  38. get => _parameters;
  39. set => SetProperty(ref _parameters, value);
  40. }
  41. // 当前结果
  42. private AnalysisResult _currentResult = new AnalysisResult();
  43. public AnalysisResult CurrentResult
  44. {
  45. get => _currentResult;
  46. set => SetProperty(ref _currentResult, value);
  47. }
  48. // 最终报告
  49. private AnalysisReport _finalReport = new AnalysisReport();
  50. public AnalysisReport FinalReport
  51. {
  52. get => _finalReport;
  53. set => SetProperty(ref _finalReport, value);
  54. }
  55. // 分析状态
  56. private bool _isAnalyzing;
  57. public bool IsAnalyzing
  58. {
  59. get => _isAnalyzing;
  60. set => SetProperty(ref _isAnalyzing, value);
  61. }
  62. // ROI区域
  63. private RotatedRect _currentRoi;
  64. public RotatedRect CurrentRoi
  65. {
  66. get => _currentRoi;
  67. set => SetProperty(ref _currentRoi, value);
  68. }
  69. // 是否显示ROI
  70. private bool _showRoi = true;
  71. public bool ShowRoi
  72. {
  73. get => _showRoi;
  74. set => SetProperty(ref _showRoi, value);
  75. }
  76. // ROI操作模式
  77. private RoiOperationMode _roiMode = RoiOperationMode.Draw;
  78. public RoiOperationMode RoiMode
  79. {
  80. get => _roiMode;
  81. set => SetProperty(ref _roiMode, value);
  82. }
  83. // 图表模型
  84. private PlotModel _plotModel;
  85. public PlotModel PlotModel
  86. {
  87. get => _plotModel;
  88. set => SetProperty(ref _plotModel, value);
  89. }
  90. // 状态文本
  91. private string _statusText = "就绪";
  92. public string StatusText
  93. {
  94. get => _statusText;
  95. set => SetProperty(ref _statusText, value);
  96. }
  97. // 帧数文本
  98. private string _frameCountText = "帧数: 0";
  99. public string FrameCountText
  100. {
  101. get => _frameCountText;
  102. set => SetProperty(ref _frameCountText, value);
  103. }
  104. // 最佳清晰度文本
  105. private string _bestSharpnessText = "最佳: 0.00";
  106. public string BestSharpnessText
  107. {
  108. get => _bestSharpnessText;
  109. set => SetProperty(ref _bestSharpnessText, value);
  110. }
  111. // 枚举列表
  112. public Array FocusMethods => Enum.GetValues(typeof(FocusMethod));
  113. public Array AnalysisModes => Enum.GetValues(typeof(AnalysisMode));
  114. #endregion
  115. #region 命令
  116. // 控制命令(绑定到外部)
  117. public ICommand StartAnalysisCommand { get; set; }
  118. public ICommand StopAnalysisCommand { get; set; }
  119. public ICommand ResetCommand { get; set; }
  120. public ICommand ExportCommand { get; set; }
  121. // 内部命令
  122. public DelegateCommand<object> SetRoiModeCommand { get; private set; }
  123. public DelegateCommand ClearRoiCommand { get; private set; }
  124. #endregion
  125. #region 事件
  126. // 控件事件(供外部订阅)
  127. public event EventHandler AnalysisStarted;
  128. public event EventHandler AnalysisStopped;
  129. public event EventHandler RoiChanged;
  130. public event EventHandler<ErrorEventArgs> ErrorOccurred;
  131. #endregion
  132. #region 私有字段
  133. private int _frameCounter = 0;
  134. private double _bestSharpness = 0;
  135. private DateTime _startTime;
  136. private ObservableCollection<Models.DataPoint> _historyData = new ObservableCollection<Models.DataPoint>();
  137. // ROI绘制状态
  138. private System.Windows.Point _roiStartPoint;
  139. private System.Windows.Point _roiDragStartPoint;
  140. private RotatedRect _roiBeforeDrag;
  141. private DragHandleType _currentDragHandle = DragHandleType.None;
  142. #endregion
  143. #region 构造函数
  144. public FocusAnalyzerViewModel()
  145. {
  146. InitializeCommands();
  147. InitializeProperties();
  148. InitializePlot();
  149. }
  150. #endregion
  151. #region 初始化
  152. private void InitializeCommands()
  153. {
  154. // ROI模式切换命令
  155. SetRoiModeCommand = new DelegateCommand<object>(mode =>
  156. {
  157. RoiMode = (RoiOperationMode)mode;
  158. });
  159. // 清除ROI命令
  160. ClearRoiCommand = new DelegateCommand(() =>
  161. {
  162. CurrentRoi = null;
  163. OnRoiChanged();
  164. });
  165. }
  166. private void InitializeProperties()
  167. {
  168. Parameters = new AnalysisParameters();
  169. CurrentResult = new AnalysisResult();
  170. FinalReport = new AnalysisReport();
  171. // 初始化默认ROI
  172. CurrentRoi = new RotatedRect(100, 100, 200, 200, 0);
  173. }
  174. private void InitializePlot()
  175. {
  176. PlotModel = new PlotModel
  177. {
  178. Title = "清晰度变化曲线",
  179. TitleFontSize = 12,
  180. PlotMargins = new OxyThickness(40, 10, 10, 40)
  181. };
  182. PlotModel.Axes.Add(new LinearAxis
  183. {
  184. Position = AxisPosition.Bottom,
  185. Title = "帧序列",
  186. MajorGridlineStyle = LineStyle.Solid,
  187. MinorGridlineStyle = LineStyle.Dot
  188. });
  189. PlotModel.Axes.Add(new LinearAxis
  190. {
  191. Position = AxisPosition.Left,
  192. Title = "清晰度",
  193. MajorGridlineStyle = LineStyle.Solid,
  194. MinorGridlineStyle = LineStyle.Dot
  195. });
  196. PlotModel.Series.Add(new LineSeries
  197. {
  198. Title = "清晰度",
  199. Color = OxyColors.DodgerBlue,
  200. StrokeThickness = 2
  201. });
  202. }
  203. #endregion
  204. #region 公共方法
  205. /// <summary>
  206. /// 处理图像帧
  207. /// </summary>
  208. public async Task ProcessImageFrameAsync(Mat frame)
  209. {
  210. if (!IsAnalyzing || frame == null || frame.Empty())
  211. return;
  212. try
  213. {
  214. // 更新图像
  215. await App.Current.Dispatcher.InvokeAsync(() =>
  216. {
  217. CurrentImage = BitmapSourceConverter.ToBitmapSource(frame);
  218. });
  219. // 执行分析
  220. await Task.Run(() => PerformAnalysis(frame));
  221. _frameCounter++;
  222. // 更新UI
  223. UpdateStatusText();
  224. }
  225. catch (Exception ex)
  226. {
  227. OnErrorOccurred(new ErrorEventArgs($"处理图像帧失败: {ex.Message}", ex));
  228. }
  229. }
  230. /// <summary>
  231. /// 执行单次分析
  232. /// </summary>
  233. public void PerformAnalysis(Mat frame)
  234. {
  235. if (frame == null || frame.Empty())
  236. return;
  237. try
  238. {
  239. // 将RotatedRect转换为Rect(简化处理)
  240. Rect? roiRect = null;
  241. if (CurrentRoi != null)
  242. {
  243. roiRect = new Rect((int)CurrentRoi.X, (int)CurrentRoi.Y, (int)CurrentRoi.Width, (int)CurrentRoi.Height);
  244. }
  245. // 计算清晰度
  246. double sharpness = FocusAnalysisEngine.CalculateImageSharpness(
  247. frame, Parameters.FocusMethod, roiRect);
  248. // 分析棋盘格
  249. CheckerboardResult checkerboard;
  250. if (Parameters.AnalysisMode == AnalysisMode.AutoCheckerboard)
  251. {
  252. checkerboard = FocusAnalysisEngine.AnalyzeCheckerboardContrast(frame, roiRect);
  253. }
  254. else
  255. {
  256. checkerboard = new CheckerboardResult();
  257. }
  258. // 更新结果
  259. App.Current.Dispatcher.Invoke(() =>
  260. {
  261. UpdateAnalysisResult(sharpness, checkerboard);
  262. UpdateHistoryData(sharpness, checkerboard);
  263. UpdatePlot();
  264. });
  265. }
  266. catch (Exception ex)
  267. {
  268. OnErrorOccurred(new ErrorEventArgs($"图像分析失败: {ex.Message}", ex));
  269. }
  270. }
  271. #endregion
  272. #region ROI绘制方法
  273. /// <summary>
  274. /// 开始ROI绘制
  275. /// </summary>
  276. public void StartRoiDrawing(System.Windows.Point startPoint)
  277. {
  278. if (RoiMode != RoiOperationMode.Draw)
  279. return;
  280. _roiStartPoint = startPoint;
  281. CurrentRoi = new RotatedRect(startPoint.X, startPoint.Y, 0, 0, 0);
  282. }
  283. /// <summary>
  284. /// 更新ROI绘制
  285. /// </summary>
  286. public void UpdateRoiDrawing(System.Windows.Point currentPoint)
  287. {
  288. if (RoiMode != RoiOperationMode.Draw || CurrentRoi == null)
  289. return;
  290. double x = Math.Min(_roiStartPoint.X, currentPoint.X);
  291. double y = Math.Min(_roiStartPoint.Y, currentPoint.Y);
  292. double width = Math.Abs(currentPoint.X - _roiStartPoint.X);
  293. double height = Math.Abs(currentPoint.Y - _roiStartPoint.Y);
  294. CurrentRoi.X = x;
  295. CurrentRoi.Y = y;
  296. CurrentRoi.Width = width;
  297. CurrentRoi.Height = height;
  298. }
  299. /// <summary>
  300. /// 完成ROI绘制
  301. /// </summary>
  302. public void FinishRoiDrawing()
  303. {
  304. OnRoiChanged();
  305. }
  306. /// <summary>
  307. /// 开始ROI拖动
  308. /// </summary>
  309. public bool StartRoiDrag(System.Windows.Point point)
  310. {
  311. if (CurrentRoi == null || !CurrentRoi.ContainsPoint(point))
  312. return false;
  313. // 检查是否点击了控制点
  314. _currentDragHandle = GetDragHandleAtPoint(point);
  315. if (_currentDragHandle != DragHandleType.None)
  316. {
  317. _roiDragStartPoint = point;
  318. _roiBeforeDrag = new RotatedRect(
  319. CurrentRoi.X, CurrentRoi.Y,
  320. CurrentRoi.Width, CurrentRoi.Height,
  321. CurrentRoi.Angle);
  322. return true;
  323. }
  324. return false;
  325. }
  326. /// <summary>
  327. /// 更新ROI拖动
  328. /// </summary>
  329. public void UpdateRoiDrag(System.Windows.Point currentPoint)
  330. {
  331. if (CurrentRoi == null || _roiBeforeDrag == null)
  332. return;
  333. double deltaX = currentPoint.X - _roiDragStartPoint.X;
  334. double deltaY = currentPoint.Y - _roiDragStartPoint.Y;
  335. switch (_currentDragHandle)
  336. {
  337. case DragHandleType.TopLeft:
  338. CurrentRoi.X = _roiBeforeDrag.X + deltaX;
  339. CurrentRoi.Y = _roiBeforeDrag.Y + deltaY;
  340. CurrentRoi.Width = _roiBeforeDrag.Width - deltaX;
  341. CurrentRoi.Height = _roiBeforeDrag.Height - deltaY;
  342. break;
  343. case DragHandleType.TopRight:
  344. CurrentRoi.Y = _roiBeforeDrag.Y + deltaY;
  345. CurrentRoi.Width = _roiBeforeDrag.Width + deltaX;
  346. CurrentRoi.Height = _roiBeforeDrag.Height - deltaY;
  347. break;
  348. case DragHandleType.BottomLeft:
  349. CurrentRoi.X = _roiBeforeDrag.X + deltaX;
  350. CurrentRoi.Width = _roiBeforeDrag.Width - deltaX;
  351. CurrentRoi.Height = _roiBeforeDrag.Height + deltaY;
  352. break;
  353. case DragHandleType.BottomRight:
  354. CurrentRoi.Width = _roiBeforeDrag.Width + deltaX;
  355. CurrentRoi.Height = _roiBeforeDrag.Height + deltaY;
  356. break;
  357. case DragHandleType.Rotate:
  358. // 计算旋转角度
  359. var center = new System.Windows.Point(
  360. _roiBeforeDrag.CenterX,
  361. _roiBeforeDrag.CenterY);
  362. double startAngle = Math.Atan2(
  363. _roiDragStartPoint.Y - center.Y,
  364. _roiDragStartPoint.X - center.X);
  365. double currentAngle = Math.Atan2(
  366. currentPoint.Y - center.Y,
  367. currentPoint.X - center.X);
  368. double angleDelta = (currentAngle - startAngle) * 180 / Math.PI;
  369. CurrentRoi.Angle = (_roiBeforeDrag.Angle + angleDelta) % 360;
  370. break;
  371. case DragHandleType.None:
  372. // 移动整个ROI
  373. CurrentRoi.X = _roiBeforeDrag.X + deltaX;
  374. CurrentRoi.Y = _roiBeforeDrag.Y + deltaY;
  375. break;
  376. }
  377. }
  378. /// <summary>
  379. /// 结束ROI拖动
  380. /// </summary>
  381. public void FinishRoiDrag()
  382. {
  383. _currentDragHandle = DragHandleType.None;
  384. _roiBeforeDrag = null;
  385. OnRoiChanged();
  386. }
  387. /// <summary>
  388. /// 获取指定点的控制点类型
  389. /// </summary>
  390. private DragHandleType GetDragHandleAtPoint(System.Windows.Point point)
  391. {
  392. if (CurrentRoi == null) return DragHandleType.None;
  393. const double handleSize = 8;
  394. var center = new System.Windows.Point(CurrentRoi.CenterX, CurrentRoi.CenterY);
  395. // 检查旋转控制点(在矩形顶部中心上方)
  396. var rotateHandle = new System.Windows.Point(center.X, CurrentRoi.Y - 20);
  397. if (Distance(point, rotateHandle) <= handleSize)
  398. return DragHandleType.Rotate;
  399. // 检查角点
  400. var corners = new[]
  401. {
  402. new System.Windows.Point(CurrentRoi.X, CurrentRoi.Y), // 左上
  403. new System.Windows.Point(CurrentRoi.X + CurrentRoi.Width, CurrentRoi.Y), // 右上
  404. new System.Windows.Point(CurrentRoi.X, CurrentRoi.Y + CurrentRoi.Height), // 左下
  405. new System.Windows.Point(CurrentRoi.X + CurrentRoi.Width, CurrentRoi.Y + CurrentRoi.Height) // 右下
  406. };
  407. // 替换 switch 表达式为 switch 语句以兼容 C# 7.3
  408. for (int i = 0; i < corners.Length; i++)
  409. {
  410. if (Distance(point, corners[i]) <= handleSize)
  411. {
  412. switch (i)
  413. {
  414. case 0:
  415. return DragHandleType.TopLeft;
  416. case 1:
  417. return DragHandleType.TopRight;
  418. case 2:
  419. return DragHandleType.BottomLeft;
  420. case 3:
  421. return DragHandleType.BottomRight;
  422. default:
  423. return DragHandleType.None;
  424. }
  425. }
  426. }
  427. return DragHandleType.None;
  428. }
  429. private double Distance(System.Windows.Point p1, System.Windows.Point p2)
  430. {
  431. return Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
  432. }
  433. #endregion
  434. #region 私有方法
  435. private void UpdateAnalysisResult(double sharpness, CheckerboardResult checkerboard)
  436. {
  437. double qualityScore = FocusAnalysisEngine.CalculateQualityScore(sharpness, checkerboard);
  438. var suggestions = FocusAnalysisEngine.GenerateSuggestions(
  439. sharpness, checkerboard,
  440. Parameters.SharpnessThreshold,
  441. Parameters.ContrastThreshold);
  442. if (sharpness > _bestSharpness)
  443. _bestSharpness = sharpness;
  444. CurrentResult.Sharpness = sharpness;
  445. CurrentResult.Checkerboard = checkerboard;
  446. CurrentResult.QualityScore = qualityScore;
  447. CurrentResult.FrameCount = _frameCounter + 1;
  448. CurrentResult.BestSharpness = _bestSharpness;
  449. CurrentResult.AnalysisTime = DateTime.Now;
  450. CurrentResult.Suggestions = new ObservableCollection<string>(suggestions);
  451. }
  452. private void UpdateHistoryData(double sharpness, CheckerboardResult checkerboard)
  453. {
  454. var dataPoint = new Models.DataPoint(_frameCounter, sharpness,
  455. checkerboard.Detected ? checkerboard.Contrast : 0);
  456. _historyData.Add(dataPoint);
  457. // 添加到最终报告
  458. FinalReport.HistoryData.Add(dataPoint);
  459. }
  460. private void UpdatePlot()
  461. {
  462. if (PlotModel.Series[0] is LineSeries series)
  463. {
  464. series.Points.Clear();
  465. for (int i = 0; i < _historyData.Count; i++)
  466. {
  467. series.Points.Add(new OxyPlot.DataPoint(i, _historyData[i].Sharpness));
  468. }
  469. // 限制显示点数
  470. if (series.Points.Count > 200)
  471. {
  472. series.Points.RemoveRange(0, series.Points.Count - 200);
  473. }
  474. PlotModel.InvalidatePlot(true);
  475. }
  476. }
  477. private void UpdateStatusText()
  478. {
  479. StatusText = $"分析中... 第 {_frameCounter + 1} 帧";
  480. FrameCountText = $"帧数: {_frameCounter + 1}";
  481. BestSharpnessText = $"最佳: {_bestSharpness:F2}";
  482. }
  483. #endregion
  484. #region 事件触发方法
  485. protected virtual void OnAnalysisStarted()
  486. {
  487. AnalysisStarted?.Invoke(this, EventArgs.Empty);
  488. }
  489. protected virtual void OnAnalysisStopped()
  490. {
  491. AnalysisStopped?.Invoke(this, EventArgs.Empty);
  492. }
  493. protected virtual void OnRoiChanged()
  494. {
  495. RoiChanged?.Invoke(this, EventArgs.Empty);
  496. }
  497. protected virtual void OnErrorOccurred(ErrorEventArgs e)
  498. {
  499. ErrorOccurred?.Invoke(this, e);
  500. }
  501. #endregion
  502. }
  503. public class ErrorEventArgs : EventArgs
  504. {
  505. public string Message { get; }
  506. public Exception Exception { get; }
  507. public ErrorEventArgs(string message, Exception exception = null)
  508. {
  509. Message = message;
  510. Exception = exception;
  511. }
  512. }
  513. }