Files
杨豪 d15b3c5b43 feat: 拖动重叠验证码离线求解(方向感知倒角+alpha轮廓+旋转扫描)
- src/: 最终交付物(solve.py base64 API、method_l_shape.py 核心算法、verify_result.py 验证工具)
- docs/: 方案文档与实验演进记录
- try/: 历史实验脚本(A~K 方法)
- 10/10 样本求解成功,3 个独立真值锚点偏差 <=4px
2026-09-07 20:13:22 +08:00

149 lines
5.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""三方法交叉对比可视化。
读取 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()