| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Media;
- using System.Windows.Shapes;
- using AutoFocusTool.Imaging;
- namespace AutoFocusTool
- {
- // Z序列扫描选层 + 清晰度曲线 + ROI叠加
- public partial class MainWindow
- {
- // ── Z 扫描:逐层移动→等稳→抓帧→算分,画曲线,取全局峰 ──
- private void BtnZScan_Click(object sender, RoutedEventArgs e)
- {
- LogAction($"点击 Z扫描 起点={TxtScanStart.Text} 层距={TxtScanStep.Text} 层数={TxtScanCount.Text}");
- if (_camera == null || _motor == null) { Log("需要相机+串口都连接。"); return; }
- if (_busy) { Log("串口忙。"); return; }
- int start = ParseInt2(TxtScanStart.Text, 0);
- int step = ParseInt2(TxtScanStep.Text, 128);
- int count = ParseInt2(TxtScanCount.Text, 11);
- int motorDelay = ParseInt2(TxtMotorDelay.Text, 1500); // UI线程先读好
- if (count < 2) { Log("层数至少2。"); return; }
- _scanCts = new CancellationTokenSource();
- var token = _scanCts.Token;
- Task.Run(() =>
- {
- _busy = true;
- _curve.Clear();
- try
- {
- if (_motor != null) _motor.MotorDelayMs = motorDelay;
- Log($"=== Z扫描开始: 起点{start} 层距{step} 层数{count} ===");
- _motor.OpenLED();
- Thread.Sleep(200);
- for (int i = 0; i < count; i++)
- {
- if (token.IsCancellationRequested) { Log("扫描已停止。"); break; }
- int z = start + step * i;
- // 移动到该层(开环,VerticalMoveTo 内含 motorDelay 等待)
- if (!_motor.VerticalMoveTo(z))
- Log($"层{i + 1}: Z移动到{z} 失败(继续)");
- // P0-2: 移动后相机缓冲区可能是移动前的旧帧,丢弃第1帧用第2帧
- if (_camera.GrabRgb() != 0) { Log($"层{i + 1}: 抓帧失败"); continue; }
- int gr = _camera.GrabRgb();
- if (gr != 0) { Log($"层{i + 1}: 抓帧失败"); continue; }
- byte[] buf = _camera.GetSourceBuffer();
- // ROI统一:先检well圆用圆内0.95r ROI(与精对焦一致),检不出降级中央40%
- var circle = WellDetector.Detect(buf, _camWidth, _camHeight);
- var roi = circle.Found
- ? new System.Drawing.Rectangle(
- Math.Max(0, (int)(circle.Cx - circle.Radius * 0.95)),
- Math.Max(0, (int)(circle.Cy - circle.Radius * 0.95)),
- (int)(circle.Radius * 1.9), (int)(circle.Radius * 1.9))
- : CenterRoi(_camWidth, _camHeight, 0.4);
- double score = Sharpness.Compute(buf, _camWidth, _camHeight, roi);
- _curve.Add((z, score));
- Log($"层{i + 1}/{count}: Z={z} 清晰度={score:F1}{(circle.Found ? "" : "(全图降级:未检出圆)")}");
- // 实时刷新画面+曲线
- var bmp = ImageConverter.ToBitmapSource(buf, _camWidth, _camHeight);
- Dispatcher.Invoke(() =>
- {
- ImgPreview.Source = bmp;
- TxtScore.Text = $"清晰度: {score:F1}";
- TxtZ.Text = $"Z: {z}";
- DrawCurve();
- });
- }
- _motor.CloseLED();
- // 选层:全局峰 + 单峰/全糊判据
- AnalyzeCurve();
- }
- catch (Exception ex) { Log($"扫描异常: {ex.Message}"); }
- finally { _busy = false; }
- }, token);
- }
- private void BtnStop_Click(object sender, RoutedEventArgs e)
- {
- LogAction("点击 停止扫描");
- _scanCts?.Cancel();
- _liveCts?.Cancel();
- _calibCts?.Cancel();
- Log("已请求停止。");
- }
- /// <summary>分析曲线:取全局峰,判断是否"全糊"。</summary>
- private void AnalyzeCurve()
- {
- if (_curve.Count == 0) { Log("无数据。"); return; }
- var scores = _curve.Select(c => c.score).ToList();
- double max = scores.Max();
- double mean = scores.Average();
- int peakIdx = scores.IndexOf(max);
- int peakZ = _curve[peakIdx].z;
- // 全糊判据:峰值/均值接近1 且 曲线平 → 没有明显焦点
- double ratio = mean > 1e-6 ? max / mean : 1;
- bool allBlur = ratio < 1.15; // 经验阈值,真机标定
- if (allBlur)
- {
- Log($"⚠ 整组偏糊:最高分层{peakIdx + 1}(Z={peakZ}) 分={max:F1},但 max/mean={ratio:F2} 偏低,可能无真实焦点(需扩范围搜索或空孔)。");
- }
- else
- {
- Log($"★ 最清晰层 = 第{peakIdx + 1}层 Z={peakZ} 分={max:F1}(max/mean={ratio:F2})");
- // 移动到最清晰层
- Task.Run(() =>
- {
- _busy = true;
- try { _motor.VerticalMoveTo(peakZ); Log($"已移动到最清晰层 Z={peakZ}"); }
- finally { _busy = false; }
- RefreshPositions();
- });
- }
- Dispatcher.Invoke(() => DrawCurve(peakIdx));
- }
- // ── 曲线绘制 ──
- private void DrawCurve(int peakIdx = -1)
- {
- var canvas = CurveCanvas;
- canvas.Children.Clear();
- if (_curve.Count < 2) return;
- double w = canvas.ActualWidth, h = canvas.ActualHeight;
- if (w < 10 || h < 10) return;
- double min = _curve.Min(c => c.score);
- double max = _curve.Max(c => c.score);
- double range = max - min;
- if (range < 1e-6) range = 1;
- double dx = w / (_curve.Count - 1);
- var poly = new Polyline { Stroke = Brushes.Aqua, StrokeThickness = 2 };
- for (int i = 0; i < _curve.Count; i++)
- {
- double x = i * dx;
- double y = h - 10 - (_curve[i].score - min) / range * (h - 20);
- poly.Points.Add(new Point(x, y));
- var dot = new Ellipse { Width = 6, Height = 6, Fill = (i == peakIdx) ? Brushes.Orange : Brushes.Aqua };
- Canvas.SetLeft(dot, x - 3); Canvas.SetTop(dot, y - 3);
- canvas.Children.Add(dot);
- }
- canvas.Children.Add(poly);
- if (peakIdx >= 0)
- {
- double px = peakIdx * dx;
- var line = new Line { X1 = px, Y1 = 0, X2 = px, Y2 = h, Stroke = Brushes.Orange, StrokeThickness = 1, StrokeDashArray = new DoubleCollection { 3, 3 } };
- canvas.Children.Add(line);
- }
- }
- // ── ROI 叠加框 ──
- private void DrawRoiOverlay(System.Drawing.Rectangle roi)
- {
- OverlayCanvas.Children.Clear();
- double iw = ImgPreview.ActualWidth, ih = ImgPreview.ActualHeight;
- if (iw < 10 || ih < 10) return;
- // 图像按 Uniform 缩放,ROI 同比例映射
- double sx = iw / _camWidth, sy = ih / _camHeight;
- double s = Math.Min(sx, sy);
- double offX = (iw - _camWidth * s) / 2, offY = (ih - _camHeight * s) / 2;
- var rect = new Rectangle
- {
- Stroke = Brushes.Lime,
- StrokeThickness = 2,
- Width = roi.Width * s,
- Height = roi.Height * s,
- StrokeDashArray = new DoubleCollection { 4, 2 }
- };
- Canvas.SetLeft(rect, offX + roi.X * s);
- Canvas.SetTop(rect, offY + roi.Y * s);
- OverlayCanvas.Children.Add(rect);
- }
- }
- }
|