#!/usr/bin/env python3 """res_test.py 的补充(分辨率决策的证据):detail_ratio = |Laplacian| 能量比只看高频「多不多」,不看「在不在对的位置」—— 没有重采样低通的 1280×704 会被奖励,哪怕多出来的高频是 VAE 伪影;crop PSNR 又偏爱模糊。这里在同一批 player crop、 同一条 VAE 往返(res_test.enc / dec / to_res 原样复用)上加三个看「对齐」的指标: ssim 灰度 SSIM(高斯窗 σ 1.5,标准常数) lap_corr Laplacian(重建) 与 Laplacian(GT) 的 Pearson 相关:高频要在对的位置才高 edge_f1 Canny(50,150) 边缘,1 px 容差的 F1(precision = 重建边缘落在 GT 边缘旁的比例;recall 反之) psnr / detail_ratio 也重算一遍,对拍 outputs/char_diag/res_test.json(应相同)。写 outputs/char_diag/res_test_edge.json, 不动 res_test.json。默认 GC_RES(_ref 的 ids 是 832×480)。 CUDA_VISIBLE_DEVICES=0 $PY scripts/res_test_edge.py """ import json import os import sys import cv2 import numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import res_test as RT # noqa: E402 (enc / dec / to_res / RES / NF / D 原样复用) import geometry as GEO # noqa: E402 import infer as INF # noqa: E402 import model as GM # noqa: E402 import sample as S # noqa: E402 K3 = np.ones((3, 3), np.uint8) def gray(a): return cv2.cvtColor(np.ascontiguousarray(a), cv2.COLOR_RGB2GRAY) def ssim(a, b): a, b = gray(a).astype(np.float64), gray(b).astype(np.float64) c1, c2 = (0.01 * 255) ** 2, (0.03 * 255) ** 2 blur = lambda x: cv2.GaussianBlur(x, (11, 11), 1.5) # noqa: E731 ma, mb = blur(a), blur(b) va, vb, cov = blur(a * a) - ma * ma, blur(b * b) - mb * mb, blur(a * b) - ma * mb return float((((2 * ma * mb + c1) * (2 * cov + c2)) / ((ma * ma + mb * mb + c1) * (va + vb + c2))).mean()) def lap_corr(a, b): la = cv2.Laplacian(gray(a).astype(np.float32), cv2.CV_32F).ravel() lb = cv2.Laplacian(gray(b).astype(np.float32), cv2.CV_32F).ravel() return float(np.corrcoef(la, lb)[0, 1]) def edge_f1(rc, gt): er, eg = cv2.Canny(gray(rc), 50, 150) > 0, cv2.Canny(gray(gt), 50, 150) > 0 if not er.any() or not eg.any(): return 0.0, 0.0, 0.0 dr, dg = cv2.dilate(er.astype(np.uint8), K3) > 0, cv2.dilate(eg.astype(np.uint8), K3) > 0 p, r = float((er & dg).sum() / er.sum()), float((eg & dr).sum() / eg.sum()) return p, r, (2 * p * r / (p + r) if p + r > 0 else 0.0) def main(): assert GEO.RES_TAG == GEO.LEGACY_RES, "_ref 的 ids 是 832×480:用默认 GC_RES 跑" clips = json.load(open(f"{RT.D}/clips.json")) vae = INF.load_vae("cuda", S.MODEL_DIR) out = {k: [] for k in RT.RES} for cid in clips: ref = np.load(f"{RT.D}/_ref/{cid}.npz") src, ids = ref["gt_src"][:RT.NF], ref["ids"] for name, wh in RT.RES.items(): x, x0, cw = (src, 0, 1280) if name == "1280x704" else RT.to_res(src, wh) rec = RT.dec(vae, RT.enc(vae, np.ascontiguousarray(x))) for t in range(8, RT.NF, 8): # 与 res_test.py 同一套 crop m = np.isin(ids[t], GM.PLAYER_PART_IDS) if m.sum() < 600: continue ys, xs = np.nonzero(m) p = GEO.dst_to_src(np.array([[xs.min() - 10, ys.min() - 10], [xs.max() + 10, ys.max() + 10]], float)) sx0, sy0 = int(max(0, p[0, 0])), int(max(0, p[0, 1])) sx1, sy1 = int(min(1280, p[1, 0])), int(min(704, p[1, 1])) gt_c = src[t, sy0:sy1, sx0:sx1] if name == "1280x704": rc = rec[t, sy0:sy1, sx0:sx1] else: up = cv2.resize(rec[t], (cw, 704), interpolation=cv2.INTER_CUBIC) full = np.zeros_like(src[t]); full[:, x0:x0 + cw] = up rc = full[sy0:sy1, sx0:sx1] mse = np.mean((rc.astype(np.float32) - gt_c.astype(np.float32)) ** 2) g = lambda a: float(np.abs(cv2.Laplacian(gray(a).astype(np.float32), cv2.CV_32F)).mean()) # noqa: E731 ep, er, ef = edge_f1(rc, gt_c) out[name].append(dict(psnr=10 * np.log10(255 ** 2 / max(mse, 1e-6)), detail=g(rc) / max(g(gt_c), 1e-6), ssim=ssim(rc, gt_c), lap_corr=lap_corr(rc, gt_c), edge_p=ep, edge_r=er, edge_f1=ef)) print(cid, {k: len(v) for k, v in out.items()}, flush=True) res = {k: {"n": len(v), **{m: round(float(np.mean([d[m] for d in v])), 4) for m in v[0]}} for k, v in out.items()} base = json.load(open(f"{RT.D}/res_test.json"))["vae_ceiling"] for k in res: # 对拍 res_test.json(同一条路径 ⇒ 同数) res[k]["matches_res_test_json"] = (abs(res[k]["psnr"] - base[k]["player_crop_psnr_vs_native_gt"]) < 0.01 and abs(res[k]["detail"] - base[k]["detail_ratio_vs_native_gt"]) < 0.001) print(json.dumps(res, indent=1), flush=True) json.dump({"vae_ceiling_alignment": res, "note": __doc__}, open(f"{RT.D}/res_test_edge.json", "w"), indent=1, ensure_ascii=False) if __name__ == "__main__": sys.exit(main())