| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- 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
- {
- /// <summary>
- /// 命令行硬件自检:不开GUI,直接联调 光源+电机+相机。
- /// 流程:扫描设备 → 选舱室 → 连接 → 开光源 → Z序列扫描(逐层抓帧算清晰度) → 选层 → 关光源。
- /// 用法:SelfTest.exe [houseSn] 不带参数则测第一个发现的舱室。
- /// </summary>
- 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;
- }
- }
- }
|