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

117 lines
3.9 KiB
Python

"""分歧裁决:对争议候选位置做细粒度尺度扫描 + 分通道 NCC。
用法:.venv/bin/python try/adjudicate.py
对 41ac 的 A2/C 两候选、444d 的共识候选、以及基线对 4db9/d7a7 做细化评估,
输出每候选的最优尺度、NCC 与分通道 NCC,判断真目标。
"""
from pathlib import Path
import sys
import cv2
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parent))
from method_c_rotation import load_mark, template_at, patch_ncc # noqa: E402
ROOT = Path(__file__).resolve().parent.parent
CAP = ROOT / "captchas"
CASES = {
# 12位前缀: [(标签, 中心, 初始尺度), ...]
"41ac8781f7e9": [
("C ", (218.0, 223.0), 0.40),
("A2 ", (110.0, 216.0), 0.50),
("A2#2", (363.0, 214.5), 0.50),
("A2#3", (270.0, 223.5), 0.50),
],
"444d0ea7c8bb": [("共识", (232.5, 202.5), 1.10)],
"4db94793ec6d": [("基线", (351.0, 197.0), 1.05)],
"d7a7f80c4554": [("基线", (222.8, 201.4), 0.95)],
}
SCALES_FINE = np.arange(-0.12, 0.121, 0.02)
def find_files(prefix):
"""按 12 位前缀 glob 完整文件名。"""
bigs = [p for p in CAP.glob(f"{prefix}*.jpeg") if "-mark" not in p.stem]
if not bigs:
return None, None
return bigs[0], CAP / f"{bigs[0].stem}-mark.png"
def channel_ncc(big_f, center, tpl, mask):
"""分通道 NCC(模板与 patch 中心对齐)。失败返回 None。"""
try:
h, w = tpl.shape[:2]
x0 = int(round(center[0] - w / 2.0))
y0 = int(round(center[1] - h / 2.0))
if x0 < 0 or y0 < 0 or x0 + w > big_f.shape[1] or y0 + h > big_f.shape[0]:
return None
patch = big_f[y0:y0 + h, x0:x0 + w]
vals = []
for c in range(3):
res = cv2.matchTemplate(patch[..., c:c + 1].copy(), tpl[..., c:c + 1].copy(),
cv2.TM_CCOEFF_NORMED, mask=mask[..., None].copy())
v = float(np.nan_to_num(res[0, 0], nan=-1.0))
vals.append(v)
return vals
except cv2.error as e:
print(f" 分通道失败: {e}")
return None
def refine(big_f, bgr, alpha, center, scale0):
"""细粒度尺度扫描,返回 (best_ncc, best_scale, 分通道NCC)。"""
try:
best_ncc = -1.0
best_scale = scale0
best_ch = None
for ds in SCALES_FINE:
s = float(scale0 + ds)
tpl, mask, _, _ = template_at(bgr, alpha, s, 0.0)
if tpl is None:
continue
ncc = patch_ncc(big_f, center[0], center[1], tpl, mask)
if ncc > best_ncc:
ch = channel_ncc(big_f, center, tpl, mask)
best_ncc = ncc
best_scale = s
best_ch = ch
return best_ncc, best_scale, best_ch
except Exception as e:
print(f" 细化失败: {e}")
return (-1.0, scale0, None)
def process_case(prefix, cands):
try:
big_path, mark_path = find_files(prefix)
if big_path is None:
print(f"{prefix} 样本缺失")
return
big = cv2.imread(str(big_path), cv2.IMREAD_COLOR)
if big is None:
print(f"{prefix} 大图读取失败")
return
big_f = big.astype(np.float32)
bgr, alpha = load_mark(mark_path)
if bgr is None or alpha is None:
print(f"{prefix} mark 读取失败")
return
print(f"== {prefix}")
for label, center, scale0 in cands:
ncc, s, ch = refine(big_f, bgr, alpha, center, scale0)
chs = f"BGR={ch[0]:.2f}/{ch[1]:.2f}/{ch[2]:.2f}" if ch else "N/A"
print(f" {label} center=({center[0]:.0f},{center[1]:.0f}) "
f"scale*={s:.2f} ncc={ncc:.3f} {chs}")
except Exception as e:
print(f"{prefix} 处理失败: {e}")
def main():
for prefix, cands in CASES.items():
process_case(prefix, cands)
if __name__ == "__main__":
main()