feat: 候选级 TV 精修(跨候选重排+显著性保护),第 2 次机器 PASS
- TV 评估覆盖全部候选,z(chamfer)+z(TV) 联合重排;切换需 TV 低 ≥1σ - target 匹配 ≤2px 容差(尺度精修后坐标不再严格相等) - 标定 5/5 全中(Δ 0/-1/-1/0/-4),离线 9/10 保持 - live_182311 x=354 218px → PASS(iframe 销毁+toast 确证)
This commit is contained in:
@@ -359,3 +359,33 @@ alpha 反推 bg(=(pixel−white·a)/(1−a)),反推结果的总变差(TV
|
||||
|
||||
VerifyErr 尾案:位置准 + VerifyErr 并存 → 服务端容差可能比预想更紧(±2px 级),
|
||||
或存在尚未观测到的判据(行为分残留 / mark 与服务端渲染的 ±2px 源差异)。
|
||||
|
||||
### 候选级 TV 精修定稿(2026-09-15 深夜,第 2 次机器 PASS)
|
||||
|
||||
窗口级 TV(仅微调 ±5px)救不了跨候选错选(live_173634:错选 (117,120) s=1.1
|
||||
refined=3.17,真位置 (271,127) s=0.95 refined=3.26 —— refined 仅差 0.09)。
|
||||
升级为候选级:对 refined 全部候选各在 ±5px 窗口找 TV 谷,z(chamfer)+z(TV) 联合
|
||||
重排跨候选选优。两次踩坑后定稿:
|
||||
|
||||
1. 显著性保护:切候选要求新候选 TV 比原 target 低 ≥1σ
|
||||
(165204 PASS 题被 TV 拉走 +7px 的教训),否则保持原位;
|
||||
2. target 匹配用 ≤2px 容差(尺度精修后 target.x/y 与 enriched 条目不再严格相等);
|
||||
3. TV 评估覆盖全部 TOP_K 候选(target 可能排在第 5 位,top_n=3 会漏)。
|
||||
|
||||
标定 5/5 全中:
|
||||
|
||||
| 题 | 真值 | 精修后 Δ |
|
||||
| --- | --- | --- |
|
||||
| live_164551 | 309 | 0 |
|
||||
| live_165204 | 177 | −1 |
|
||||
| live_172458 | 388 | −1 |
|
||||
| live_173321 | 391 | 0 |
|
||||
| live_173634 | 270 | −4 |
|
||||
|
||||
离线 10 题回归 9/10 保持。
|
||||
|
||||
**第 2 次机器 PASS**:live_182311,x=354 s=0.9875,218px 18 步重放,
|
||||
up 后 iframe 立即销毁(POST 读数 Session not found = PASS 特征),
|
||||
toast「验证成功,正在为你登录」。
|
||||
|
||||
当日统计:solve 误差从 ±5px 压到 ≤4px(5 个 GT 全中),线上 PASS 2 次。
|
||||
|
||||
+76
-55
@@ -242,69 +242,89 @@ def rotation_scan(d, pts, angs, w, h, x, y):
|
||||
return None
|
||||
|
||||
|
||||
def _tv_refine(big, mark, d, target, win=5, lam=1.0):
|
||||
"""基于半透明贴片物理模型的亚像素精修。
|
||||
def _tv_refine(big, mark, d, target, enriched=None, win=5, lam=1.0, top_n=None):
|
||||
"""基于半透明贴片物理模型的候选重排与亚像素精修。
|
||||
|
||||
缺口渲染 = bg*(1-a) + white*a。给定候选位置,用 mark 的 alpha 反推 bg,
|
||||
反推结果的 Total Variation 越低越自然 → 位置越正确。与 chamfer 分数
|
||||
z-normalize 后相加(λ=1.0),在 ±win px 窗口取联合最低点。
|
||||
无有效 TV 信号(alpha 全不透明 / 信号弱)时保持原位。
|
||||
反推结果的 Total Variation 越低越自然 → 位置越正确。
|
||||
|
||||
两级工作:
|
||||
1. 候选级:对 refined 前 top_n 个候选各在 ±win 窗口找 TV 谷,
|
||||
z(chamfer)+λ·z(TV) 联合重排(防 refined 差 0.09 的错选,live_173634)
|
||||
2. 窗口级:对胜出候选在 ±win 窗口用联合分微调 dx/dy
|
||||
|
||||
无有效 TV 信号(alpha 全不透明 / 信噪弱)时保持原 target。
|
||||
"""
|
||||
try:
|
||||
if big is None or mark is None or mark.ndim != 3 or mark.shape[2] != 4:
|
||||
return target
|
||||
bigf = big.astype(np.float32)
|
||||
a_full = mark[..., 3].astype(np.float32) / 255.0
|
||||
s = target["scale"]
|
||||
bx0, by0 = d["bbox"]
|
||||
h, w = a_full.shape
|
||||
ar_full = cv2.resize(a_full, (max(1, round(w * s)), max(1, round(h * s))),
|
||||
interpolation=cv2.INTER_AREA)
|
||||
ay0, ax0 = round(by0 * s), round(bx0 * s)
|
||||
bh, bw = d["am"].shape
|
||||
ay1, ax1 = round((by0 + bh) * s), round((bx0 + bw) * s)
|
||||
ay0, ax0 = max(0, ay0), max(0, ax0)
|
||||
ar = ar_full[ay0:ay1, ax0:ax1]
|
||||
if ar.size == 0 or ((ar > 0.05) & (ar < 0.85)).sum() < 200:
|
||||
return target
|
||||
H, W = d["shape"]
|
||||
score2, _, _, _ = score_at_scale(d, s)
|
||||
if score2 is None:
|
||||
return target
|
||||
ch_vals, tv_vals, dxs = [], [], []
|
||||
for dy in range(-win, win + 1):
|
||||
for dx in range(-win, win + 1):
|
||||
x0 = target["x"] + dx
|
||||
y0 = target["y"] + dy
|
||||
if y0 < 0 or x0 < 0 or y0 + ar.shape[0] > H or x0 + ar.shape[1] > W:
|
||||
ch_vals.append(None); tv_vals.append(None); dxs.append((dx, dy))
|
||||
continue
|
||||
tv = _tv_at(bigf, ar, x0, y0)
|
||||
ch_vals.append(float(score2[y0, x0]))
|
||||
tv_vals.append(tv)
|
||||
dxs.append((dx, dy))
|
||||
chv = np.array([v if v is not None else np.nan for v in ch_vals], np.float32)
|
||||
tvv = np.array([v if v is not None else np.nan for v in tv_vals], np.float32)
|
||||
if np.isnan(tvv).all():
|
||||
return target
|
||||
sd_t = np.nanstd(tvv)
|
||||
# TV 信噪保护:谷值不显著(<0.5σ 低于均值)说明该题 TV 无区分度,不采纳
|
||||
if sd_t < 1e-6 or (np.nanmean(tvv) - np.nanmin(tvv)) < 0.5 * sd_t:
|
||||
bx0, by0 = d["bbox"]
|
||||
cands = enriched if enriched else [target]
|
||||
if top_n:
|
||||
cands = list(cands)[:top_n]
|
||||
scored = []
|
||||
for e in list(cands)[:top_n]:
|
||||
s = e["scale"]
|
||||
h, w = a_full.shape
|
||||
ar_full = cv2.resize(a_full, (max(1, round(w * s)), max(1, round(h * s))),
|
||||
interpolation=cv2.INTER_AREA)
|
||||
ay0, ax0 = max(0, round(by0 * s)), max(0, round(bx0 * s))
|
||||
ar = ar_full[ay0:round((by0 + d["am"].shape[0]) * s),
|
||||
ax0:round((bx0 + d["am"].shape[1]) * s)]
|
||||
if ar.size == 0 or ((ar > 0.05) & (ar < 0.85)).sum() < 200:
|
||||
continue
|
||||
pts = []
|
||||
for dy in range(-win, win + 1):
|
||||
for dx in range(-win, win + 1):
|
||||
x0 = e["x"] + dx
|
||||
y0 = e["y"] + dy
|
||||
if y0 < 0 or x0 < 0 or y0 + ar.shape[0] > H or x0 + ar.shape[1] > W:
|
||||
continue
|
||||
tv = _tv_at(bigf, ar, x0, y0)
|
||||
if tv is not None:
|
||||
pts.append((dx, dy, tv))
|
||||
if len(pts) < 20:
|
||||
continue
|
||||
tvs = np.array([p[2] for p in pts], np.float32)
|
||||
sd = float(np.std(tvs))
|
||||
if sd < 1e-6 or (float(np.mean(tvs)) - float(np.min(tvs))) < 0.5 * sd:
|
||||
continue # 该候选 TV 无区分度
|
||||
bi = int(np.argmin(tvs))
|
||||
dx, dy, tvmin = pts[bi]
|
||||
scored.append(dict(cand=e, dx=dx, dy=dy, tv=tvmin,
|
||||
refined=e["refined"]))
|
||||
if not scored:
|
||||
return target
|
||||
ref = np.array([r["refined"] for r in scored], np.float32)
|
||||
tvv = np.array([r["tv"] for r in scored], np.float32)
|
||||
def zn(v):
|
||||
m = np.nanmean(v)
|
||||
sd = np.nanstd(v) + 1e-9
|
||||
return (v - m) / sd
|
||||
comb = zn(chv) + lam * zn(tvv)
|
||||
if np.isnan(comb).all():
|
||||
return target
|
||||
bi = int(np.nanargmin(comb))
|
||||
dx, dy = dxs[bi]
|
||||
if dx == 0 and dy == 0:
|
||||
return target
|
||||
out = dict(target)
|
||||
out["x"] = target["x"] + dx
|
||||
out["y"] = target["y"] + dy
|
||||
sd = float(np.std(v)) + 1e-9
|
||||
return (v - float(np.mean(v))) / sd
|
||||
comb = zn(ref) + lam * zn(tvv)
|
||||
bi = int(np.argmin(comb))
|
||||
best = scored[bi]
|
||||
# 显著性保护:切候选要求新候选 TV 比原 target 低 ≥1σ,
|
||||
# 否则保持(165204 PASS 题被 TV 拉走 +7px 的教训)
|
||||
tv_sd = float(np.std(tvv)) + 1e-9
|
||||
def _same(e, t):
|
||||
return (abs(e.get("x", -9999) - t.get("x", -9998)) <= 2
|
||||
and abs(e.get("y", -9999) - t.get("y", -9998)) <= 2
|
||||
and abs(e.get("scale", -1) - t.get("scale", -2)) < 0.005)
|
||||
same_as_target = _same(best["cand"], target)
|
||||
if not same_as_target:
|
||||
tgt_entry = next((r for r in scored if _same(r["cand"], target)), None)
|
||||
if tgt_entry is not None and (tgt_entry["tv"] - best["tv"]) < tv_sd:
|
||||
best = tgt_entry
|
||||
elif tgt_entry is None:
|
||||
return target
|
||||
out = dict(best["cand"])
|
||||
out["x"] = best["cand"]["x"] + best["dx"]
|
||||
out["y"] = best["cand"]["y"] + best["dy"]
|
||||
# TV 精修分合成到 refined(用于日志/阈值可比)
|
||||
out["refined"] = target["refined"]
|
||||
return out
|
||||
except Exception:
|
||||
return target
|
||||
@@ -488,9 +508,10 @@ def solve_pair(big, mark, tip_y=None):
|
||||
refined=best[0])
|
||||
# TV 精修(2026-09-15):贴片 = bg*(1-a)+white*a,位置正确时反推出的
|
||||
# bg(=(pixel-white*a)/(1-a))应是自然图像(总变差最低)。
|
||||
# 4 标定样本上真值处 TV 全部谷底或并列谷底;与 chamfer z-分数相加
|
||||
# (λ=1.0) 后 4/4 命中 ≤1px。仅 OK 候选做,窗口 ±5px。
|
||||
target = _tv_refine(big, mark, d, target)
|
||||
# 候选级判别 + 窗口微调:对 refined 前 3 候选各在 ±5px 窗口找 TV 谷,
|
||||
# chamfer z 分 + TV z 分联合重排(live_173634 实测:错选候选 TV=304,
|
||||
# 真候选 TV=198,判别力跨候选有效)。
|
||||
target = _tv_refine(big, mark, d, target, enriched)
|
||||
s = target["scale"]
|
||||
off = (target["x"] - round(bx0 * s), target["y"] - round(by0 * s))
|
||||
return dict(x=off[0], y=off[1], scale=s, rot=target["rot"],
|
||||
|
||||
Reference in New Issue
Block a user