Program.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. using System;
  2. using System.Linq;
  3. using System.Threading;
  4. using AutoFocusTool.Devices;
  5. using AutoFocusTool.Serial;
  6. using AutoFocusTool.Imaging;
  7. using AutoFocusTool.Calib;
  8. using SerialCamera = AutoFocusTool.Camera.Camera;
  9. namespace AutoFocusTool
  10. {
  11. /// <summary>
  12. /// 命令行硬件自检:不开GUI,直接联调 光源+电机+相机。
  13. /// 流程:扫描设备 → 选舱室 → 连接 → 开光源 → Z序列扫描(逐层抓帧算清晰度) → 选层 → 关光源。
  14. /// 用法:SelfTest.exe [houseSn] 不带参数则测第一个发现的舱室。
  15. /// </summary>
  16. internal class Program
  17. {
  18. static int Main(string[] args)
  19. {
  20. Console.OutputEncoding = System.Text.Encoding.UTF8;
  21. void L(string m) => Console.WriteLine($"{DateTime.Now:HH:mm:ss} {m}");
  22. L("========== 相机自动对焦 硬件自检 ==========");
  23. int wantHouse = (args.Length > 0 && int.TryParse(args[0], out int hs)) ? hs : -1;
  24. int wantExp = (args.Length > 1 && int.TryParse(args[1], out int expArg)) ? expArg : 400;
  25. var scanner = new DeviceScanner { Log = L };
  26. // 1) 扫描
  27. L("【1】扫描设备(枚举相机 + 扫串口握手)...");
  28. var houses = scanner.ScanAll();
  29. if (houses.Count == 0)
  30. {
  31. L("✗ 未发现任何舱室。检查:串口线、相机USB、相机驱动、培养箱电源。");
  32. return 1;
  33. }
  34. L($"发现 {houses.Count} 个舱室:");
  35. foreach (var h in houses) L($" {h}");
  36. // 2) 选舱室
  37. HouseDevice target = wantHouse > 0
  38. ? houses.FirstOrDefault(x => x.HouseSn == wantHouse)
  39. : houses.FirstOrDefault(x => x.HasCamera) ?? houses[0];
  40. if (target == null) { L($"✗ 未找到舱室 {wantHouse}"); return 1; }
  41. L($"【2】选定 {target}");
  42. // 3) 连接串口
  43. L("【3】连接串口...");
  44. using var motor = new HouseMotor(target.PortName) { Log = L, MotorDelayMs = 1500 };
  45. if (!motor.Open()) { L("✗ 串口打开失败"); return 1; }
  46. int sn = motor.ShakeHands();
  47. L($" 握手回报 houseSn={sn}");
  48. int vpos = motor.ReadVerticalPosition();
  49. int hpos = motor.ReadHorizontalPosition();
  50. L($" 当前位置 Z={vpos} 水平={hpos}");
  51. // 4) 连接相机
  52. SerialCamera cam = null;
  53. if (target.HasCamera)
  54. {
  55. L($"【4】连接相机#{target.CcdIndex}...");
  56. cam = new SerialCamera(target.CcdIndex, 1600, 1200, wantExp);
  57. int init = cam.Init();
  58. if (init == 0) { cam.SetOpMode(0); cam.SetExposure(wantExp); L($" 相机就绪(拍照模式,曝光={wantExp}×100µs={wantExp/10.0}ms)"); }
  59. else { L($"✗ 相机初始化失败 code={init}"); cam.Dispose(); cam = null; }
  60. }
  61. else L("【4】该舱无配对相机,跳过相机测试");
  62. // 5) 开光源
  63. L("【5】开光源 LED...");
  64. bool led = motor.OpenLED();
  65. L($" 开光源 {(led ? "OK" : "失败")}");
  66. Thread.Sleep(300);
  67. // 6) Z序列扫描 + 算清晰度
  68. if (cam != null)
  69. {
  70. L("【6】Z序列扫描...");
  71. L(" 先 Z 复位归零...");
  72. motor.VerticalReset();
  73. int start = 0;
  74. int step = (args.Length > 2 && int.TryParse(args[2], out int st)) ? st : 128;
  75. int count = (args.Length > 3 && int.TryParse(args[3], out int ct)) ? ct : 11;
  76. L($" 层距={step}脉冲 层数={count} 覆盖Z=0~{step*(count-1)}");
  77. var roi = new System.Drawing.Rectangle(400, 300, 800, 600); // 中心ROI
  78. var results = new System.Collections.Generic.List<(int z, double score)>();
  79. for (int i = 0; i < count; i++)
  80. {
  81. int z = start + step * i;
  82. motor.VerticalMoveTo(z); // 移动+等motorDelay稳定
  83. int g = cam.GrabRgb();
  84. if (g != 0) { L($" 层{i + 1}: 抓帧失败 code={g}"); continue; }
  85. byte[] buf = cam.GetSourceBuffer();
  86. var (mn, mx, avg) = Diag.Stats(buf);
  87. double score = Sharpness.Compute(buf, 1600, 1200, roi);
  88. double scoreFull = Sharpness.Compute(buf, 1600, 1200, null);
  89. results.Add((z, score));
  90. L($" 层{i + 1,2}/{count}: Z={z,-7} ROI分={score:F4} 全图分={scoreFull:F4} 像素[min={mn} max={mx} 均值={avg:F0}]");
  91. // 存前3层图供肉眼确认相机是否真出图
  92. if (i < 3)
  93. {
  94. string p = $"C:\\claudeFile\\TL\\AutoFocusTool\\selftest_house{target.HouseSn}_layer{i + 1}.bmp";
  95. try { Diag.SaveBmp(buf, 1600, 1200, p); L($" 已存图: {p}"); }
  96. catch (Exception ex) { L($" 存图失败: {ex.Message}"); }
  97. }
  98. }
  99. if (results.Count > 0)
  100. {
  101. double max = results.Max(r => r.score);
  102. double mean = results.Average(r => r.score);
  103. int pk = results.FindIndex(r => r.score == max);
  104. double ratio = mean > 1e-6 ? max / mean : 1;
  105. L($"【7】选层结果:最清晰=第{pk + 1}层 Z={results[pk].z} 分={max:F1} max/mean={ratio:F2}");
  106. if (ratio < 1.15) L(" ⚠ 曲线偏平,可能全糊/空孔(需扩范围或确认)");
  107. else L(" ✓ 有明显焦点峰");
  108. // 回到最清晰层
  109. motor.VerticalMoveTo(results[pk].z);
  110. L($" 已移动到最清晰层 Z={results[pk].z}");
  111. }
  112. }
  113. // 8) 关光源 + 收尾
  114. L("【8】关光源 LED...");
  115. motor.CloseLED();
  116. cam?.Dispose();
  117. L("========== 自检结束 ==========");
  118. L("结论:上面每一【步】都有OK即表示 光源+电机+相机 通路正常。");
  119. return 0;
  120. }
  121. }
  122. }