Files
douyin-captcha/docs/solution.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

134 lines
5.6 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.
# 拖动重叠验证码:最终方案
> 历史实验过程与方法淘汰记录见 [experiments.md](experiments.md)
> 实验原始日志在 `try/findings.md`。
## 任务定义
给定「拖动重叠」验证码的背景大图(552×344)与 mark 小图(110×110 RGBA),
离线求出 mark 应拖动重叠的目标区域偏移。
背景中存在 ≥2 个与 mark 同形的区域(缩放/旋转生成的干扰项),
只有**无缩放、无旋转**(尺度≈1.0、旋转≈0°)的实例是目标。
## 核心发现(决定技术路线)
1. **mark 的 RGB 是"捐赠补丁",不是洞内内容。**
决定性实验:真掩码 NCC 全图任意位置最高仅 0.56241ac/ 0.464444d)。
若"洞 = 该处照片变暗、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
```