大切なのは、いきなり「失敗(ブロック)」から始めないことです。最初の二週間は全部「警告」にとどめ、実際にどのくらいの頻度で、どんな変更のときにスコアが動くかを観察します。敷地の広さが見えてから、ブロックの線を引く。私はこの順番を守らなかったせいで、正当なデザイン更新のたびに CI が赤くなり、一度ゲートを外す羽目になりました。
ゲートスクリプトにまとめる
二つのものさしができたら、ベースラインと候補のディレクトリを突き合わせ、逸脱した画像だけを報告するスクリプトにまとめます。エージェントや CI が読める終了コードと、後で目視する用の差分レポートを吐くのがポイントです。
#!/usr/bin/env python3"""image_gate.py — ベースラインと候補の画像アセットを知覚的に比較する。"""import sysimport jsonfrom pathlib import Pathimport numpy as npfrom PIL import Imageimport imagehashfrom skimage.metrics import structural_similarity as ssim# しきい値(自分のデータで測り直すこと)PHASH_WARN, PHASH_FAIL = 3, 7SSIM_WARN, SSIM_FAIL = 0.99, 0.95def compare(base: Path, cand: Path) -> dict: a, b = Image.open(base), Image.open(cand) dist = imagehash.phash(a) - imagehash.phash(b) ga = np.asarray(a.convert("L")) gb_img = b.convert("L") if gb_img.size != a.size: gb_img = gb_img.resize(a.size, Image.LANCZOS) score = float(ssim(ga, np.asarray(gb_img))) if dist >= PHASH_FAIL or score < SSIM_FAIL: verdict = "fail" elif dist >= PHASH_WARN or score < SSIM_WARN: verdict = "warn" else: verdict = "pass" return {"name": base.name, "phash": dist, "ssim": round(score, 4), "verdict": verdict}def main(baseline_dir: str, candidate_dir: str) -> int: base_root, cand_root = Path(baseline_dir), Path(candidate_dir) results, worst = [], "pass" for base in sorted(base_root.glob("*.jpg")): cand = cand_root / base.name if not cand.exists(): results.append({"name": base.name, "verdict": "missing"}) worst = "fail" continue r = compare(base, cand) results.append(r) if r["verdict"] == "fail": worst = "fail" elif r["verdict"] == "warn" and worst != "fail": worst = "warn" Path("image_gate_report.json").write_text(json.dumps(results, indent=2, ensure_ascii=False)) for r in results: print(f"{r['verdict']:>7} {r['name']} phash={r.get('phash','-')} ssim={r.get('ssim','-')}") print(f"\n=== overall: {worst} ===") return 1 if worst == "fail" else 0if __name__ == "__main__": sys.exit(main(sys.argv[1], sys.argv[2]))
ロゴを刷新した、配色を変えた——こうした正当な変更でもゲートは赤くなります。これはバグではなく仕様です。人間がベースラインを更新する明示的な手順(例えば make update-baseline)を用意し、その操作をレビュー対象にしておくと、更新の意図が履歴に残ります。解像度バリアントをまとめて扱うなら、壁紙の解像度バリアント配信を設計する の考え方と接続すると、どの寸法をゲートの対象にするかを整理しやすくなります。