- 合并 matcher_core + solve 为自包含单文件 captcha_solver.py, 对外仅暴露 solve_slide()(base64 进、JSON 出),内部实现细节不外泄 - 新增 README_3rd.md:接入步骤、返回字段、像素换算、鼠标操作规范、 轨迹形态要求、踩坑清单与验收基线 - 删除内部 CLI 批量工具与调试字段(candidates/method), 返回清洗后的 quality/alternatives - try/live_solve.py 改用新模块路径;docs 同步更新引用
70 lines
2.4 KiB
Python
70 lines
2.4 KiB
Python
"""线上验证码求解(带服务端 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 <big.jpeg> <mark.png> <tip_y>
|
||
输出 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}')
|