- method_l_shape: OK 候选尺度 ±0.0375 精修(步长 0.0125 + ±6px 窗口), GT live_165204 Δ 179→176(真人 177),离线 9/10 保持 - 插桩 JSON.stringify 捕获 SDK 日志明文;指针 probe 排除 coalesced/pressure - 拖动不跟手指标作废:post_pos 晚于 SDK 重置滑块,鼠标事件流始终完整 - 剩余 VerifyErr = 候选整体选错(视觉确证),方向:多候选质量评估 - docs: experiments/solution/retrospective 同步收尾
208 lines
9.7 KiB
Markdown
208 lines
9.7 KiB
Markdown
# 拖动重叠验证码:最终方案
|
||
|
||
> 历史实验过程与方法淘汰记录见 [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),建议保留人工复核,
|
||
或积累更多样本校准"可信分数"阈值;
|
||
- 洞叠在深色照片上时"内-环亮度差"约束减弱,可提高方向倒角权重;
|
||
- 若验证码改用亮色覆盖物,需要把亮度差项改为双向(|内-环|)并重标权重。
|
||
|
||
## 线上增强:tip_y 锚定与 flatness 贴片检测(2026-05-15)
|
||
|
||
线上实测发现题目分布漂移:覆盖物从离线样本的暗色洞变为**亮白半透明贴片**,
|
||
方法 L 分数从 1–3 恶化到 3.6–7.7,且出现假峰(chamfer 精修分 2.6 的雪枝纹理
|
||
压过真缺口 8.3)—— 边界/纹理特征在亮贴片雪景题上完全失灵。
|
||
|
||
两阶段解决:
|
||
|
||
### 1. 坐标系修正(bug 修复)
|
||
|
||
服务端 `question.tip_y×2` 是**画布坐标系**(mark 全图左上)缺口顶 y 真值,
|
||
而求解器内部候选 `e.y` 是 alpha-bbox 左上 y,两者相差 `bbox_y0 × scale`
|
||
(实测 ≈9px)。不换算会把真候选推出 ±8px 锚窗、错误候选反而入选。
|
||
换算关系(由离线 GT 实证):**canvas = bbox − bbox_y0×s**。
|
||
|
||
### 2. flatness 贴片检测(线上新特征,决定性)
|
||
|
||
亮白半透明贴片的物理特征:**把盖住的背景“贴平”** —— 贴片区域内纹理方差
|
||
远低于周围。定义 flatness = 掩码内灰度 std / 环形外灰度 std:
|
||
|
||
- 线上真缺口 flatness 实测 **0.04–0.28**(live_145400 GT 位置 0.056);
|
||
- 同图假峰(雪枝纹理)0.49–0.67,区分度巨大;
|
||
- 内部纹理 zNCC、亮度抬升等其它特征在此类题上全部失效(实测)。
|
||
|
||
求解流程(tip_y 提供时):各尺度在 y 条带内用 filter2D 向量化扫描 flatness,
|
||
取画布 y 落在锚窗(±8px)内的最低值位置;flatness ≤ 0.30 判 OK(method
|
||
= "flatness")。无匹配或未提供 tip_y → 回退 chamfer 路径(离线逻辑不变)。
|
||
|
||
**验证结果**:
|
||
|
||
- 线上 batch 8/8 全 OK(此前 3 道 AMBIGUOUS 全修复),dy ≤ 8 全部满足;
|
||
- live_145400(chamfer 假峰题)flatness 命中 (407,64),与 IoU 亚像素实证
|
||
GT (412,68) 差 (5,4)px;
|
||
- 离线 10 题无 tip_y 走原逻辑,9/10 保持(4db94793 为历史已知边界)。
|
||
|
||
`solve(big, mark, tip_y=...)` 返回新增 `method` 字段;score 量纲随 method
|
||
变化(flatness 为 0–1 比值,chamfer 为 px 分)。
|
||
|
||
### 3. flatness 路径最终降级为禁用(2026-05-15 晚,真人 GT 实证)
|
||
|
||
flatness 在线批量指标全绿,但**位置真值校准暴露系统性偏差**。用真人手拖
|
||
(轨迹录制器采样 down→up 位移)拿到 4 个真值:
|
||
|
||
| 题 | flatness | chamfer(tip_y 锚定) | 真人 GT | 判定 |
|
||
| --- | --- | --- | --- | --- |
|
||
| live_165204 | — | 179 | 177 | chamfer ✓(真人 PASS) |
|
||
| live_172458 | 433 (s=0.70) | 388 | 388 | flatness +45px ✗ |
|
||
| live_173321 | 209 (s=0.825) | 391 | 391 | flatness −182px ✗ |
|
||
| live_180227 | 266 (s=0.95) | 257 | (用户拖偏,不采信) | — |
|
||
|
||
结论:**chamfer(tip_y 锚定 + refined 排序)3/3 命中(dx=0/0/2),
|
||
flatness 每次都大幅偏移**。平坦区特征在雪景题上会吸到贴片内部/
|
||
天空等大平坦区,且可与同样为假的弱 chamfer 候选互相“掩护”
|
||
(交叉验证被穿透,live_173321 实测)。
|
||
|
||
处理:`FLAT_DISABLED = True` 禁用 flatness 选点(`flatness_scan`
|
||
函数保留供参考);主路径 = tip_y 锚定 chamfer。采题侧把
|
||
“flatness/chamfer 共识”作为加分项但不作硬依赖。
|
||
|
||
同时期的输入层排除(详见 experiments.md):CDP 合成指纹、事件指纹、
|
||
时序积压均被 xdotool 硬件输入 + 60Hz 样条加密重放排除——
|
||
**此前所有失败案例事后看都是位置错,不存在独立的“输入指纹拒”**。
|
||
|
||
### 4. 尺度精修 + 首次机器 PASS(2026-09-15 深夜)
|
||
|
||
SCALES 网格步长 0.025,真尺度落在步间时轮廓对齐可偏 5-15px —— 正是 VerifyErr
|
||
(社区资料确认其语义 = 缺口距离错,非轨迹问题)的量级。修法:对入选 OK 候选在
|
||
s±0.0375(步长 0.0125)重扫 + ±6px 窗口局部最优,取 rotation_scan refined 最低者。
|
||
|
||
- GT live_165204:179 → 176(真人 177,Δ −1);离线回归 9/10 保持
|
||
- **机器 PASS**:live_160548 x=255 **s=1.0375**(精修值,网格外),拖 157.1px → code=200
|
||
- 剩余 VerifyErr 案例视觉确证为候选整体选错(如 s=0.7 网格下边界假峰),
|
||
方向:多候选质量评估 / 不像就 refresh 换题
|
||
|
||
## 复现
|
||
|
||
```bash
|
||
.venv/bin/python src/method_l_shape.py # 批量求解 + 可视化
|
||
.venv/bin/python src/verify_result.py # 叠加轮廓验证图
|
||
.venv/bin/python src/solve.py <大图> <mark> # 单样本 API
|
||
```
|