Quality Ladder をCIに組み込む最小パイプライン
品質劣化を レビュー者の主観疲弊 に委ねると境界が徐々に緩む“品質ドリフト”が起きます。本稿は Quality Ladder ツールで得る指標を GitHub Actions 上で自動判定し PR をブロックする最小構成を示します。
目的明確化
目標は“過圧縮を未然に検知する”ことであり“常に最高PSNRを目指す”ことではない。高速性と再現性を優先。
要点 (TL;DR)
- 代表セットは golden/ フォルダ に固定し LFS は避ける(差分レビュー性確保)。
- PSNR/SSIM の 下限閾値 を JSON 1ファイルに集中管理。
- 差分のみ再エンコード→ベース指標をキャッシュ → 実行時間を <60s へ。
- 失敗時: 低品質画像タイルをシート化しアーティファクト + Slack URL。
1. ディレクトリ設計
repo/
ladder/
golden/ # 代表元画像
masks/ # 領域マスク PNG (text/ui 等)
baseline.json # 各画像のベース PSNR/SSIM
thresholds.json # カテゴリ別下限
reports/ # CI生成 (差分 PNG / JSON)
scripts/ # compute-quality-ladder.mjs 等
LFS を避ける理由
golden / baseline / thresholds は頻繁に差分レビューしたいテキストや画像です。Git LFS 化するとレビュー粒度が落ちるため通常管理を推奨。
2. Actions ワークフロー骨子
- チェックアウト + Node セットアップ
- キャッシュ復元 (node_modules, ladder/.cache)
- 差分検知:
git diff --name-only origin/main...HEAD | grep assets/
- 差分対象のみ再エンコード → PSNR/SSIM 計測
- thresholds.json 比較 → 下回り箇所を markdown / png 化
- 失敗時 artifact + Slack 通知
name: ladder
on: [pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci --ignore-scripts
- name: Compute metrics
run: node ladder/scripts/compute-quality-ladder.mjs --changed > report.json
- name: Validate
run: node ladder/scripts/validate-thresholds.mjs report.json ladder/thresholds.json
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: ladder-report
path: ladder/reports
3. 閾値設計
カテゴリ別に初期値を置き 2 週間の False Positive / Negative ログで微調整。頻繁な改変は境界の流動化を招くため“理由付き PR”でのみ変更。
{
"photo": { "psnr": 38, "ssim": 0.96 },
"ui": { "psnr": 40, "ssim": 0.985 },
"illust":{ "psnr": 37, "ssim": 0.95 }
}
4. レポート自動化
失敗時は品質低下画像タイル + 指標差分 (ΔPSNR, ΔSSIM) を markdown テーブル化しレビューを 30s 以内に。
// ladder/scripts/validate-thresholds.mjs (抜粋)
import fs from 'node:fs';
const thresholds = JSON.parse(fs.readFileSync(process.argv[3],'utf8'));
const report = JSON.parse(fs.readFileSync(process.argv[2],'utf8'));
const failed = [];
for (const r of report.results) {
const th = thresholds[r.category];
if (r.psnr < th.psnr || r.ssim < th.ssim) failed.push(r);
}
if (failed.length) {
const lines = failed.map(f => '|'+f.file+'|'+f.psnr.toFixed(2)+'|'+f.ssim.toFixed(4)+'|'+f.category+'|').join('\n');
fs.writeFileSync('ladder/reports/fail.md', '|file|psnr|ssim|cat|\n|---|---|---|---|\n'+lines);
process.exitCode = 1;
}
5. 改善ループ
- False Positive 多発 → 当該カテゴリ閾値 -0.2dB 緩和 (過剰検知抑止)
- False Negative 発生 → 問題画像を golden/ に追加し baseline 再生成
- 四半期ごと baseline.json 再生成 (エンコーダ更新吸収)
6. まとめ
CI 化で品質境界を “口頭合意” から “再現可能なコード” へ。履歴は Git / PR に残り説明責任を担保。
公開日: 2025-09-06編集: gazou-compressor.jp
次のステップ
Slack 通知・メトリクス時系列化・文字領域マスク導入で MTTR をさらに短縮。
FAQ
FAQ(よくある質問)
1画像形式の基本方針は?(写真/スクショ/透過)
写真は AVIF / WebP(画質80–85%目安)、UIやスクショはPNG / WebP Lossless、単色ロゴはSVGが基本です。 実装の詳細は srcset/sizes設計ガイド と スクショ最適化 を参照してください。
2圧縮しても画質を落とさないコツは?
実表示幅に合わせたリサイズ → 過大ダウンロードを防ぎ、
srcset/sizes
を 実描画幅に一致させます。画質は写真で 80–85% を起点に、ノイズやエッジを目視確認。 仕上げは /compare で Before/After を見比べるのがおすすめです。