MainWindow.Camera.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. using System;
  2. using System.Threading;
  3. using System.Threading.Tasks;
  4. using System.Windows;
  5. using System.Windows.Media;
  6. using System.Windows.Media.Imaging;
  7. using Microsoft.Win32;
  8. using AutoFocusTool.Imaging;
  9. namespace AutoFocusTool
  10. {
  11. // 抓帧显示 / 实时预览 / 曝光增益 / 光源 / 存图
  12. public partial class MainWindow
  13. {
  14. private byte[] _lastFrame; // 最近一帧原始 BGR
  15. /// <summary>抓一帧并刷新显示+清晰度分。返回是否成功。</summary>
  16. private bool GrabAndShow()
  17. {
  18. if (_camera == null) { Log("无相机。"); return false; }
  19. int r = _camera.GrabRgb();
  20. if (r != 0) { Log($"抓帧失败(code={r})"); return false; }
  21. byte[] buf = _camera.GetSourceBuffer();
  22. _lastFrame = buf;
  23. // 中心 ROI(画面中央 50%)
  24. var roi = CenterRoi(_camWidth, _camHeight, 0.5);
  25. double score = Sharpness.Compute(buf, _camWidth, _camHeight, roi);
  26. var bmp = ImageConverter.ToBitmapSource(buf, _camWidth, _camHeight);
  27. Dispatcher.Invoke(() =>
  28. {
  29. ImgPreview.Source = bmp;
  30. TxtScore.Text = $"清晰度: {score:F1}";
  31. DrawRoiOverlay(roi);
  32. });
  33. return true;
  34. }
  35. private static System.Drawing.Rectangle CenterRoi(int w, int h, double frac)
  36. {
  37. int rw = (int)(w * frac), rh = (int)(h * frac);
  38. return new System.Drawing.Rectangle((w - rw) / 2, (h - rh) / 2, rw, rh);
  39. }
  40. // ── 抓一帧 ──
  41. private void BtnGrab_Click(object sender, RoutedEventArgs e)
  42. {
  43. Task.Run(() => GrabAndShow());
  44. }
  45. /// <summary>刷新"当前各参数值"显示(曝光/增益/Z/水平)。在UI线程调用。</summary>
  46. private void UpdateParamDisplay()
  47. {
  48. void Upd()
  49. {
  50. int exp = _camera?.Exposure ?? ParseInt(TxtExposure.Text, 0);
  51. string gain = $"R{ParseInt(TxtGainR.Text, 0)} G{ParseInt(TxtGainG.Text, 0)} B{ParseInt(TxtGainB.Text, 0)}";
  52. TxtCurValues.Text = $"当前值:曝光 {exp}×100µs({exp / 10.0}ms) | 增益 {gain} | Z {_lastZ} | 水平 {_lastH} | well {(_curWell > 0 ? _curWell.ToString() : "--")}";
  53. }
  54. if (Dispatcher.CheckAccess()) Upd(); else Dispatcher.BeginInvoke((Action)Upd);
  55. }
  56. // ── 实时预览 ──
  57. private void TglLive_Click(object sender, RoutedEventArgs e)
  58. {
  59. if (TglLive.IsChecked == true)
  60. {
  61. _liveCts = new CancellationTokenSource();
  62. var token = _liveCts.Token;
  63. Log("实时预览开始。");
  64. Task.Run(async () =>
  65. {
  66. int frames = 0;
  67. var sw = System.Diagnostics.Stopwatch.StartNew();
  68. while (!token.IsCancellationRequested)
  69. {
  70. if (!_busy) GrabAndShow();
  71. frames++;
  72. if (sw.ElapsedMilliseconds >= 1000)
  73. {
  74. double fps = frames * 1000.0 / sw.ElapsedMilliseconds;
  75. Dispatcher.Invoke(() => TxtFps.Text = $"帧率: {fps:F1} fps");
  76. frames = 0; sw.Restart();
  77. }
  78. await Task.Delay(30, token).ContinueWith(_ => { });
  79. }
  80. }, token);
  81. }
  82. else
  83. {
  84. _liveCts?.Cancel();
  85. Log("实时预览停止。");
  86. }
  87. }
  88. // ── 曝光 ──
  89. private void BtnSetExp_Click(object sender, RoutedEventArgs e)
  90. {
  91. if (_camera == null) return;
  92. int exp = ParseInt(TxtExposure.Text, 400);
  93. int r = _camera.SetExposure(exp);
  94. Log($"设置曝光 {exp}×100µs = {exp / 10.0}ms (code={r})");
  95. UpdateParamDisplay();
  96. // P0-3: 设曝光后传感器需时间生效,等待+丢弃旧帧,再抓显示当前曝光的画面
  97. if (TglLive.IsChecked != true)
  98. Task.Run(() =>
  99. {
  100. Thread.Sleep(Math.Max(200, exp / 5)); // 至少2×曝光时间(单位100µs)
  101. if (_camera != null) _camera.GrabRgb(); // 丢弃旧曝光的帧
  102. GrabAndShow();
  103. });
  104. }
  105. // ── 增益 ──
  106. private void BtnSetGain_Click(object sender, RoutedEventArgs e)
  107. {
  108. if (_camera == null) return;
  109. byte rr = (byte)ParseInt(TxtGainR.Text, 25);
  110. byte gg = (byte)ParseInt(TxtGainG.Text, 14);
  111. byte bb = (byte)ParseInt(TxtGainB.Text, 25);
  112. int r = _camera.SetGain(rr, gg, bb);
  113. Log($"设置增益 R{rr} G{gg} B{bb} (code={r})");
  114. UpdateParamDisplay();
  115. if (TglLive.IsChecked != true) Task.Run(() => GrabAndShow());
  116. }
  117. // ── 光源 ──
  118. private void BtnLedOn_Click(object sender, RoutedEventArgs e)
  119. {
  120. if (_motor == null) return;
  121. Task.Run(() => { _busy = true; bool ok = _motor.OpenLED(); _busy = false; Log($"开光源 {(ok ? "OK" : "失败")}"); });
  122. }
  123. private void BtnLedOff_Click(object sender, RoutedEventArgs e)
  124. {
  125. if (_motor == null) return;
  126. Task.Run(() => { _busy = true; bool ok = _motor.CloseLED(); _busy = false; Log($"关光源 {(ok ? "OK" : "失败")}"); });
  127. }
  128. // ── 存图 ──
  129. private void BtnSaveImg_Click(object sender, RoutedEventArgs e)
  130. {
  131. if (_lastFrame == null) { Log("还没有图像。"); return; }
  132. var dlg = new SaveFileDialog
  133. {
  134. Filter = "BMP 图片|*.bmp",
  135. FileName = $"house{_current?.HouseSn}_{DateTime.Now:yyyyMMdd_HHmmss}.bmp"
  136. };
  137. if (dlg.ShowDialog() == true)
  138. {
  139. try
  140. {
  141. ImageConverter.SaveBmp(_lastFrame, _camWidth, _camHeight, dlg.FileName);
  142. Log($"已存图: {dlg.FileName}");
  143. }
  144. catch (Exception ex) { Log($"存图失败: {ex.Message}"); }
  145. }
  146. }
  147. }
  148. }