"""三方法交叉对比可视化。 读取 A2/C 的 summary.txt,把各自的候选框画到同一张大图上: - 红=C 的目标框(scale+offset) - 黄=A2 的 #1 峰框 分歧对(两框中心距离 > 30px)额外输出两倍放大局部图。 输出 → try/out/compare/ """ from pathlib import Path import math import re import cv2 import numpy as np ROOT = Path(__file__).resolve().parent.parent CAP = ROOT / "captchas" A2 = ROOT / "try" / "out" / "A2" / "summary.txt" CS = ROOT / "try" / "out" / "C" / "summary.txt" OUT = ROOT / "try" / "out" / "compare" OUT.mkdir(parents=True, exist_ok=True) def parse_a2(text): """name -> (scale, (x, y)),取 #1 峰。""" out = {} try: for line in text.splitlines(): m = re.match(r"^(\S+)\t.*?尺度=([\d.]+)\t.*?#1:([\d.]+)@\((\d+), (\d+)\)", line) if m: s = float(m.group(2)) x = int(m.group(4)) y = int(m.group(5)) out[m.group(1)] = (s, (x, y)) except Exception as e: print(f" A2 summary 解析失败: {e}") return out def parse_c(text): """name -> (scale, (x, y)),取目标行。""" out = {} try: for line in text.splitlines(): m = re.match(r"^(\S+)\t目标\tscale=([\d.]+)\t.*?offset=\((\d+),(\d+)\)", line) if m: s = float(m.group(2)) x = int(m.group(3)) y = int(m.group(4)) out[m.group(1)] = (s, (x, y)) except Exception as e: print(f" C summary 解析失败: {e}") return out def box_center(scale, xy): """模板框中心坐标。""" try: size = 110.0 * scale return (xy[0] + size / 2.0, xy[1] + size / 2.0) except Exception as e: print(f" 中心计算失败: {e}") return (0.0, 0.0) def zoom_crop(big, center, size_px, factor=2.0): """以 center 为中心裁 2.2*size 区域并放大。""" try: half = int(size_px * 1.1) x0 = max(0, int(center[0] - half)) y0 = max(0, int(center[1] - half)) x1 = min(big.shape[1], int(center[0] + half)) y1 = min(big.shape[0], int(center[1] + half)) crop = big[y0:y1, x0:x1].copy() return cv2.resize(crop, None, fx=factor, fy=factor, interpolation=cv2.INTER_CUBIC) except cv2.error as e: print(f" 裁剪失败: {e}") return None def draw_box(vis, xy, size, color, label, ly): """画框+标签,参数已验证。""" try: x1 = int(xy[0] + size) y1 = int(xy[1] + size) cv2.rectangle(vis, xy, (x1, y1), color, 2) cv2.putText(vis, label, (xy[0], max(12, ly)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) except cv2.error as e: print(f" 绘制失败: {e}") def process_pair(name, s_c, xy_c, s_a, xy_a): try: big = cv2.imread(str(CAP / f"{name}.jpeg"), cv2.IMREAD_COLOR) if big is None: # summary 里的名字是 12 位截断,匹配完整文件名 full = [p.stem for p in CAP.glob(f"{name}*.jpeg") if "-mark" not in p.stem] if not full: print(f"{name[:12]} 大图缺失") return big = cv2.imread(str(CAP / f"{full[0]}.jpeg"), cv2.IMREAD_COLOR) if big is None: print(f"{name[:12]} 大图读取失败") return cc = box_center(s_c, xy_c) ca = box_center(s_a, xy_a) dist = math.hypot(cc[0] - ca[0], cc[1] - ca[1]) vis = big.copy() draw_box(vis, xy_c, 110.0 * s_c, (0, 0, 255), "C", xy_c[1] - 4) draw_box(vis, xy_a, 110.0 * s_a, (0, 255, 255), "A2", xy_a[1] + 14) cv2.imwrite(str(OUT / f"{name[:12]}-cmp.png"), vis) if dist <= 30: print(f"{name[:12]} 一致 中心距={dist:5.1f}px C@({cc[0]:.0f},{cc[1]:.0f}) s={s_c:.2f}") else: print(f"{name[:12]} 分歧 中心距={dist:5.1f}px C@({cc[0]:.0f},{cc[1]:.0f}) s={s_c:.2f}" f" A2@({ca[0]:.0f},{ca[1]:.0f}) s={s_a:.2f}") img_c = zoom_crop(big, cc, 110.0 * s_c) img_a = zoom_crop(big, ca, 110.0 * s_a) if img_c is not None: cv2.imwrite(str(OUT / f"{name[:12]}-cmp-C.png"), img_c) if img_a is not None: cv2.imwrite(str(OUT / f"{name[:12]}-cmp-A2.png"), img_a) except Exception as e: print(f"{name[:12]} 对比失败: {e}") def main(): try: a2 = parse_a2(A2.read_text(encoding="utf-8")) c = parse_c(CS.read_text(encoding="utf-8")) agree, disagree = 0, 0 for name in sorted(set(a2) & set(c)): s_c, xy_c = c[name] s_a, xy_a = a2[name] cc = box_center(s_c, xy_c) ca = box_center(s_a, xy_a) if math.hypot(cc[0] - ca[0], cc[1] - ca[1]) <= 30: agree += 1 else: disagree += 1 process_pair(name, s_c, xy_c, s_a, xy_a) print(f"\n一致 {agree} / 分歧 {disagree};图已写入 {OUT}/") except Exception as e: print(f" 对比失败: {e}") if __name__ == "__main__": main()