0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

画像を内容で PNG-8 と WebP に振り分ける — 平均 SSIM では 64 色への劣化を見逃した

0
Posted at

業務のアップロード処理は、どの画像も同じ JPEG 設定で圧縮しています。商品写真はそれで困りません。困るのは運用担当が上げてくる注文画面や管理画面のスクリーンショットで、文字のまわりが灰色にざらつきます。そこで、スクショはパレット PNG、写真は WebP と、中身を見て形式を選ぶ案を出すことにしました。判定ロジックの話はよく見かけます。ただ、レビューで突っ込まれるのはその周りです。1 枚あたりの判定コストはどれくらいか。選んだ形式でかえってファイルが大きくなったらどうするか。半年後に誰かがパラメーターを変えたとき、劣化に気づけるか。この 3 つを先に数字で押さえました。

検証に使った画像と判定の指標

画像は 8 枚です。架空のページをブラウザーで描画して撮った 1280×800 のスクショ 2 枚(中国語の議事録と、色付きステータスのある注文一覧)。ウィキメディア・コモンズの写真 2 枚(Jakub Fryš 氏の湖、Josep M. Gracia 氏の果物の露店。どちらも CC BY-SA 4.0)を幅 1280 に縮めた可逆 PNG。スクショ 2 枚を JPEG 品質 80 で保存し直したもの。コモンズの CC BY-SA の人物写真を 413×579 に切り出した JPEG 2 枚です。処理時間を測るためだけに、人物写真の 2996×4200 版も使いました。すべて Node と sharp 0.34.5、Apple M4 の Mac で動かしています。

判定にユニーク色数は使いませんでした。注文一覧を一度 JPEG で保存しただけで 21,500 色になり、人物写真の 26,827 色と区別がつかなくなるからです。代わりに、上位 16 色が全画素に占める割合と、同じ行で左隣と同じ色の画素の割合を見ています。

実装の前に、公開されているツールがどう振り分けるかも見ておきました。ブラウザー上で動く ImgIng(https://imging.ai/)を Chromium 149 で開き、原画像 4 枚と人物写真 2 枚(2 サイズずつ)を読み込むと、スクショは PNG-8・256 色、写真は WebP 品質 75 が既定になりました。果物の露店と 2996×4200 の人物写真はスクショ・UI と判定されましたが、品質が 84 に上がっただけで WebP のままでした。後で出てくる「迷ったら品質を上げた WebP」という中間の経路は、この挙動を参考にしています。

特徴量は最近傍で縮小してから計算する

2996×4200 の人物写真は、原寸のまま計算するとデコード込みで 404 ms かかりました。長辺 512 に縮めてから計算すると 52 ms です。意外だったのは縮小方法の影響でした。最近傍なら、スクショの 2 つの値の低下は最大 0.03 で、写真はどちらも 0.25 以下のままです。sharp の既定の lanczos3 で縮めると、補間で輪郭に中間色が生まれ、スクショの上位 16 色の割合が 0.697〜0.812 まで下がりました。

3 つの経路と、元ファイルを残すフォールバック

振り分けのルールは書きながら決めたもので、調整はしていません。2 つの値の小さいほうが 0.6 以上ならスクショとしてパレット PNG(256 色、ディザなし)に回します。大きいほうが 0.35 以下なら写真として WebP 品質 75。どちらでもなければ WebP 品質 84 です。出力が入力より小さくならなければ、元のファイルをそのまま残します。

8 枚はすべて正しい経路に入り、中間に落ちた画像はありませんでした。それでもフォールバックは働きました。JPEG で届いたスクショ 2 枚はパレット PNG にすると 164,265 バイトが 256,076 バイトに、111,650 バイトが 159,108 バイトに増えたので、元ファイルが残っています。試しに WebP 品質 75 に回すと 105,738 バイトと 54,482 バイトでした。議事録のほうは、可逆の原画像と比べた SSIM が 0.9947 で、JPEG の入力そのもの(0.9917)より高く出ます。JPEG の入力を基準にすると逆に悪く見えるので、何を基準に測るかで結論が変わります。JPEG で届いたスクショに専用の経路を作るかどうかは、まだ決めていません。

経路を間違えたときの損は向きで大きく違いました。可逆のスクショを WebP 品質 75 に回すと、パレット PNG より 1 割ほど小さくなる代わりに非可逆になります(議事録で 103,054 バイト、PSNR 40.39)。写真をパレット PNG に回すと、湖は 56,414 バイトが 417,298 バイトに、露店は 127,602 バイトが 461,340 バイトに膨らみました。

劣化は画像ごとの基準値と比べて拾う

あとからパラメーターを変えられたときに気づけるかを確かめるため、パレットを 64 色に減らす変更と、写真の品質を 50 に下げる変更を試しました。最初のチェックは固定の合格ラインです(スクショは PSNR 50 以上、写真は平均 SSIM 0.93 以上、入力より大きくない)。64 色の変更ではスクショ 4 枚とも引っかかりました。品質 50 のほうは 4 枚中 2 枚しか拾えず、露店(0.9543)と青紫背景の人物写真(0.9476)は通ってしまいました。そこで、前回の実行結果を基準として画像ごとに比べる方式に変えました。

function findRegressions(base, cur) {
  const hits = [];
  for (const [k, b] of Object.entries(base)) {
    const a = cur[k];
    if (a.lane !== b.lane) hits.push(`${k}: 経路`);
    if (a.bytes > b.bytes * 1.1) hits.push(`${k}: サイズ`);
    if (b.ssim - a.ssim > 0.01 || b.ssimWorst1 - a.ssimWorst1 > 0.01) hits.push(`${k}: SSIM`);
    if (b.psnr - a.psnr > 3) hits.push(`${k}: PSNR`);
  }
  return hits;
}

ssimWorst1 は SSIM マップの下位 1% の値です。この比較だと、どちらの変更でも 8 枚中 4 枚、つまり変更の影響を受けた画像はすべて引っかかりました。平均 SSIM だけを見ていたら、64 色の変更は素通りしていたはずです。スクショ 4 枚の平均 SSIM は 0.9997、0.9962、0.9991、0.9913 で、どれも 0.99 を超えています。文字の多い画像では下位 1% のほうがずっと敏感で、注文一覧を JPEG 品質 80 にしたものは平均 0.9874、下位 1% は 0.8386 でした。測れなかったものもあります。拡大すると分かるステータスラベルの色にじみは、色差だけで計算した SSIM でも輝度の SSIM より低くならず、数字には出てきませんでした。

しきい値の 0.6 と 0.35、許容幅の 0.01・10%・3 dB は 8 枚から決めた値なので、精度の数字は出していません。ブラウザーの canvas での実装もまだ試していません。既定値を本番で変える前に、コンプライアンス担当が持ち出しを認めた範囲の実際のアップロード画像で、まず基準値を取るところから始めます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?