File size: 6,856 Bytes
51ac7cc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | #!/usr/bin/env python3
"""genbench2 结果整理:
* compare2_<tag>.mp4:每个地图一个 4 列网格(每格 416×240 半分辨率):输入 ids / GT(val)/ 原首帧生成 / 12 种风格;
* compare2_L3.mp4:4 张新地图,每张一行块:ids / 第一轮原首帧生成 / 6 种风格;
* style_sheet.png:每个地图两行(帧 0 = FLUX 首帧;帧 80 = 生成的最后一帧),看风格能不能撑到最后;
* style_stats.json:风格保持 / 回退统计(HSV 直方图交):
keep = sim(生成帧 t, 风格首帧) 越高越好(风格保持)
back = sim(生成帧 t, 原始未风格化帧 0) 相对 keep 越高 ⇒ 越像回退到训练外观
以及 FLUX restyle 的结构保持分(firstframe_flux 的 struct_metrics)。"""
import json
import os
import sys
import cv2
import imageio.v3 as iio
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import genbench2_run as R # noqa: E402
G, G1 = R.G, R.G1
L1 = f"{R.M}/outputs/sft_v10/eval_step-8000/L1_cond"
W, H = 832, 480
HW, HH = W // 2, H // 2
def panel(path, k):
v = iio.imread(path)
return v[:, -H:, k * W:(k + 1) * W]
def gen_frames(out_dir, name):
p = f"{out_dir}/{name}_gen.mp4"
return iio.imread(p) if os.path.exists(p) else panel(f"{out_dir}/{name}.mp4", 0)
def half(fr):
return cv2.resize(fr, (HW, HH), interpolation=cv2.INTER_AREA)
def label(fr, text, scale=0.5):
out = fr.copy()
cv2.rectangle(out, (0, 0), (out.shape[1], 22), (0, 0, 0), -1)
cv2.putText(out, text, (6, 16), cv2.FONT_HERSHEY_SIMPLEX, scale, (255, 230, 120), 1, cv2.LINE_AA)
return out
def grid_video(tiles, ncol=4):
T = min(len(v) for _, v in tiles)
while len(tiles) % ncol:
tiles.append(("", np.zeros((T, HH, HW, 3), np.uint8)))
frames = []
for t in range(T):
rows = [np.concatenate([label(half(v[t]) if v.shape[1] != HH else v[t], n) for n, v in tiles[r:r + ncol]], 1)
for r in range(0, len(tiles), ncol)]
frames.append(np.concatenate(rows, 0))
return np.stack(frames)
def write(path, frames):
iio.imwrite(path, frames, fps=16, codec="libx264", quality=None, macro_block_size=16,
ffmpeg_params=["-crf", "22", "-pix_fmt", "yuv420p", "-movflags", "+faststart"])
print(path, frames.shape, round(os.path.getsize(path) / 1e6, 1), "MB", flush=True)
def hist(fr):
hsv = cv2.cvtColor(fr, cv2.COLOR_RGB2HSV)
h = cv2.calcHist([hsv], [0, 1, 2], None, [12, 6, 6], [0, 180, 0, 256, 0, 256]).ravel()
return h / max(h.sum(), 1)
def sim(a, b):
return float(np.minimum(hist(a), hist(b)).sum())
def main():
stats, sheet_rows = {}, []
TW, TH = 208, 120
for tag in R.SOURCES:
d, sd = f"{G}/L2/{tag}", R.src_dir(tag)
orig0 = cv2.cvtColor(cv2.imread(R.pick(sd, "frame0.png")), cv2.COLOR_BGR2RGB)
tiles = [("input ids (numeric, parts coloured)", panel(f"{d}/{tag}_{next(iter(R.STYLES))}.mp4", 1))]
if tag.startswith("val"):
cid = R.SOURCES[tag]
tiles.append(("GT (held-out run)", panel(f"{L1}/{cid}.mp4", 0)))
tiles.append(("gen: original first frame", panel(f"{L1}/{cid}.mp4", 1)))
else:
od = f"{G1}/L2/{tag}" if os.path.exists(f"{G1}/L2/{tag}/{tag}_orig.mp4") else d
tiles.append(("gen: original first frame", gen_frames(od, f"{tag}_orig")))
row0, row80 = [cv2.resize(orig0, (TW, TH), interpolation=cv2.INTER_AREA)], [None]
stats[tag] = {}
for s in R.STYLES:
if not os.path.exists(f"{d}/{tag}_{s}.mp4"):
print("缺", tag, s)
continue
v = gen_frames(d, f"{tag}_{s}")
tiles.append((f"UNSEEN style: {s}", v))
f0 = cv2.cvtColor(cv2.imread(f"{d}/{s}_f0.png"), cv2.COLOR_BGR2RGB)
fj = json.load(open(f"{d}/{s}_f0.json"))
stats[tag][s] = {"keep_t40": round(sim(v[40], f0), 3), "keep_t80": round(sim(v[80], f0), 3),
"back_t80": round(sim(v[80], orig0), 3), "flux_struct": fj.get("struct")}
row0.append(cv2.resize(f0, (TW, TH), interpolation=cv2.INTER_AREA))
row80.append(cv2.resize(v[80], (TW, TH), interpolation=cv2.INTER_AREA))
write(f"{G}/compare2_{tag}.mp4", grid_video(tiles))
o = tiles[2 if tag.startswith("val") else 1][1]
row80[0] = cv2.resize(o[80], (TW, TH), interpolation=cv2.INTER_AREA)
stats[tag]["_orig"] = {"keep_t80": round(sim(o[80], orig0), 3)}
sheet_rows.append(label(np.concatenate(row0, 1), f"{tag} frame 0 (FLUX first frames; col 1 = original)", 0.45))
sheet_rows.append(label(np.concatenate(row80, 1), f"{tag} frame 80 (generated)", 0.45))
# L3
blocks = []
for c in sorted(os.listdir(f"{G}/L3")):
d = f"{G}/L3/{c}"
theme = c.split("__")[0]
tiles = [(f"UNSEEN map [{theme}]: ids", panel(f"{G1}/L3/{c}/{c}.mp4", 1)),
("gen: FLUX map2frame first frame", panel(f"{G1}/L3/{c}/{c}.mp4", 0))]
orig0 = cv2.cvtColor(cv2.imread(f"{G1}/L3/{c}/f0.png"), cv2.COLOR_BGR2RGB)
stats[f"L3_{theme}"] = {}
for s in R.L3_STYLES:
if os.path.exists(f"{d}/{c}_{s}.mp4"):
v = gen_frames(d, f"{c}_{s}")
tiles.append((f"UNSEEN style: {s}", v))
f0 = cv2.cvtColor(cv2.imread(f"{d}/{s}_f0.png"), cv2.COLOR_BGR2RGB)
stats[f"L3_{theme}"][s] = {"keep_t80": round(sim(v[80], f0), 3), "back_t80": round(sim(v[80], orig0), 3)}
blocks.append(grid_video(tiles))
if blocks:
T = min(len(b) for b in blocks)
write(f"{G}/compare2_L3.mp4", np.concatenate([b[:T] for b in blocks], 1))
w = max(r.shape[1] for r in sheet_rows)
sheet = np.concatenate([cv2.copyMakeBorder(r, 0, 4 * (i % 2), 0, w - r.shape[1], cv2.BORDER_CONSTANT)
for i, r in enumerate(sheet_rows)], 0)
cv2.imwrite(f"{G}/style_sheet.jpg", sheet[:, :, ::-1], [cv2.IMWRITE_JPEG_QUALITY, 92])
print("sheet", sheet.shape)
# 汇总:每种风格跨地图平均
agg = {}
for tag, d in stats.items():
for s, m in d.items():
if s.startswith("_"):
continue
a = agg.setdefault(s, {"keep_t80": [], "back_t80": []})
a["keep_t80"].append(m["keep_t80"]); a["back_t80"].append(m["back_t80"])
summary = {s: {k: round(float(np.mean(v)), 3) for k, v in a.items()} | {"n": len(a["keep_t80"])} for s, a in agg.items()}
summary["_orig_keep_t80"] = round(float(np.mean([d["_orig"]["keep_t80"] for d in stats.values() if "_orig" in d])), 3)
json.dump({"per_style": summary, "per_run": stats}, open(f"{G}/style_stats.json", "w"), indent=1)
print(json.dumps(summary, indent=1))
if __name__ == "__main__":
sys.exit(main())
|