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

4.5 KiB
Raw Blame History

实验演进记录(A → L

最终方案与使用说明见 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 的高分全是小尺度纹理巧合,方向本身是错的。

BSIFT(意外价值)

  • 平面图形缺少纹理,大部分样本匹配不足;
  • 但在纹理丰富的 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.91.1 全图扫描,最高分仅 0.56/0.46 → 证明 mark RGB 与洞内容无像素关系,RGB 路线全部判死。
  2. 闭合轮廓 + matchShapes444d 上): 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 完全一致 41ac88×106 vs 88×106);
  3. 全部候选可视化后人工目检:真目标应落在几何图形上而非纹理上;
  4. 结果叠加轮廓回贴大图(src/verify_result.py),观察贴合精度。