"""线上验证码求解(带服务端 tip_y 约束)。 服务端 captcha/get 响应中的 tip_y × 2 = 缺口 y 坐标(mark 画布顶)。 经 10 组线上样本校准:solver 无约束 y 与 2×tip_y 差 ≤4px(定位准的样本), 偏差大的样本(AMBIGUOUS)正是被服务端判 VerifyErr 的。 用 tip_y 约束 y 后在 score map 上重选 x,可显著提高准确率。 用法: .venv/bin/python try/live_solve.py 输出 JSON: x(拖动目标, 原图px)、score、scale """ import json import sys from pathlib import Path import cv2 sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src")) import captcha_solver as L # noqa: E402 def live_solve(big_path, mark_path, tip_y, y_tol=8): """tip_y 约束版求解。返回 {x, y, scale, score} 或 None。""" try: target_y = int(tip_y) * 2 except (TypeError, ValueError): return None big = cv2.imread(big_path) mark = cv2.imread(mark_path, cv2.IMREAD_UNCHANGED) if big is None or mark is None: return None try: d = L.setup(big, mark) if d is None: return None best = [] for s in L.SCALES: s = round(float(s), 3) score, w, h, ptset = L.score_at_scale(d, s) if score is None: continue for v, x, y in L.nms_take(score, 30, int(0.45 * max(w, h))): if abs(y - target_y) <= y_tol: best.append((v, s, x, y, w, h)) except (ValueError, IndexError, TypeError) as e: print(f"匹配失败: {e}", file=sys.stderr) return None if not best: # 放宽容差重试 return live_solve(big_path, mark_path, tip_y, y_tol=y_tol * 2) best.sort(key=lambda c: c[0]) try: v, s, x, y, w, h = best[0] return {"x": x, "y": y, "scale": s, "score": round(float(v), 2), "w": w, "h": h} except (IndexError, TypeError, ValueError) as e: print(f"候选解析失败: {e}", file=sys.stderr) return None if __name__ == "__main__": if len(sys.argv) < 4: print(__doc__) sys.exit(1) try: result = live_solve(sys.argv[1], sys.argv[2], int(sys.argv[3])) except (OSError, ValueError) as e: result = None print(f"求解失败: {e}", file=sys.stderr) print(json.dumps(result) if result else '{"ok": false}')