using System; using System.IO; using System.Linq; using AutoFocusTool.Imaging; namespace AutoFocusTool { /// /// 离线验证标定算法:读已存的BMP → WellDetector找圆心 → ExposureMeter评曝光。 /// 不连机器,快速验证算法对真实图是否有效。 /// 用法:CalibTest.exe [目录或文件glob] /// internal class CalibTest { static void Main(string[] args) { Console.OutputEncoding = System.Text.Encoding.UTF8; string dir = args.Length > 0 ? args[0] : @"C:\claudeFile\TL\AutoFocusTool\survey"; string pat = args.Length > 1 ? args[1] : "h9_w2_*.bmp"; var files = Directory.GetFiles(dir, pat).OrderBy(f => f).ToList(); Console.WriteLine($"== 离线标定算法验证: {files.Count} 张图 ({pat}) =="); Console.WriteLine($"{"文件",-22} {"圆心X",6} {"圆心Y",6} {"半径",6} {"偏移X%",7} {"偏移Y%",7} {"完整",5} {"曝光均值",8} {"饱和%",6} {"曝光判定",8} {"清晰度",8}"); foreach (var f in files) { var (buf, w, h) = ReadBmp24(f); if (buf == null) { Console.WriteLine($"{Path.GetFileName(f)}: 读取失败"); continue; } var circle = WellDetector.Detect(buf, w, h); var exp = ExposureMeter.Measure(buf, w, h, circle); double sharp = Sharpness.Compute(buf, w, h, circle.Found ? RoiOf(circle, w, h) : (System.Drawing.Rectangle?)null); string name = Path.GetFileNameWithoutExtension(f); if (circle.Found) Console.WriteLine($"{name,-22} {circle.Cx,6:F0} {circle.Cy,6:F0} {circle.Radius,6:F0} {circle.OffsetXPct,7:F1} {circle.OffsetYPct,7:F1} {(circle.Complete ? "是" : "否"),5} {exp.Mean,8:F0} {exp.SaturatedPct,6:F1} {exp.State,8} {sharp,8:F4}"); else Console.WriteLine($"{name,-22} 未检出well圆 曝光均值={exp.Mean:F0} 拒绝原因[{circle.RejectReason}] 面积={circle.AreaPct:F1}%"); } } // well圆内ROI(内部70%)做清晰度 static System.Drawing.Rectangle RoiOf(WellCircle c, int w, int h) { int r = (int)(c.Radius * 0.7); int x = Math.Max(0, (int)c.Cx - r), y = Math.Max(0, (int)c.Cy - r); int ww = Math.Min(2 * r, w - x), hh = Math.Min(2 * r, h - y); return new System.Drawing.Rectangle(x, y, ww, hh); } /// 读24bpp BMP → (BGR字节[自顶向下], 宽, 高)。我们存图时h>0自底向上,这里翻回自顶向下。 static (byte[], int, int) ReadBmp24(string path) { try { byte[] all = File.ReadAllBytes(path); if (all.Length < 54 || all[0] != 'B' || all[1] != 'M') return (null, 0, 0); int off = BitConverter.ToInt32(all, 10); int w = BitConverter.ToInt32(all, 18); int h = BitConverter.ToInt32(all, 22); short bpp = BitConverter.ToInt16(all, 28); if (bpp != 24) return (null, 0, 0); bool bottomUp = h > 0; h = Math.Abs(h); int rowSize = ((w * 3 + 3) / 4) * 4; var buf = new byte[w * h * 3]; for (int y = 0; y < h; y++) { int srcRow = off + (bottomUp ? (h - 1 - y) : y) * rowSize; Buffer.BlockCopy(all, srcRow, buf, y * w * 3, w * 3); } return (buf, w, h); } catch { return (null, 0, 0); } } } }