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

86 lines
4.5 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.
# 实验演进记录(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`),观察贴合精度。