WellSpacing.cs 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. using System;
  2. using System.IO;
  3. using System.Threading;
  4. using AutoFocusTool.Serial;
  5. using AutoFocusTool.Imaging;
  6. using SerialCamera = AutoFocusTool.Camera.Camera;
  7. namespace AutoFocusTool
  8. {
  9. /// <summary>
  10. /// 只读诊断工具:逐个 well 移动到其 EEPROM 水平位置 → 抓帧 → 跑 WellDetector → 存图。
  11. /// 用于验证“每个 well 移动过去后相机实际看到的是不是对应的孔、偏差多少”。
  12. /// 不做对焦/不改标定,只在固定 Z(传入或默认)和固定曝光下拍一张。
  13. ///
  14. /// 用法:WellSpacing.exe [COM口=COM11] [相机index=2] [曝光=80] [Z=93000]
  15. /// 存图目录:calib_result\well_check_yyyyMMdd_HHmmss\
  16. /// </summary>
  17. internal class WellSpacing
  18. {
  19. [STAThread]
  20. static int Main(string[] args)
  21. {
  22. Console.OutputEncoding = System.Text.Encoding.UTF8;
  23. string port = args.Length > 0 ? args[0] : "COM11";
  24. int camIdx = args.Length > 1 && int.TryParse(args[1], out int ci) ? ci : 2;
  25. int exposure = args.Length > 2 && int.TryParse(args[2], out int ex) ? ex : 80;
  26. int zPos = args.Length > 3 && int.TryParse(args[3], out int z) ? z : 93000;
  27. int camW = 2592, camH = 1944;
  28. void L(string m) => Console.WriteLine($"{DateTime.Now:HH:mm:ss} {m}");
  29. string stamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
  30. string outDir = $@"C:\claudeFile\TL\AutoFocusTool\calib_result\well_check_{stamp}";
  31. Directory.CreateDirectory(outDir);
  32. L($"========== well 实拍检测 {port} cam#{camIdx} 曝光{exposure} Z{zPos} ==========");
  33. L($"存图目录: {outDir}");
  34. var motor = new HouseMotor(port) { Log = null };
  35. if (!motor.Open()) { L($"✗ 打开 {port} 失败(串口被占用?)"); return 1; }
  36. motor.MotorDelayMs = 1500;
  37. int sn = motor.ShakeHands();
  38. L($"握手 houseSn = {sn}");
  39. var cam = new SerialCamera(camIdx, camW, camH, exposure);
  40. int init = cam.Init();
  41. if (init != 0) { L($"✗ 相机#{camIdx} 初始化失败 code={init}"); motor.Close(); return 2; }
  42. cam.SetOpMode(0);
  43. cam.SetExposure(exposure);
  44. motor.OpenLED();
  45. Thread.Sleep(300);
  46. // 先到统一对焦 Z(所有 well 用同一 Z,只看水平是否对到不同孔)
  47. motor.VerticalMoveTo(zPos, 1500);
  48. try
  49. {
  50. for (int w = 1; w <= 16; w++)
  51. {
  52. int hpos = motor.ReadWellHorizontalPos(w);
  53. if (hpos < 0) { L($"well{w,2}: 读EEPROM位置失败,跳过"); continue; }
  54. motor.HorizontalMoveTo(hpos, 1500);
  55. Thread.Sleep(200);
  56. cam.GrabRgb(); // 丢弃第一帧
  57. Thread.Sleep(100);
  58. cam.GrabRgb();
  59. byte[] buf = cam.GetSourceBuffer();
  60. var c = WellDetector.Detect(buf, camW, camH);
  61. string tag = c.Found
  62. ? $"圆心({c.Cx:F0},{c.Cy:F0}) R={c.Radius:F0} 偏移X={c.OffsetXPct:F1}% Y={c.OffsetYPct:F1}% 完整={c.Complete}"
  63. : $"未检出圆[{c.RejectReason}]";
  64. L($"well{w,2}: EEPROM水平={hpos,7} {tag}");
  65. string detTag = c.Found ? (c.Complete ? "OK" : "partial") : "NOCIRCLE";
  66. string path = Path.Combine(outDir, $"well{w:D2}_hp{hpos}_{detTag}.bmp");
  67. ImageConverter.SaveBmp(buf, camW, camH, path);
  68. }
  69. }
  70. finally
  71. {
  72. motor.CloseLED();
  73. cam.Dispose();
  74. motor.Close();
  75. }
  76. L($"========== 完成,图片在: {outDir} ==========");
  77. return 0;
  78. }
  79. }
  80. }