using System; using System.Linq; using System.Threading; using AutoFocusTool.Devices; using AutoFocusTool.Serial; using AutoFocusTool.Imaging; using AutoFocusTool.Calib; using SerialCamera = AutoFocusTool.Camera.Camera; namespace AutoFocusTool { /// /// 命令行硬件自检:不开GUI,直接联调 光源+电机+相机。 /// 流程:扫描设备 → 选舱室 → 连接 → 开光源 → Z序列扫描(逐层抓帧算清晰度) → 选层 → 关光源。 /// 用法:SelfTest.exe [houseSn] 不带参数则测第一个发现的舱室。 /// internal class Program { static int Main(string[] args) { Console.OutputEncoding = System.Text.Encoding.UTF8; void L(string m) => Console.WriteLine($"{DateTime.Now:HH:mm:ss} {m}"); L("========== 相机自动对焦 硬件自检 =========="); int wantHouse = (args.Length > 0 && int.TryParse(args[0], out int hs)) ? hs : -1; int wantExp = (args.Length > 1 && int.TryParse(args[1], out int expArg)) ? expArg : 400; var scanner = new DeviceScanner { Log = L }; // 1) 扫描 L("【1】扫描设备(枚举相机 + 扫串口握手)..."); var houses = scanner.ScanAll(); if (houses.Count == 0) { L("✗ 未发现任何舱室。检查:串口线、相机USB、相机驱动、培养箱电源。"); return 1; } L($"发现 {houses.Count} 个舱室:"); foreach (var h in houses) L($" {h}"); // 2) 选舱室 HouseDevice target = wantHouse > 0 ? houses.FirstOrDefault(x => x.HouseSn == wantHouse) : houses.FirstOrDefault(x => x.HasCamera) ?? houses[0]; if (target == null) { L($"✗ 未找到舱室 {wantHouse}"); return 1; } L($"【2】选定 {target}"); // 3) 连接串口 L("【3】连接串口..."); using var motor = new HouseMotor(target.PortName) { Log = L, MotorDelayMs = 1500 }; if (!motor.Open()) { L("✗ 串口打开失败"); return 1; } int sn = motor.ShakeHands(); L($" 握手回报 houseSn={sn}"); int vpos = motor.ReadVerticalPosition(); int hpos = motor.ReadHorizontalPosition(); L($" 当前位置 Z={vpos} 水平={hpos}"); // 4) 连接相机 SerialCamera cam = null; if (target.HasCamera) { L($"【4】连接相机#{target.CcdIndex}..."); cam = new SerialCamera(target.CcdIndex, 1600, 1200, wantExp); int init = cam.Init(); if (init == 0) { cam.SetOpMode(0); cam.SetExposure(wantExp); L($" 相机就绪(拍照模式,曝光={wantExp}×100µs={wantExp/10.0}ms)"); } else { L($"✗ 相机初始化失败 code={init}"); cam.Dispose(); cam = null; } } else L("【4】该舱无配对相机,跳过相机测试"); // 5) 开光源 L("【5】开光源 LED..."); bool led = motor.OpenLED(); L($" 开光源 {(led ? "OK" : "失败")}"); Thread.Sleep(300); // 6) Z序列扫描 + 算清晰度 if (cam != null) { L("【6】Z序列扫描..."); L(" 先 Z 复位归零..."); motor.VerticalReset(); int start = 0; int step = (args.Length > 2 && int.TryParse(args[2], out int st)) ? st : 128; int count = (args.Length > 3 && int.TryParse(args[3], out int ct)) ? ct : 11; L($" 层距={step}脉冲 层数={count} 覆盖Z=0~{step*(count-1)}"); var roi = new System.Drawing.Rectangle(400, 300, 800, 600); // 中心ROI var results = new System.Collections.Generic.List<(int z, double score)>(); for (int i = 0; i < count; i++) { int z = start + step * i; motor.VerticalMoveTo(z); // 移动+等motorDelay稳定 int g = cam.GrabRgb(); if (g != 0) { L($" 层{i + 1}: 抓帧失败 code={g}"); continue; } byte[] buf = cam.GetSourceBuffer(); var (mn, mx, avg) = Diag.Stats(buf); double score = Sharpness.Compute(buf, 1600, 1200, roi); double scoreFull = Sharpness.Compute(buf, 1600, 1200, null); results.Add((z, score)); L($" 层{i + 1,2}/{count}: Z={z,-7} ROI分={score:F4} 全图分={scoreFull:F4} 像素[min={mn} max={mx} 均值={avg:F0}]"); // 存前3层图供肉眼确认相机是否真出图 if (i < 3) { string p = $"C:\\claudeFile\\TL\\AutoFocusTool\\selftest_house{target.HouseSn}_layer{i + 1}.bmp"; try { Diag.SaveBmp(buf, 1600, 1200, p); L($" 已存图: {p}"); } catch (Exception ex) { L($" 存图失败: {ex.Message}"); } } } if (results.Count > 0) { double max = results.Max(r => r.score); double mean = results.Average(r => r.score); int pk = results.FindIndex(r => r.score == max); double ratio = mean > 1e-6 ? max / mean : 1; L($"【7】选层结果:最清晰=第{pk + 1}层 Z={results[pk].z} 分={max:F1} max/mean={ratio:F2}"); if (ratio < 1.15) L(" ⚠ 曲线偏平,可能全糊/空孔(需扩范围或确认)"); else L(" ✓ 有明显焦点峰"); // 回到最清晰层 motor.VerticalMoveTo(results[pk].z); L($" 已移动到最清晰层 Z={results[pk].z}"); } } // 8) 关光源 + 收尾 L("【8】关光源 LED..."); motor.CloseLED(); cam?.Dispose(); L("========== 自检结束 =========="); L("结论:上面每一【步】都有OK即表示 光源+电机+相机 通路正常。"); return 0; } } }