Download scripts/char_analyze.py from teawhite/maliang: direct link, hf CLI and curl.
- Browser
- Download file 4.6 kB
-
https://huggingface.co/teawhite/maliang/resolve/main/scripts/char_analyze.py
- Command line
-
hf download hf://teawhite/maliang/scripts/char_analyze.py
-
curl -L -o char_analyze.py https://huggingface.co/teawhite/maliang/resolve/main/scripts/char_analyze.py
4.6 kB
| #!/usr/bin/env python3 | |
| """角色清晰度诊断·分析端:按 ids 里的角色掩码裁框,比较 GT(源 1280×704) / GT(832×480) / VAE 重建 / 各生成变体。 | |
| 指标(框内,相对 GT832):sharp = Sobel 梯度均值之比,lap = Laplacian 方差之比,psnr。另出无损裁剪对比图。""" | |
| import json | |
| import os | |
| import sys | |
| import cv2 | |
| import numpy as np | |
| import geometry as GEO | |
| import model as GM | |
| M = "/opt/dlami/nvme/zhiyangdeng/maliang" | |
| D = f"{M}/outputs/char_diag" | |
| VARIANTS = ["s4000_cfg1", "s8000_cfg1", "s8000_semcfg2", "s8000_semcfg3"] | |
| ROLES = {"player": GM.PLAYER_PART_IDS, "boss": GM.BOSS_PART_IDS} | |
| PAD, MIN_PX, UP = 10, 600, 3 | |
| def bbox(mask): | |
| ys, xs = np.nonzero(mask) | |
| return max(0, ys.min() - PAD), min(mask.shape[0], ys.max() + PAD + 1), max(0, xs.min() - PAD), min(mask.shape[1], xs.max() + PAD + 1) | |
| def grad(img): | |
| g = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32) | |
| gx, gy = cv2.Sobel(g, cv2.CV_32F, 1, 0), cv2.Sobel(g, cv2.CV_32F, 0, 1) | |
| return float(np.sqrt(gx ** 2 + gy ** 2).mean()), float(cv2.Laplacian(g, cv2.CV_32F).var()) | |
| def psnr(a, b): | |
| mse = np.mean((a.astype(np.float32) - b.astype(np.float32)) ** 2) | |
| return 10 * np.log10(255.0 ** 2 / max(mse, 1e-6)) | |
| def src_crop(src, box): | |
| """GT 源分辨率里与 832×480 框对应的区域,缩放到框的尺寸 ×UP(看 1280×704 原生能有多清楚)。""" | |
| y0, y1, x0, x1 = box | |
| p = GEO.dst_to_src(np.array([[x0 - 0.5, y0 - 0.5], [x1 - 0.5, y1 - 0.5]])) | |
| sx0, sy0 = int(max(0, np.floor(p[0, 0]))), int(max(0, np.floor(p[0, 1]))) | |
| sx1, sy1 = int(min(src.shape[1], np.ceil(p[1, 0]))), int(min(src.shape[0], np.ceil(p[1, 1]))) | |
| return cv2.resize(src[sy0:sy1, sx0:sx1], ((x1 - x0) * UP, (y1 - y0) * UP), interpolation=cv2.INTER_CUBIC) | |
| def label(img, text): | |
| out = img.copy() | |
| cv2.rectangle(out, (0, 0), (out.shape[1], 18), (0, 0, 0), -1) | |
| cv2.putText(out, text, (3, 13), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (255, 230, 120), 1, cv2.LINE_AA) | |
| return out | |
| def main(): | |
| clips = json.load(open(f"{D}/clips.json")) | |
| variants = [v for v in VARIANTS if all(os.path.exists(f"{D}/{v}/{c}.npz") for c in clips)] | |
| stats = {r: {k: [] for k in ["recon"] + variants} for r in ROLES} | |
| sheet_rows = [] | |
| for ci, cid in enumerate(clips): | |
| ref = np.load(f"{D}/_ref/{cid}.npz") | |
| gt, rec, src, ids = ref["gt"], ref["recon"], ref["gt_src"], ref["ids"] | |
| gens = {v: np.load(f"{D}/{v}/{cid}.npz")["frames"] for v in variants} | |
| for role, pids in ROLES.items(): | |
| for t in range(8, 81, 8): | |
| m = np.isin(ids[t], pids) | |
| if m.sum() < MIN_PX: | |
| continue | |
| y0, y1, x0, x1 = box = bbox(m) | |
| g_ref, l_ref = grad(gt[t, y0:y1, x0:x1]) | |
| for k, fr in [("recon", rec)] + list(gens.items()): | |
| c = fr[t, y0:y1, x0:x1] | |
| g, l = grad(c) | |
| stats[role][k].append((g / max(g_ref, 1e-6), l / max(l_ref, 1e-6), psnr(c, gt[t, y0:y1, x0:x1]))) | |
| if role == "player" and t in (40, 72) and len(sheet_rows) < 12: | |
| up = lambda a: cv2.resize(a, ((x1 - x0) * UP, (y1 - y0) * UP), interpolation=cv2.INTER_NEAREST) | |
| tiles = [label(src_crop(src[t], box), "GT 1280x704 (native)"), label(up(gt[t, y0:y1, x0:x1]), "GT 832x480"), | |
| label(up(rec[t, y0:y1, x0:x1]), "VAE recon")] | |
| tiles += [label(up(gens[v][t, y0:y1, x0:x1]), v) for v in variants] | |
| h = max(x.shape[0] for x in tiles) | |
| tiles = [cv2.copyMakeBorder(x, 0, h - x.shape[0], 0, 4, cv2.BORDER_CONSTANT) for x in tiles] | |
| sheet_rows.append(np.concatenate(tiles, 1)) | |
| summary = {} | |
| for role, d in stats.items(): | |
| summary[role] = {k: {"n": len(v), "sharp_ratio": round(float(np.mean([x[0] for x in v])), 3), | |
| "lapvar_ratio": round(float(np.mean([x[1] for x in v])), 3), | |
| "psnr": round(float(np.mean([x[2] for x in v])), 2)} for k, v in d.items() if v} | |
| json.dump(summary, open(f"{D}/summary.json", "w"), indent=1) | |
| print(json.dumps(summary, indent=1)) | |
| if sheet_rows: | |
| w = max(r.shape[1] for r in sheet_rows) | |
| rows = [cv2.copyMakeBorder(r, 0, 6, 0, w - r.shape[1], cv2.BORDER_CONSTANT) for r in sheet_rows] | |
| sheet = np.concatenate(rows, 0) | |
| cv2.imwrite(f"{D}/player_crops.png", sheet[:, :, ::-1]) | |
| print("sheet", sheet.shape) | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |