- src/: 最终交付物(solve.py base64 API、method_l_shape.py 核心算法、verify_result.py 验证工具) - docs/: 方案文档与实验演进记录 - try/: 历史实验脚本(A~K 方法) - 10/10 样本求解成功,3 个独立真值锚点偏差 <=4px
149 lines
5.1 KiB
Python
149 lines
5.1 KiB
Python
"""三方法交叉对比可视化。
|
||
|
||
读取 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()
|