Files
douyin-captcha/try/findings.md
T
杨豪 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

102 lines
5.1 KiB
Markdown
Raw 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.
# 实验记录与最终结论
> 本文为历史实验日志,经验总结已沉淀至 `docs/solution.md` 与
> `docs/experiments.md`,以 docs 为准。
## 任务
对「拖动重叠」验证码:给定背景大图(552×344)与 mark 小图(110×110 RGBA),
离线求出 mark 应拖动重叠的目标区域偏移。
## 关键发现(决定技术路线)
1. **mark 的 RGB 是"捐赠补丁"**:其内部是场景纹理,与洞内内容无像素关系。
实测真掩码 NCC 全图任意位置 ≤0.57(若"洞=该处照片变暗"则真目标应≈1.0),
因此**一切基于 RGB/灰度的模板匹配(方法 A/A2/C)原理上不可行**,
早期 0.7 的高分全是小尺度纹理巧合。
2. **洞是叠加在照片上的实心暗色几何覆盖物**:形状 = mark 的 alpha 轮廓,
尺度 ≈1.0、旋转 ≈0;干扰项是同形的缩放/旋转实例。
因此唯一可靠信号是 **alpha 轮廓 + 尺度 + 旋转**。
3. 照片纹理会产生大量虚假"贴合":纯倒角距离会被草地等密纹理击败
(早期方法 D/E 失败的根因),必须加方向约束与洞内部特征。
## 方法演进(均位于 try/,输出 try/out/<方法字母>/)
| 方法 | 思路 | 结论 |
| --- | --- | --- |
| A | 蒙版 CCORR 多尺度 | 废弃:不减均值、跨尺度不公平 |
| A2 | 蒙版 CCOEFF + 峰显著度 | 基线可用但会锁纹理(41ac 分歧样本的来源) |
| B | SIFT + RANSAC | 仅 2 个纹理丰富样本可用(4db9/d7a7,作为真值锚点保留) |
| C | 旋转感知 NCC | 第一阶段候选即错,旋转校验无法挽回 |
| D/E/F/G | Chamfer / 覆盖率 / 通用轮廓 / 连通域 | 小尺度纹理饱和 / 背景轮廓合并 |
| H/I/J/K | alpha 轮廓 + 局部对比度 / 暗目标分割 | 阈值敏感,误检集中在照片纹理 |
| **L** | **方向感知倒角 + 洞内部特征 + 局部旋转扫描** | **最终方案,10/10 OK** |
## 最终方案(src/method_l_shape.py)
1. **粗扫**:尺度 0.85–1.15(步长 0.025)× 全图位置,评分:
`方向感知倒角DT + 20×内部边缘密度 + 0.5×max(0, 内-环亮度差)`
- 倒角带方向:边界点只匹配同法向桶(9 桶×20°)的 Canny 边缘,
草地等无方向纹理被自然抑制;
- 内部边缘密度:洞内部实心平坦(≈0),纹理区内部边缘多;
- 内-环亮度差:洞比周围暗(实测 -40~-104),误检区不暗(+6)。
2. **NMS 取 top-5 候选**。
3. **局部旋转扫描**:每候选 36 角(步长 10°)× ±3px 平移,得最佳旋转角
rot*与精修分;近并列(±0.15)角中取离 0 最近者为 rot*
(兼容星形等旋转对称形状)。
4. **选择**:`|rot*|≤12° 且 0.96≤s≤1.05` 的候选中取精修分最低者;
否则取 |rot*| 最小并标 AMBIGUOUS。
实测判别力:真目标 rot*=0 且 rot=0 拟合分明显低于干扰项
(干扰项 rot*≠0 或 rot=0 分数高 1~2 分)。
## 最终结果(offset = mark 110×110 画布左上角应放置的位置)
| 样本 | offset | 洞 bbox 左上角 | 尺度 | rot* | 精修分 | 备注 |
| --- | --- | --- | --- | --- | --- | --- |
| 2a313202f58c | (370,114) | (373,125) | 1.025 | 0° | 4.04 | |
| 41ac8781f7e9 | (398,151) | (409,153) | 1.000 | 0° | 4.40 | 右月牙;左月牙为干扰 |
| 444d0ea7c8bb | (178,148) | (183,160) | 1.000 | 0° | 4.50 | 轮廓分析交叉验证 ✓ |
| 4db94793ec6d | (297,143) | (306,146) | 1.000 | 0° | 2.43 | SIFT 真值 (295.8,142.1) ✓ |
| 632f84299d75 | (113,136) | (116,147) | 1.025 | 0° | 6.82 | 拟合最弱,置信度中 |
| 6a53f4529063 | (166,152) | (170,155) | 1.025 | 0° | 4.97 | |
| 87276de26640 | (153,134) | (157,137) | 1.025 | 0° | 3.94 | 星形(椭圆角不可用,旋转扫描解决) |
| d6cd0ebb7eed | (200,55) | (209,58) | 1.000 | 0° | 5.82 | |
| d7a7f80c4554 | (171,146) | (174,157) | 1.025 | 0° | 3.11 | SIFT 真值 (167.8,146.4) ✓ |
| f4fc5aa426ce | (298,68) | (310,70) | 1.050 | 0° | 4.79 | |
可视化:`src/out/L/<前缀>-match.png`(红框=目标,其余=候选,标注 s/rot)。
## API 封装
`src/solve.py`:传入两张图片的 base64,返回拖动距离与目标位置。
```python
from solve import solve
result = solve(big_image_b64, mark_image_b64)
# result = {"ok": true, "distance_px": 297, "x": 297, "y": 143,
# "scale": 1.0, "rot": 0.0, "score": 2.429,
# "confidence": "OK", "candidates": [...]}
```
命令行:`python src/solve.py <大图路径> <mark路径>`。
- `distance_px` = mark 110x110 画布左上角应到达的 x 偏移;若拖动 UI 的
mark 初始位置不在 x=0,用 `x` 减去初始 x。
- mark 图必须含 alpha 通道(PNG),否则返回 `ok=false`。
## 复现
```bash
.venv/bin/python src/method_l_shape.py
```
## 已知边界
- 632f 的拟合分 6.82 明显高于其余样本(2.4–5.8),建议对该类样本
保留人工复核或结合更多样本校准阈值;
- 洞若与 mark 非同色/同明度或叠加深色照片上,"内-环亮度差"约束会减弱,
届时可提高方向倒角权重;
- 无 ground truth 的样本中,444d(轮廓分析)、41ac(bbox 与 alpha 尺寸
完全一致 88×106)已通过方法间交叉验证。