MainWindow.Camera.cs 6.8 KB

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