CalibTest.cs 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using AutoFocusTool.Imaging;
  5. namespace AutoFocusTool
  6. {
  7. /// <summary>
  8. /// 离线验证标定算法:读已存的BMP → WellDetector找圆心 → ExposureMeter评曝光。
  9. /// 不连机器,快速验证算法对真实图是否有效。
  10. /// 用法:CalibTest.exe [目录或文件glob]
  11. /// </summary>
  12. internal class CalibTest
  13. {
  14. static void Main(string[] args)
  15. {
  16. Console.OutputEncoding = System.Text.Encoding.UTF8;
  17. string dir = args.Length > 0 ? args[0] : @"C:\claudeFile\TL\AutoFocusTool\survey";
  18. string pat = args.Length > 1 ? args[1] : "h9_w2_*.bmp";
  19. var files = Directory.GetFiles(dir, pat).OrderBy(f => f).ToList();
  20. Console.WriteLine($"== 离线标定算法验证: {files.Count} 张图 ({pat}) ==");
  21. Console.WriteLine($"{"文件",-22} {"圆心X",6} {"圆心Y",6} {"半径",6} {"偏移X%",7} {"偏移Y%",7} {"完整",5} {"曝光均值",8} {"饱和%",6} {"曝光判定",8} {"清晰度",8}");
  22. foreach (var f in files)
  23. {
  24. var (buf, w, h) = ReadBmp24(f);
  25. if (buf == null) { Console.WriteLine($"{Path.GetFileName(f)}: 读取失败"); continue; }
  26. var circle = WellDetector.Detect(buf, w, h);
  27. var exp = ExposureMeter.Measure(buf, w, h, circle);
  28. double sharp = Sharpness.Compute(buf, w, h,
  29. circle.Found ? RoiOf(circle, w, h) : (System.Drawing.Rectangle?)null);
  30. string name = Path.GetFileNameWithoutExtension(f);
  31. if (circle.Found)
  32. 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}");
  33. else
  34. Console.WriteLine($"{name,-22} 未检出well圆 曝光均值={exp.Mean:F0} 拒绝原因[{circle.RejectReason}] 面积={circle.AreaPct:F1}%");
  35. }
  36. }
  37. // well圆内ROI(内部70%)做清晰度
  38. static System.Drawing.Rectangle RoiOf(WellCircle c, int w, int h)
  39. {
  40. int r = (int)(c.Radius * 0.7);
  41. int x = Math.Max(0, (int)c.Cx - r), y = Math.Max(0, (int)c.Cy - r);
  42. int ww = Math.Min(2 * r, w - x), hh = Math.Min(2 * r, h - y);
  43. return new System.Drawing.Rectangle(x, y, ww, hh);
  44. }
  45. /// <summary>读24bpp BMP → (BGR字节[自顶向下], 宽, 高)。我们存图时h>0自底向上,这里翻回自顶向下。</summary>
  46. static (byte[], int, int) ReadBmp24(string path)
  47. {
  48. try
  49. {
  50. byte[] all = File.ReadAllBytes(path);
  51. if (all.Length < 54 || all[0] != 'B' || all[1] != 'M') return (null, 0, 0);
  52. int off = BitConverter.ToInt32(all, 10);
  53. int w = BitConverter.ToInt32(all, 18);
  54. int h = BitConverter.ToInt32(all, 22);
  55. short bpp = BitConverter.ToInt16(all, 28);
  56. if (bpp != 24) return (null, 0, 0);
  57. bool bottomUp = h > 0; h = Math.Abs(h);
  58. int rowSize = ((w * 3 + 3) / 4) * 4;
  59. var buf = new byte[w * h * 3];
  60. for (int y = 0; y < h; y++)
  61. {
  62. int srcRow = off + (bottomUp ? (h - 1 - y) : y) * rowSize;
  63. Buffer.BlockCopy(all, srcRow, buf, y * w * 3, w * 3);
  64. }
  65. return (buf, w, h);
  66. }
  67. catch { return (null, 0, 0); }
  68. }
  69. }
  70. }