Quellcode durchsuchen

feat(diag): WellSpacing加Z清晰度曲线模式(记录分数+亮度均值)+输出改TestData

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie vor 1 Woche
Ursprung
Commit
4088563bbe
2 geänderte Dateien mit 83 neuen und 1 gelöschten Zeilen
  1. 82 1
      WellSpacing/WellSpacing.cs
  2. 1 0
      WellSpacing/WellSpacing.csproj

+ 82 - 1
WellSpacing/WellSpacing.cs

@@ -21,6 +21,8 @@ namespace AutoFocusTool
         static int Main(string[] args)
         {
             Console.OutputEncoding = System.Text.Encoding.UTF8;
+            if (args.Length > 0 && args[0] == "zcurve")
+                return ZCurve(args);
             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;
@@ -30,7 +32,7 @@ namespace AutoFocusTool
             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}";
+            string outDir = $@"C:\claudeFile\TL\AutoFocusTool\TestData\well_check_{stamp}";
             Directory.CreateDirectory(outDir);
 
             L($"========== well 实拍检测  {port}  cam#{camIdx}  曝光{exposure}  Z{zPos} ==========");
@@ -88,5 +90,84 @@ namespace AutoFocusTool
             L($"========== 完成,图片在: {outDir} ==========");
             return 0;
         }
+
+        /// <summary>
+        /// Z清晰度曲线扫描:移到指定well的EEPROM水平位置,从zLo到zHi逐层抓帧算清晰度,
+        /// 同时记录灰度均值(验证亮度归一化是否带偏),每层存图。
+        /// 用法:WellSpacing.exe zcurve <COM> <相机idx> <well> [zLo=20000] [zHi=120000] [zStep=2000] [曝光=60]
+        /// </summary>
+        static int ZCurve(string[] a)
+        {
+            string port = a.Length > 1 ? a[1] : "COM11";
+            int camIdx = a.Length > 2 && int.TryParse(a[2], out int ci) ? ci : 2;
+            int well = a.Length > 3 && int.TryParse(a[3], out int wv) ? wv : 1;
+            int zLo = a.Length > 4 && int.TryParse(a[4], out int v4) ? v4 : 20000;
+            int zHi = a.Length > 5 && int.TryParse(a[5], out int v5) ? v5 : 120000;
+            int zStep = a.Length > 6 && int.TryParse(a[6], out int v6) ? v6 : 2000;
+            int exposure = a.Length > 7 && int.TryParse(a[7], out int v7) ? v7 : 60;
+            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\TestData\zcurve_w{well}_{stamp}";
+            Directory.CreateDirectory(outDir);
+            L($"==== Z清晰度曲线 well{well}  {port} cam#{camIdx}  Z[{zLo},{zHi}] step{zStep} 曝光{exposure} ====");
+            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}");
+            int hpos = motor.ReadWellHorizontalPos(well);
+            if (hpos < 0) { L("✗ 读well水平位置失败"); motor.Close(); return 1; }
+
+            var cam = new SerialCamera(camIdx, camW, camH, exposure);
+            if (cam.Init() != 0) { L("✗ 相机初始化失败"); motor.Close(); return 2; }
+            cam.SetOpMode(0); cam.SetExposure(exposure);
+            motor.OpenLED(); Thread.Sleep(300);
+            motor.HorizontalMoveTo(hpos, 1500);
+            L($"已移到 well{well} 水平={hpos}");
+
+            double bestScore = -1; int bestZ = zLo;
+            try
+            {
+                for (int z = zLo; z <= zHi; z += zStep)
+                {
+                    motor.VerticalMoveTo(z, 1500);
+                    Thread.Sleep(150);
+                    cam.GrabRgb(); Thread.Sleep(80); cam.GrabRgb();
+                    byte[] buf = cam.GetSourceBuffer();
+
+                    int roiW = (int)(camW * 0.4), roiH = (int)(camH * 0.4);
+                    var roi = new System.Drawing.Rectangle((camW - roiW) / 2, (camH - roiH) / 2, roiW, roiH);
+                    double score = Sharpness.Compute(buf, camW, camH, roi);
+                    double mean = GrayMean(buf, camW, camH, roi);
+                    L($"z={z,6}  归一化分={score:F5}  亮度均值={mean:F1}");
+                    if (score > bestScore) { bestScore = score; bestZ = z; }
+
+                    ImageConverter.SaveBmp(buf, camW, camH,
+                        Path.Combine(outDir, $"z{z}_s{score:F4}.bmp"));
+                }
+            }
+            finally { motor.CloseLED(); cam.Dispose(); motor.Close(); }
+
+            L($"==== 峰值 z={bestZ} 分={bestScore:F5}  图在 {outDir} ====");
+            return 0;
+        }
+
+        /// <summary>中央ROI灰度均值(BGR→灰度,行优先)。</summary>
+        static double GrayMean(byte[] bgr, int W, int H, System.Drawing.Rectangle roi)
+        {
+            long sum = 0; int n = 0; int stride = W * 3;
+            for (int y = roi.Y; y < roi.Y + roi.Height && y < H; y++)
+                for (int x = roi.X; x < roi.X + roi.Width && x < W; x++)
+                {
+                    int p = y * stride + x * 3;
+                    sum += (bgr[p] * 29 + bgr[p + 1] * 150 + bgr[p + 2] * 77) >> 8;
+                    n++;
+                }
+            return n > 0 ? (double)sum / n : 0;
+        }
     }
 }

+ 1 - 0
WellSpacing/WellSpacing.csproj

@@ -26,6 +26,7 @@
     <Compile Include="..\Camera\CapInfoStruct.cs" />
     <Compile Include="..\Camera\Camera.cs" />
     <Compile Include="..\Imaging\WellDetector.cs" />
+    <Compile Include="..\Imaging\Sharpness.cs" />
     <Compile Include="..\Imaging\ImageConverter.cs" />
     <Compile Include="WellSpacing.cs" />
   </ItemGroup>