ToPng.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. using System;
  2. using System.IO;
  3. using System.Drawing;
  4. using System.Drawing.Imaging;
  5. using System.Runtime.InteropServices;
  6. using AutoFocusTool.Imaging;
  7. namespace AutoFocusTool
  8. {
  9. /// <summary>
  10. /// BMP→小PNG 诊断转换器(不依赖相机/串口,纯 System.Drawing)。
  11. /// 把相机 15MB 的 24bpp BMP 缩成小 PNG 供肉眼/AI 视觉检查,并:
  12. /// - 跑 WellDetector 把"检测器眼里的圆"画上去(绿圆+圆心十字)
  13. /// - 画画面中心十字(白)+ 居中容差带(±12% 高度,黄色虚拟区,用上下两条线表示)
  14. /// - stdout 打印检测数值(圆心/半径/偏移/完整/拒绝原因)
  15. /// 用法:ToPng.exe <输入.bmp 或 目录> [缩放宽度=900] [--nodetect]
  16. /// 注意:磁盘上的 BMP 已被 FlipY 翻正(人眼朝向),WellDetector 在此朝向上跑,
  17. /// 得到的圆位置即人眼所见;Y偏移符号与引擎内部(未翻转)相反,但位置/半径一致。
  18. /// </summary>
  19. internal class ToPng
  20. {
  21. static void Main(string[] args)
  22. {
  23. Console.OutputEncoding = System.Text.Encoding.UTF8;
  24. if (args.Length == 0) { Console.WriteLine("用法: ToPng <bmp或目录> [宽度=900] [--nodetect]"); return; }
  25. string input = args[0];
  26. int targetW = 900;
  27. bool detect = true;
  28. for (int i = 1; i < args.Length; i++)
  29. {
  30. if (args[i] == "--nodetect") detect = false;
  31. else if (int.TryParse(args[i], out int w)) targetW = w;
  32. }
  33. if (Directory.Exists(input))
  34. {
  35. foreach (var f in Directory.GetFiles(input, "*.bmp"))
  36. Convert(f, targetW, detect);
  37. }
  38. else if (File.Exists(input))
  39. {
  40. Convert(input, targetW, detect);
  41. }
  42. else Console.WriteLine($"找不到: {input}");
  43. }
  44. static void Convert(string bmpPath, int targetW, bool detect)
  45. {
  46. try
  47. {
  48. using var src = new Bitmap(bmpPath);
  49. int W = src.Width, H = src.Height;
  50. string outPath = Path.ChangeExtension(bmpPath, ".png");
  51. if (bmpPath.EndsWith("_v.png", StringComparison.OrdinalIgnoreCase)) return; // 跳过已生成的
  52. // 取 BGR24 原始字节供 WellDetector
  53. WellCircle c = null;
  54. if (detect)
  55. {
  56. byte[] bgr = ToBgr24(src, W, H);
  57. c = WellDetector.Detect(bgr, W, H);
  58. Console.WriteLine($"{Path.GetFileName(bmpPath)}: " +
  59. (c.Found
  60. ? $"圆心({c.Cx:F0},{c.Cy:F0}) R={c.Radius:F0} 偏移X={c.OffsetXPct:F1}% Y={c.OffsetYPct:F1}% 完整={c.Complete} 面积={c.AreaPct:F1}% aspect={c.Aspect:F2} boxFill={c.BoxFill:F2} rConsist={c.RConsist:F2}"
  61. : $"未检出圆 [{c.RejectReason}] 面积={c.AreaPct:F1}%"));
  62. }
  63. // 叠加标注(在全分辨率上画,再缩放,线条清晰)
  64. using (var g = Graphics.FromImage(src))
  65. {
  66. // 画面中心十字(白)
  67. var cxp = W / 2f; var cyp = H / 2f;
  68. using var wp = new Pen(Color.White, 3);
  69. g.DrawLine(wp, cxp - 40, cyp, cxp + 40, cyp);
  70. g.DrawLine(wp, cxp, cyp - 40, cxp, cyp + 40);
  71. // 居中容差带(±12% 高度)黄色横线
  72. using var yp = new Pen(Color.Yellow, 2) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dash };
  73. float tol = 0.12f * H;
  74. g.DrawLine(yp, 0, cyp - tol, W, cyp - tol);
  75. g.DrawLine(yp, 0, cyp + tol, W, cyp + tol);
  76. if (c != null && c.Found)
  77. {
  78. using var gp = new Pen(Color.Lime, 4);
  79. g.DrawEllipse(gp, (float)(c.Cx - c.Radius), (float)(c.Cy - c.Radius),
  80. (float)(c.Radius * 2), (float)(c.Radius * 2));
  81. // 检测圆心(红十字)
  82. using var rp = new Pen(Color.Red, 3);
  83. g.DrawLine(rp, (float)c.Cx - 30, (float)c.Cy, (float)c.Cx + 30, (float)c.Cy);
  84. g.DrawLine(rp, (float)c.Cx, (float)c.Cy - 30, (float)c.Cx, (float)c.Cy + 30);
  85. }
  86. }
  87. int targetH = (int)((long)targetW * H / W);
  88. using var dst = new Bitmap(targetW, targetH);
  89. using (var g = Graphics.FromImage(dst))
  90. {
  91. g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
  92. g.DrawImage(src, 0, 0, targetW, targetH);
  93. }
  94. dst.Save(outPath, ImageFormat.Png);
  95. Console.WriteLine($" → {Path.GetFileName(outPath)} ({targetW}x{targetH})");
  96. }
  97. catch (Exception ex) { Console.WriteLine($"{bmpPath} 转换失败: {ex.Message}"); }
  98. }
  99. /// <summary>System.Drawing.Bitmap → 24bpp BGR 行优先字节(WellDetector 输入格式)。</summary>
  100. static byte[] ToBgr24(Bitmap bmp, int W, int H)
  101. {
  102. var clone = bmp.PixelFormat == PixelFormat.Format24bppRgb ? bmp
  103. : bmp.Clone(new Rectangle(0, 0, W, H), PixelFormat.Format24bppRgb);
  104. var data = clone.LockBits(new Rectangle(0, 0, W, H), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
  105. byte[] buf = new byte[W * H * 3];
  106. int rowBytes = W * 3;
  107. for (int y = 0; y < H; y++)
  108. Marshal.Copy(data.Scan0 + y * data.Stride, buf, y * rowBytes, rowBytes);
  109. clone.UnlockBits(data);
  110. if (!ReferenceEquals(clone, bmp)) clone.Dispose();
  111. return buf;
  112. }
  113. }
  114. }