FocusAnalyzerViewModel.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  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 SqlSugar;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Collections.ObjectModel;
  12. using System.Linq;
  13. using System.Text;
  14. using System.Threading.Tasks;
  15. using System.Windows.Input;
  16. using System.Windows.Media;
  17. using System.Windows.Media.Imaging;
  18. using System.Windows.Threading;
  19. using TeamAAS_VP.Core;
  20. using TeamAAS_VP.Enums;
  21. using TeamAAS_VP.Models;
  22. using TeamAAS_VP.Services;
  23. namespace TeamAAS_VP.Controls
  24. {
  25. /// <summary>
  26. /// FocusAnalyzer用户控件ViewModel
  27. /// </summary>
  28. public class FocusAnalyzerViewModel : BindableBase
  29. {
  30. private readonly FileDialogService _fileDialogService;
  31. private readonly ImageAnalysisService _imageAnalysisService;
  32. private readonly CheckerboardDetectionService _checkerboardService;
  33. private readonly DispatcherTimer _analysisTimer;
  34. private string _title = "棋盘格清晰度分析工具";
  35. private BitmapSource _currentImage;
  36. private Mat _currentMat;
  37. private AnalysisParameters _parameters;
  38. private AnalysisResult _currentResult;
  39. private RoiModel _currentRoi;
  40. private ChessboardInfo _chessboardInfo;
  41. private RoiOperationMode _roiMode;
  42. private string _statusText;
  43. private bool _isAnalyzing;
  44. private double _bestSharpness;
  45. private int _frameCount;
  46. private PlotModel _plotModel;
  47. private List<AnalysisResult> _resultHistory;
  48. // function to convert ROI from canvas coords to image pixel Rect (set by view)
  49. public Func<RoiModel, OpenCvSharp.Rect?> RoiToImageRectConverter { get; set; }
  50. public string Title
  51. {
  52. get => _title;
  53. set => SetProperty(ref _title, value);
  54. }
  55. public BitmapSource CurrentImage
  56. {
  57. get => _currentImage;
  58. set => SetProperty(ref _currentImage, value);
  59. }
  60. public AnalysisParameters Parameters
  61. {
  62. get => _parameters;
  63. set => SetProperty(ref _parameters, value);
  64. }
  65. public AnalysisResult CurrentResult
  66. {
  67. get => _currentResult;
  68. set => SetProperty(ref _currentResult, value);
  69. }
  70. public RoiModel CurrentRoi
  71. {
  72. get => _currentRoi;
  73. set => SetProperty(ref _currentRoi, value);
  74. }
  75. public ChessboardInfo ChessboardInfo
  76. {
  77. get => _chessboardInfo;
  78. set => SetProperty(ref _chessboardInfo, value);
  79. }
  80. public RoiOperationMode RoiMode
  81. {
  82. get => _roiMode;
  83. set => SetProperty(ref _roiMode, value);
  84. }
  85. public string StatusText
  86. {
  87. get => _statusText;
  88. set => SetProperty(ref _statusText, value);
  89. }
  90. public bool IsAnalyzing
  91. {
  92. get => _isAnalyzing;
  93. set => SetProperty(ref _isAnalyzing, value);
  94. }
  95. public PlotModel PlotModel
  96. {
  97. get => _plotModel;
  98. set => SetProperty(ref _plotModel, value);
  99. }
  100. public string BestSharpnessText => _bestSharpness > 0 ? $"{_bestSharpness:F2}" : "N/A";
  101. public string FrameCountText => $"帧数: {_frameCount}";
  102. public ObservableCollection<FocusMethod> FocusMethods { get; }
  103. public ObservableCollection<AnalysisMode> AnalysisModes { get; }
  104. public DelegateCommand LoadImageCommand { get; }
  105. public DelegateCommand StartAnalysisCommand { get; }
  106. public DelegateCommand StopAnalysisCommand { get; }
  107. public DelegateCommand ResetCommand { get; }
  108. public DelegateCommand ExportCommand { get; }
  109. public DelegateCommand ClearRoiCommand { get; }
  110. public DelegateCommand DetectChessboardCommand { get; }
  111. public FocusAnalyzerViewModel()
  112. {
  113. _fileDialogService = new FileDialogService();
  114. _imageAnalysisService = new ImageAnalysisService();
  115. _checkerboardService = new CheckerboardDetectionService();
  116. _parameters = new AnalysisParameters();
  117. _currentResult = new AnalysisResult();
  118. _currentRoi = new RoiModel();
  119. _resultHistory = new List<AnalysisResult>();
  120. _roiMode = RoiOperationMode.Draw; // 默认为绘制模式
  121. FocusMethods = new ObservableCollection<FocusMethod>(Enum.GetValues(typeof(FocusMethod)).Cast<FocusMethod>());
  122. AnalysisModes = new ObservableCollection<AnalysisMode>(Enum.GetValues(typeof(AnalysisMode)).Cast<AnalysisMode>());
  123. LoadImageCommand = new DelegateCommand(OnLoadImage);
  124. StartAnalysisCommand = new DelegateCommand(OnStartAnalysis, CanStartAnalysis).ObservesProperty(() => CurrentImage);
  125. StopAnalysisCommand = new DelegateCommand(OnStopAnalysis, () => IsAnalyzing).ObservesProperty(() => IsAnalyzing);
  126. ResetCommand = new DelegateCommand(OnReset);
  127. ExportCommand = new DelegateCommand(OnExport, () => _resultHistory.Count > 0);
  128. ClearRoiCommand = new DelegateCommand(OnClearRoi);
  129. DetectChessboardCommand = new DelegateCommand(OnDetectChessboard, () => _currentMat != null);
  130. InitializePlot();
  131. _analysisTimer = new DispatcherTimer
  132. {
  133. Interval = TimeSpan.FromMilliseconds(100)
  134. };
  135. _analysisTimer.Tick += OnAnalysisTick;
  136. StatusText = "就绪";
  137. }
  138. private void OnLoadImage()
  139. {
  140. try
  141. {
  142. string filePath = _fileDialogService.OpenImageFile();
  143. if (string.IsNullOrEmpty(filePath))
  144. return;
  145. // 释放之前的图像
  146. _currentMat?.Dispose();
  147. // 加载图像
  148. _currentMat = Cv2.ImRead(filePath, ImreadModes.Color);
  149. if (_currentMat == null || _currentMat.Empty())
  150. {
  151. StatusText = "无法加载图像";
  152. return;
  153. }
  154. // 转换为WPF图像
  155. CurrentImage = _currentMat.ToWriteableBitmap();
  156. // 自动检测棋盘格
  157. OnDetectChessboard();
  158. // 如果启用了实时分析,立即分析
  159. if (Parameters.EnableRealtimeAnalysis)
  160. {
  161. AnalyzeCurrentImage();
  162. }
  163. StatusText = $"图像已加载: {_currentMat.Width}x{_currentMat.Height}";
  164. }
  165. catch (Exception ex)
  166. {
  167. StatusText = $"加载图像失败: {ex.Message}";
  168. }
  169. }
  170. private void OnDetectChessboard()
  171. {
  172. if (_currentMat == null || _currentMat.Empty())
  173. return;
  174. try
  175. {
  176. StatusText = "正在检测棋盘格...";
  177. Task.Run(() =>
  178. {
  179. ChessboardInfo info = null;
  180. if (RoiToImageRectConverter != null && CurrentRoi?.IsValid == true)
  181. {
  182. var imgRoi = RoiToImageRectConverter(CurrentRoi);
  183. if (imgRoi.HasValue)
  184. {
  185. using (var sub = new Mat(_currentMat, imgRoi.Value))
  186. {
  187. info = _checkerboardService.DetectChessboard(sub);
  188. }
  189. if (info != null && info.IsDetected)
  190. {
  191. for (int i = 0; i < info.Corners.Length; i++)
  192. {
  193. info.Corners[i].X += imgRoi.Value.X;
  194. info.Corners[i].Y += imgRoi.Value.Y;
  195. }
  196. var br = info.BoundingRect;
  197. info.BoundingRect = new OpenCvSharp.Rect(br.X + imgRoi.Value.X, br.Y + imgRoi.Value.Y, br.Width, br.Height);
  198. }
  199. }
  200. }
  201. if (info == null || !info.IsDetected)
  202. {
  203. info = _checkerboardService.DetectChessboard(_currentMat);
  204. }
  205. System.Windows.Application.Current.Dispatcher.Invoke(() =>
  206. {
  207. ChessboardInfo = info;
  208. if (info.IsDetected)
  209. {
  210. StatusText = $"检测成功: {info.CornersWidth}x{info.CornersHeight} 角点";
  211. // 绘制棋盘格
  212. var displayMat = _currentMat.Clone();
  213. _checkerboardService.DrawChessboard(displayMat, info);
  214. CurrentImage = displayMat.ToWriteableBitmap();
  215. displayMat.Dispose();
  216. }
  217. else
  218. {
  219. StatusText = "未检测到棋盘格";
  220. }
  221. });
  222. });
  223. }
  224. catch (Exception ex)
  225. {
  226. StatusText = $"检测失败: {ex.Message}";
  227. }
  228. }
  229. private bool CanStartAnalysis()
  230. {
  231. return CurrentImage != null && !IsAnalyzing;
  232. }
  233. private void OnStartAnalysis()
  234. {
  235. IsAnalyzing = true;
  236. _frameCount = 0;
  237. _bestSharpness = 0;
  238. _resultHistory.Clear();
  239. StatusText = "分析中...";
  240. if (Parameters.EnableRealtimeAnalysis)
  241. {
  242. _analysisTimer.Start();
  243. }
  244. else
  245. {
  246. AnalyzeCurrentImage();
  247. IsAnalyzing = false;
  248. StatusText = "分析完成";
  249. }
  250. }
  251. private void OnStopAnalysis()
  252. {
  253. _analysisTimer.Stop();
  254. IsAnalyzing = false;
  255. StatusText = "已停止";
  256. }
  257. private void OnAnalysisTick(object sender, EventArgs e)
  258. {
  259. AnalyzeCurrentImage();
  260. }
  261. private void AnalyzeCurrentImage()
  262. {
  263. if (_currentMat == null || _currentMat.Empty())
  264. return;
  265. try
  266. {
  267. Rect? roi = null;
  268. // 根据分析模式确定分析区域
  269. switch (Parameters.AnalysisMode)
  270. {
  271. case AnalysisMode.RoiOnly:
  272. if (CurrentRoi.IsValid)
  273. {
  274. if (RoiToImageRectConverter != null)
  275. {
  276. roi = RoiToImageRectConverter(CurrentRoi);
  277. }
  278. else
  279. {
  280. roi = new Rect(
  281. (int)CurrentRoi.X,
  282. (int)CurrentRoi.Y,
  283. (int)CurrentRoi.Width,
  284. (int)CurrentRoi.Height);
  285. }
  286. }
  287. break;
  288. case AnalysisMode.ChessboardOnly:
  289. if (ChessboardInfo?.IsDetected == true)
  290. {
  291. roi = ChessboardInfo.BoundingRect;
  292. }
  293. break;
  294. }
  295. // 执行分析
  296. var result = _imageAnalysisService.AnalyzeImage(_currentMat, Parameters.FocusMethod, roi);
  297. CurrentResult = result;
  298. _resultHistory.Add(result);
  299. _frameCount++;
  300. // 更新最佳清晰度
  301. if (result.Sharpness > _bestSharpness)
  302. {
  303. _bestSharpness = result.Sharpness;
  304. RaisePropertyChanged(nameof(BestSharpnessText));
  305. }
  306. RaisePropertyChanged(nameof(FrameCountText));
  307. // 更新图表
  308. UpdatePlot();
  309. StatusText = $"清晰度: {result.Sharpness:F2}, 对比度: {result.Contrast:F2}";
  310. }
  311. catch (Exception ex)
  312. {
  313. StatusText = $"分析错误: {ex.Message}";
  314. }
  315. }
  316. private void OnReset()
  317. {
  318. OnStopAnalysis();
  319. _frameCount = 0;
  320. _bestSharpness = 0;
  321. _resultHistory.Clear();
  322. CurrentResult = new AnalysisResult();
  323. InitializePlot();
  324. StatusText = "已重置";
  325. RaisePropertyChanged(nameof(BestSharpnessText));
  326. RaisePropertyChanged(nameof(FrameCountText));
  327. }
  328. private void OnExport()
  329. {
  330. try
  331. {
  332. string filePath = _fileDialogService.SaveFile(
  333. $"分析报告_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
  334. "CSV文件|*.csv|所有文件|*.*");
  335. if (string.IsNullOrEmpty(filePath))
  336. return;
  337. using (var writer = new System.IO.StreamWriter(filePath))
  338. {
  339. writer.WriteLine("时间,清晰度,对比度,质量评分");
  340. foreach (var result in _resultHistory)
  341. {
  342. writer.WriteLine($"{result.Timestamp:yyyy-MM-dd HH:mm:ss.fff},{result.Sharpness:F2},{result.Contrast:F2},{result.QualityScore:F2}");
  343. }
  344. }
  345. StatusText = $"报告已导出: {filePath}";
  346. }
  347. catch (Exception ex)
  348. {
  349. StatusText = $"导出失败: {ex.Message}";
  350. }
  351. }
  352. private void OnClearRoi()
  353. {
  354. CurrentRoi = new RoiModel();
  355. StatusText = "ROI已清除";
  356. }
  357. private void InitializePlot()
  358. {
  359. PlotModel = new PlotModel
  360. {
  361. Title = "清晰度趋势",
  362. Background = OxyColors.White
  363. };
  364. PlotModel.Axes.Add(new LinearAxis
  365. {
  366. Position = AxisPosition.Bottom,
  367. Title = "帧数",
  368. MajorGridlineStyle = LineStyle.Solid,
  369. MinorGridlineStyle = LineStyle.Dot,
  370. MajorGridlineColor = OxyColor.FromRgb(230, 230, 230)
  371. });
  372. PlotModel.Axes.Add(new LinearAxis
  373. {
  374. Position = AxisPosition.Left,
  375. Title = "清晰度",
  376. MajorGridlineStyle = LineStyle.Solid,
  377. MinorGridlineStyle = LineStyle.Dot,
  378. MajorGridlineColor = OxyColor.FromRgb(230, 230, 230)
  379. });
  380. var series = new LineSeries
  381. {
  382. Title = "清晰度",
  383. Color = OxyColor.FromRgb(0, 122, 204),
  384. StrokeThickness = 2,
  385. MarkerType = MarkerType.Circle,
  386. MarkerSize = 3,
  387. MarkerFill = OxyColor.FromRgb(0, 122, 204)
  388. };
  389. PlotModel.Series.Add(series);
  390. }
  391. private void UpdatePlot()
  392. {
  393. if (PlotModel.Series.Count == 0)
  394. return;
  395. var series = PlotModel.Series[0] as LineSeries;
  396. if (series == null)
  397. return;
  398. series.Points.Clear();
  399. for (int i = 0; i < _resultHistory.Count; i++)
  400. {
  401. series.Points.Add(new OxyPlot.DataPoint(i + 1, _resultHistory[i].Sharpness));
  402. }
  403. PlotModel.InvalidatePlot(true);
  404. }
  405. }
  406. }