feat: 拖动重叠验证码离线求解(方向感知倒角+alpha轮廓+旋转扫描)
- src/: 最终交付物(solve.py base64 API、method_l_shape.py 核心算法、verify_result.py 验证工具) - docs/: 方案文档与实验演进记录 - try/: 历史实验脚本(A~K 方法) - 10/10 样本求解成功,3 个独立真值锚点偏差 <=4px
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# docs
|
||||
|
||||
项目文档索引。
|
||||
|
||||
## 文档列表
|
||||
|
||||
- [solution.md](solution.md) — 最终方案:算法原理、API 用法、结果验证方法、10 样本结果与已知边界
|
||||
- [experiments.md](experiments.md) — 实验演进记录(A→L):各方法失败原因、转折点实验、调试坑、无真值交叉验证方法
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 单样本求解(base64 或文件路径)
|
||||
.venv/bin/python src/solve.py <大图> <mark>
|
||||
|
||||
# 批量求解 + 可视化(输出 src/out/L/)
|
||||
.venv/bin/python src/method_l_shape.py
|
||||
|
||||
# 结果验证(叠加轮廓图,输出 src/out/L/verify/)
|
||||
.venv/bin/python src/verify_result.py
|
||||
```
|
||||
|
||||
## 相关目录
|
||||
|
||||
| 目录 | 用途 |
|
||||
| --- | --- |
|
||||
| `captchas/` | 原始样本(只读) |
|
||||
| `src/` | 最终交付物:求解 API、核心算法、验证工具及其输出 |
|
||||
| `try/` | 实验脚本与中间产物(历史方法 A~K) |
|
||||
@@ -0,0 +1,85 @@
|
||||
# 实验演进记录(A → L)
|
||||
|
||||
> 最终方案与使用说明见 [solution.md](solution.md)。
|
||||
> 本文记录每个方法的思路、失败原因与沉淀的经验教训,
|
||||
> 原始实验脚本在 `try/`,输出在 `try/out/<方法字母>/`。
|
||||
|
||||
## 方法总览
|
||||
|
||||
| 方法 | 脚本 | 思路 | 结论 |
|
||||
| --- | --- | --- | --- |
|
||||
| A | method_a_template.py | 蒙版 CCORR 多尺度模板匹配 | 废弃 |
|
||||
| A2 | method_a2_ccoeff.py | 蒙版 CCOEFF + 峰显著度 + NMS | 基线可用,但锁纹理 |
|
||||
| B | method_b_sift.py | SIFT + RANSAC 迭代聚类 | 仅 2 样本可用,留作真值锚点 |
|
||||
| C | method_c_rotation.py | NCC 粗扫 + 旋转响应二次评分 | 第一阶段候选即错 |
|
||||
| D | method_d_shape.py | Chamfer 轮廓距离 | 小尺度纹理饱和 |
|
||||
| E | method_e_shape_coverage.py | 轮廓边缘覆盖率 | 同上 |
|
||||
| F | method_f_contour.py | Canny 轮廓 + matchShapes | 背景轮廓合并成超大块 |
|
||||
| G | method_g_component.py | 固定暗度阈值连通域 | 阈值敏感 |
|
||||
| H | method_h_silhouette.py | alpha 轮廓距离 + 局部对比度 | 分数归一化饱和 |
|
||||
| I | method_i_adaptive_components.py | 自适应暗目标连通域 | 误检照片内容 |
|
||||
| J | method_j_objects.py | 低饱和度暗色物体分割 | 同上 |
|
||||
| K | method_k_objectness.py | 暗色覆盖物概率图 + 掩码 CCORR | 掩码 CCORR 边界产生 inf |
|
||||
| **L** | `src/method_l_shape.py` | **方向感知倒角 + 洞内部特征 + 旋转扫描** | **最终方案,10/10** |
|
||||
|
||||
## 逐方法教训
|
||||
|
||||
### A / A2:RGB 模板匹配(不减小均值的代价)
|
||||
|
||||
- `TM_CCORR_NORMED` 不做均值中心化,亮背景上处处高分;
|
||||
- 跨尺度直接比原始分数偏向小模板 → A2 改用峰显著度 `(max-median)/std`;
|
||||
- A2 在 8/10 样本上候选位置合理,但 41ac 出现多峰分歧——
|
||||
后来证明这些 0.7 的高分全是**小尺度纹理巧合**,方向本身是错的。
|
||||
|
||||
### B:SIFT(意外价值)
|
||||
|
||||
- 平面图形缺少纹理,大部分样本匹配不足;
|
||||
- 但在纹理丰富的 4db9、d7a7 上给出亚像素级真值
|
||||
(scale≈1.00、rot≈0°),成为后续所有方法的**交叉验证锚点**。
|
||||
|
||||
### C:旋转感知 NCC(两次评分救不了错误的候选)
|
||||
|
||||
- 旋转响应曲线本身有效(真目标 rot≈0),但候选来自 NCC 粗扫,
|
||||
粗扫错了旋转阶段无法挽回。
|
||||
|
||||
### D/E:倒角与覆盖率(尺度公平性问题)
|
||||
|
||||
- 覆盖率在小尺度上饱和到 1.0(背景纹理到处有边);
|
||||
- 倒角均值在密纹理区反而更低 → 必须限制尺度范围并加方向约束。
|
||||
|
||||
### F/G/H/I/J/K:分割路线的共同困境
|
||||
|
||||
- Canny 轮廓易把地面/山脉合并成超大块(F);
|
||||
- 固定/自适应暗度阈值对每张照片都不同(G/I/J);
|
||||
- K 的教训:`TM_CCORR_NORMED` 带 mask 在边界产生 inf 值,
|
||||
需要手写 filter2D 掩码 NCC(后来用于决定性实验)。
|
||||
|
||||
## 转折点实验(值得复用的诊断手段)
|
||||
|
||||
1. **掩码 NCC 全图扫描**(手写 filter2D 实现,无 inf 问题):
|
||||
对 41ac/444d 在尺度 0.9–1.1 全图扫描,最高分仅 0.56/0.46
|
||||
→ 证明 mark RGB 与洞内容无像素关系,RGB 路线全部判死。
|
||||
2. **闭合轮廓 + matchShapes**(444d 上):
|
||||
Canny 闭合轮廓直接命中两个洞,Hu≈0.003,左洞 rot=-0.6°、右洞 -15.5°
|
||||
→ 证明"轮廓 + 尺度 + 旋转"路线成立,且给出 444d 独立真值。
|
||||
3. **三特征分离度测量**:
|
||||
洞:内部边缘密度 ≤0.1、亮度差 -40~-104;
|
||||
草地误检:密度 0.36、亮度差 +6 → 直接给出 L 的特征权重依据。
|
||||
|
||||
## 调试中踩过的具体坑
|
||||
|
||||
- `np.flatnonzero` 对 2D 数组返回一维扁平索引,取坐标应用 `np.argwhere`;
|
||||
- `filter2D` 输出是全尺寸,与手写滑窗比对时要裁剪有效相关区
|
||||
`[h//2 : h//2+H-h+1, w//2 : w//2+W-w+1]`;
|
||||
- 方向桶不能越界重叠:角度已在 [0,180),9 桶直接线性划分即可,
|
||||
最后一桶再做 wrap 会导致点被双计(表现为均值超上限);
|
||||
- `np.arange` 有浮点累积误差,参与区间比较前先 `round(s, 3)`;
|
||||
- Canny 阈值过低(如 26/77)会让 JPEG 噪声淹没"内部平坦"特征。
|
||||
|
||||
## 无 ground truth 时的交叉验证方法
|
||||
|
||||
1. 找至少一个可用独立方法的样本建立锚点(如 SIFT 能用的纹理丰富样本);
|
||||
2. 用结构性证据补充锚点:候选 bbox 尺寸与 mark alpha bbox 完全一致
|
||||
(41ac:88×106 vs 88×106);
|
||||
3. 全部候选可视化后人工目检:真目标应落在几何图形上而非纹理上;
|
||||
4. 结果叠加轮廓回贴大图(`src/verify_result.py`),观察贴合精度。
|
||||
@@ -0,0 +1,133 @@
|
||||
# 拖动重叠验证码:最终方案
|
||||
|
||||
> 历史实验过程与方法淘汰记录见 [experiments.md](experiments.md);
|
||||
> 实验原始日志在 `try/findings.md`。
|
||||
|
||||
## 任务定义
|
||||
|
||||
给定「拖动重叠」验证码的背景大图(552×344)与 mark 小图(110×110 RGBA),
|
||||
离线求出 mark 应拖动重叠的目标区域偏移。
|
||||
|
||||
背景中存在 ≥2 个与 mark 同形的区域(缩放/旋转生成的干扰项),
|
||||
只有**无缩放、无旋转**(尺度≈1.0、旋转≈0°)的实例是目标。
|
||||
|
||||
## 核心发现(决定技术路线)
|
||||
|
||||
1. **mark 的 RGB 是"捐赠补丁",不是洞内内容。**
|
||||
决定性实验:真掩码 NCC 全图任意位置最高仅 0.562(41ac)/ 0.464(444d)。
|
||||
若"洞 = 该处照片变暗、mark = 洞下原图"成立,真目标处 NCC 应接近 1.0
|
||||
(NCC 对仿射亮度变换不变)。因此**一切基于 RGB/灰度的模板匹配在原理上不可行**。
|
||||
2. **洞是叠加在照片上的实心暗色几何覆盖物**,边界 = mark 的 alpha 轮廓。
|
||||
可靠信号只有三个:**alpha 轮廓形状、尺度≈1.0、旋转≈0°**。
|
||||
3. **照片纹理会产生大量虚假贴合**:纯倒角距离会被草地等密纹理区域击败。
|
||||
必须叠加方向约束与洞内部特征。
|
||||
|
||||
## 最终算法(`src/method_l_shape.py`)
|
||||
|
||||
```text
|
||||
粗扫:尺度 0.85–1.15(步长 0.025)× 全图位置
|
||||
评分 = 方向感知倒角DT + 20×内部边缘密度 + 0.5×max(0, 内-环亮度差)
|
||||
↓
|
||||
全图 NMS 取 top-5 候选
|
||||
↓
|
||||
每候选局部旋转扫描:36 角(步长 10°)× ±3px 平移
|
||||
→ rot*(近并列 ±0.15 内取离 0 最近者,兼容星形等旋转对称形状)
|
||||
→ rot=0 拟合分 refined
|
||||
↓
|
||||
选择:|rot*|≤12° 且 0.96≤s≤1.05 的候选中 refined 最低者
|
||||
否则取 |rot*| 最小并标 AMBIGUOUS
|
||||
```
|
||||
|
||||
三个评分项的物理含义:
|
||||
|
||||
| 项 | 含义 | 区分度(实测) |
|
||||
| --- | --- | --- |
|
||||
| 方向感知倒角DT | 边界点只匹配同法向桶(9 桶×20°)的 Canny 边缘 | 真洞 3–6,天空截断 8+ |
|
||||
| 内部边缘密度 | 洞内部实心平坦(≈0),纹理区内部边缘多 | 真洞 ≤0.1,草地 0.36 |
|
||||
| 内-环亮度差 | 洞比周围暗(半透明暗色覆盖) | 真洞 -40~-104,误检区 +6 |
|
||||
|
||||
Canny 阈值固定 `lo = max(40, 0.5×中位灰度)`、`hi = 3×lo`。
|
||||
阈值过低会让 JPEG 噪声在平坦区产生大量假边缘,污染"内部平坦"特征
|
||||
(曾导致 4db9 星形样本漏检)。
|
||||
|
||||
## 判别力证据
|
||||
|
||||
所有样本的候选旋转曲线均能区分目标/干扰:
|
||||
|
||||
- 目标:`rot*≈0°` 且 rot=0 拟合分明显低于其他角度;
|
||||
- 干扰项:`rot*≠0°`(如 41ac 左月牙 -10°、8727 竞争星 +20°、f4fc 竞品 -20°)。
|
||||
|
||||
三个独立真值锚点全部命中:
|
||||
|
||||
| 样本 | 独立真值来源 | 真值 | 本方案 | 偏差 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 4db94793 | SIFT + RANSAC | (295.8, 142.1) | (297, 143) | ~2px |
|
||||
| d7a7f80c | SIFT + RANSAC | (167.8, 146.4) | (171, 146) | ~4px |
|
||||
| 444d0ea7 | 闭合轮廓分析(Hu≈0.003) | 左洞 rot≈0° | (183, 160) rot=0° | 一致 |
|
||||
|
||||
## API(`src/solve.py`)
|
||||
|
||||
```python
|
||||
from solve import solve
|
||||
result = solve(big_image_b64, mark_image_b64) # 支持 dataURI 前缀
|
||||
```
|
||||
|
||||
命令行:`python src/solve.py <大图路径或base64> <mark路径或base64>`
|
||||
|
||||
返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"distance_px": 297,
|
||||
"x": 297, "y": 143,
|
||||
"scale": 1.0, "rot": 0.0,
|
||||
"score": 2.429,
|
||||
"confidence": "OK",
|
||||
"candidates": [...]
|
||||
}
|
||||
```
|
||||
|
||||
- `distance_px`:mark 110×110 画布左上角应到达的 x 偏移;
|
||||
若拖动 UI 的 mark 初始位置不在 x=0,用 `x` 减去初始 x。
|
||||
- `confidence`:`OK`(满足 rot/尺度门限)或 `AMBIGUOUS`(降级选择,建议人工复核)。
|
||||
- mark 图必须含 alpha 通道(PNG),否则返回 `ok=false`。
|
||||
|
||||
## 结果验证方法
|
||||
|
||||
1. **叠加轮廓检查(最直接)**:`python src/verify_result.py` 生成
|
||||
`src/out/L/verify/`,把 alpha 轮廓按 offset 以绿色半透明+黄色描边叠回大图。
|
||||
轮廓与暗色图形边缘完全重合 = 正确。
|
||||
2. **候选对比**:看 `src/out/L/<前缀>-match.png`,红框目标应同形同大同向,
|
||||
其余框应为旋转/缩放干扰项或被淘汰纹理。
|
||||
3. **数值检查**:`summary.txt` 中目标 rot≈0、scale∈[0.96,1.05]、精修分最低。
|
||||
|
||||
## 10 样本最终结果
|
||||
|
||||
| 样本 | offset | 尺度 | rot* | 精修分 | 备注 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2a313202f58c | (370,114) | 1.025 | 0° | 4.04 | |
|
||||
| 41ac8781f7e9 | (398,151) | 1.000 | 0° | 4.40 | 右月牙;bbox 88×106 与 alpha 完全一致 |
|
||||
| 444d0ea7c8bb | (178,148) | 1.000 | 0° | 4.50 | 轮廓分析交叉验证 ✓ |
|
||||
| 4db94793ec6d | (297,143) | 1.000 | 0° | 2.43 | SIFT 真值 ✓ |
|
||||
| 632f84299d75 | (113,136) | 1.025 | 0° | 6.82 | 拟合最弱,置信度中 |
|
||||
| 6a53f4529063 | (166,152) | 1.025 | 0° | 4.97 | |
|
||||
| 87276de26640 | (153,134) | 1.025 | 0° | 3.94 | 星形,靠旋转扫描解决 |
|
||||
| d6cd0ebb7eed | (200,55) | 1.000 | 0° | 5.82 | |
|
||||
| d7a7f80c4554 | (171,146) | 1.025 | 0° | 3.11 | SIFT 真值 ✓ |
|
||||
| f4fc5aa426ce | (298,68) | 1.050 | 0° | 4.79 | |
|
||||
|
||||
## 已知边界
|
||||
|
||||
- `632f` 精修分 6.82 明显高于其余样本(2.4–5.8),建议保留人工复核,
|
||||
或积累更多样本校准"可信分数"阈值;
|
||||
- 洞叠在深色照片上时"内-环亮度差"约束减弱,可提高方向倒角权重;
|
||||
- 若验证码改用亮色覆盖物,需要把亮度差项改为双向(|内-环|)并重标权重。
|
||||
|
||||
## 复现
|
||||
|
||||
```bash
|
||||
.venv/bin/python src/method_l_shape.py # 批量求解 + 可视化
|
||||
.venv/bin/python src/verify_result.py # 叠加轮廓验证图
|
||||
.venv/bin/python src/solve.py <大图> <mark> # 单样本 API
|
||||
```
|
||||
Reference in New Issue
Block a user