|
|
@@ -0,0 +1,92 @@
|
|
|
+using System;
|
|
|
+using System.IO;
|
|
|
+using System.Threading;
|
|
|
+using AutoFocusTool.Serial;
|
|
|
+using AutoFocusTool.Imaging;
|
|
|
+using SerialCamera = AutoFocusTool.Camera.Camera;
|
|
|
+
|
|
|
+namespace AutoFocusTool
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// 只读诊断工具:逐个 well 移动到其 EEPROM 水平位置 → 抓帧 → 跑 WellDetector → 存图。
|
|
|
+ /// 用于验证“每个 well 移动过去后相机实际看到的是不是对应的孔、偏差多少”。
|
|
|
+ /// 不做对焦/不改标定,只在固定 Z(传入或默认)和固定曝光下拍一张。
|
|
|
+ ///
|
|
|
+ /// 用法:WellSpacing.exe [COM口=COM11] [相机index=2] [曝光=80] [Z=93000]
|
|
|
+ /// 存图目录:calib_result\well_check_yyyyMMdd_HHmmss\
|
|
|
+ /// </summary>
|
|
|
+ internal class WellSpacing
|
|
|
+ {
|
|
|
+ [STAThread]
|
|
|
+ static int Main(string[] args)
|
|
|
+ {
|
|
|
+ Console.OutputEncoding = System.Text.Encoding.UTF8;
|
|
|
+ string port = args.Length > 0 ? args[0] : "COM11";
|
|
|
+ int camIdx = args.Length > 1 && int.TryParse(args[1], out int ci) ? ci : 2;
|
|
|
+ int exposure = args.Length > 2 && int.TryParse(args[2], out int ex) ? ex : 80;
|
|
|
+ int zPos = args.Length > 3 && int.TryParse(args[3], out int z) ? z : 93000;
|
|
|
+
|
|
|
+ int camW = 2592, camH = 1944;
|
|
|
+ void L(string m) => Console.WriteLine($"{DateTime.Now:HH:mm:ss} {m}");
|
|
|
+
|
|
|
+ string stamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
|
+ string outDir = $@"C:\claudeFile\TL\AutoFocusTool\calib_result\well_check_{stamp}";
|
|
|
+ Directory.CreateDirectory(outDir);
|
|
|
+
|
|
|
+ L($"========== well 实拍检测 {port} cam#{camIdx} 曝光{exposure} Z{zPos} ==========");
|
|
|
+ L($"存图目录: {outDir}");
|
|
|
+
|
|
|
+ var motor = new HouseMotor(port) { Log = null };
|
|
|
+ if (!motor.Open()) { L($"✗ 打开 {port} 失败(串口被占用?)"); return 1; }
|
|
|
+ motor.MotorDelayMs = 1500;
|
|
|
+ int sn = motor.ShakeHands();
|
|
|
+ L($"握手 houseSn = {sn}");
|
|
|
+
|
|
|
+ var cam = new SerialCamera(camIdx, camW, camH, exposure);
|
|
|
+ int init = cam.Init();
|
|
|
+ if (init != 0) { L($"✗ 相机#{camIdx} 初始化失败 code={init}"); motor.Close(); return 2; }
|
|
|
+ cam.SetOpMode(0);
|
|
|
+ cam.SetExposure(exposure);
|
|
|
+ motor.OpenLED();
|
|
|
+ Thread.Sleep(300);
|
|
|
+
|
|
|
+ // 先到统一对焦 Z(所有 well 用同一 Z,只看水平是否对到不同孔)
|
|
|
+ motor.VerticalMoveTo(zPos, 1500);
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ for (int w = 1; w <= 16; w++)
|
|
|
+ {
|
|
|
+ int hpos = motor.ReadWellHorizontalPos(w);
|
|
|
+ if (hpos < 0) { L($"well{w,2}: 读EEPROM位置失败,跳过"); continue; }
|
|
|
+
|
|
|
+ motor.HorizontalMoveTo(hpos, 1500);
|
|
|
+ Thread.Sleep(200);
|
|
|
+ cam.GrabRgb(); // 丢弃第一帧
|
|
|
+ Thread.Sleep(100);
|
|
|
+ cam.GrabRgb();
|
|
|
+ byte[] buf = cam.GetSourceBuffer();
|
|
|
+
|
|
|
+ var c = WellDetector.Detect(buf, camW, camH);
|
|
|
+ string tag = c.Found
|
|
|
+ ? $"圆心({c.Cx:F0},{c.Cy:F0}) R={c.Radius:F0} 偏移X={c.OffsetXPct:F1}% Y={c.OffsetYPct:F1}% 完整={c.Complete}"
|
|
|
+ : $"未检出圆[{c.RejectReason}]";
|
|
|
+ L($"well{w,2}: EEPROM水平={hpos,7} {tag}");
|
|
|
+
|
|
|
+ string detTag = c.Found ? (c.Complete ? "OK" : "partial") : "NOCIRCLE";
|
|
|
+ string path = Path.Combine(outDir, $"well{w:D2}_hp{hpos}_{detTag}.bmp");
|
|
|
+ ImageConverter.SaveBmp(buf, camW, camH, path);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ motor.CloseLED();
|
|
|
+ cam.Dispose();
|
|
|
+ motor.Close();
|
|
|
+ }
|
|
|
+
|
|
|
+ L($"========== 完成,图片在: {outDir} ==========");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|