| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- using System;
- using System.IO;
- using System.Drawing;
- using System.Drawing.Imaging;
- using System.Runtime.InteropServices;
- using AutoFocusTool.Imaging;
- namespace AutoFocusTool
- {
- /// <summary>
- /// BMP→小PNG 诊断转换器(不依赖相机/串口,纯 System.Drawing)。
- /// 把相机 15MB 的 24bpp BMP 缩成小 PNG 供肉眼/AI 视觉检查,并:
- /// - 跑 WellDetector 把"检测器眼里的圆"画上去(绿圆+圆心十字)
- /// - 画画面中心十字(白)+ 居中容差带(±12% 高度,黄色虚拟区,用上下两条线表示)
- /// - stdout 打印检测数值(圆心/半径/偏移/完整/拒绝原因)
- /// 用法:ToPng.exe <输入.bmp 或 目录> [缩放宽度=900] [--nodetect]
- /// 注意:磁盘上的 BMP 已被 FlipY 翻正(人眼朝向),WellDetector 在此朝向上跑,
- /// 得到的圆位置即人眼所见;Y偏移符号与引擎内部(未翻转)相反,但位置/半径一致。
- /// </summary>
- internal class ToPng
- {
- static void Main(string[] args)
- {
- Console.OutputEncoding = System.Text.Encoding.UTF8;
- if (args.Length == 0) { Console.WriteLine("用法: ToPng <bmp或目录> [宽度=900] [--nodetect]"); return; }
- string input = args[0];
- int targetW = 900;
- bool detect = true;
- for (int i = 1; i < args.Length; i++)
- {
- if (args[i] == "--nodetect") detect = false;
- else if (int.TryParse(args[i], out int w)) targetW = w;
- }
- if (Directory.Exists(input))
- {
- foreach (var f in Directory.GetFiles(input, "*.bmp"))
- Convert(f, targetW, detect);
- }
- else if (File.Exists(input))
- {
- Convert(input, targetW, detect);
- }
- else Console.WriteLine($"找不到: {input}");
- }
- static void Convert(string bmpPath, int targetW, bool detect)
- {
- try
- {
- using var src = new Bitmap(bmpPath);
- int W = src.Width, H = src.Height;
- string outPath = Path.ChangeExtension(bmpPath, ".png");
- if (bmpPath.EndsWith("_v.png", StringComparison.OrdinalIgnoreCase)) return; // 跳过已生成的
- // 取 BGR24 原始字节供 WellDetector
- WellCircle c = null;
- if (detect)
- {
- byte[] bgr = ToBgr24(src, W, H);
- c = WellDetector.Detect(bgr, W, H);
- Console.WriteLine($"{Path.GetFileName(bmpPath)}: " +
- (c.Found
- ? $"圆心({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}"
- : $"未检出圆 [{c.RejectReason}] 面积={c.AreaPct:F1}%"));
- }
- // 叠加标注(在全分辨率上画,再缩放,线条清晰)
- using (var g = Graphics.FromImage(src))
- {
- // 画面中心十字(白)
- var cxp = W / 2f; var cyp = H / 2f;
- using var wp = new Pen(Color.White, 3);
- g.DrawLine(wp, cxp - 40, cyp, cxp + 40, cyp);
- g.DrawLine(wp, cxp, cyp - 40, cxp, cyp + 40);
- // 居中容差带(±12% 高度)黄色横线
- using var yp = new Pen(Color.Yellow, 2) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dash };
- float tol = 0.12f * H;
- g.DrawLine(yp, 0, cyp - tol, W, cyp - tol);
- g.DrawLine(yp, 0, cyp + tol, W, cyp + tol);
- if (c != null && c.Found)
- {
- using var gp = new Pen(Color.Lime, 4);
- g.DrawEllipse(gp, (float)(c.Cx - c.Radius), (float)(c.Cy - c.Radius),
- (float)(c.Radius * 2), (float)(c.Radius * 2));
- // 检测圆心(红十字)
- using var rp = new Pen(Color.Red, 3);
- g.DrawLine(rp, (float)c.Cx - 30, (float)c.Cy, (float)c.Cx + 30, (float)c.Cy);
- g.DrawLine(rp, (float)c.Cx, (float)c.Cy - 30, (float)c.Cx, (float)c.Cy + 30);
- }
- }
- int targetH = (int)((long)targetW * H / W);
- using var dst = new Bitmap(targetW, targetH);
- using (var g = Graphics.FromImage(dst))
- {
- g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
- g.DrawImage(src, 0, 0, targetW, targetH);
- }
- dst.Save(outPath, ImageFormat.Png);
- Console.WriteLine($" → {Path.GetFileName(outPath)} ({targetW}x{targetH})");
- }
- catch (Exception ex) { Console.WriteLine($"{bmpPath} 转换失败: {ex.Message}"); }
- }
- /// <summary>System.Drawing.Bitmap → 24bpp BGR 行优先字节(WellDetector 输入格式)。</summary>
- static byte[] ToBgr24(Bitmap bmp, int W, int H)
- {
- var clone = bmp.PixelFormat == PixelFormat.Format24bppRgb ? bmp
- : bmp.Clone(new Rectangle(0, 0, W, H), PixelFormat.Format24bppRgb);
- var data = clone.LockBits(new Rectangle(0, 0, W, H), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
- byte[] buf = new byte[W * H * 3];
- int rowBytes = W * 3;
- for (int y = 0; y < H; y++)
- Marshal.Copy(data.Scan0 + y * data.Stride, buf, y * rowBytes, rowBytes);
- clone.UnlockBits(data);
- if (!ReferenceEquals(clone, bmp)) clone.Dispose();
- return buf;
- }
- }
- }
|