当初の圧縮ルールはかなり単純でした。SSIMULACRA2のtarget 60を満たす範囲で、できるだけ低いAVIF qualityを選ぶ。画像数が多い場合は、代表sampleのうち1枚だけ58まで下がることを許し、それ以外は60以上を必須にする、というルールです。
PNGや通常のsource imageでは、この方針で問題ないと考えていました。ところが、手元に届く前に高品質なoriginalからすでに非可逆圧縮されていたWebPを扱うようになり、話が変わりました。
higher-quality original: ~2 MB
↓
lossy WebP: ~100 KB
↓
AVIF
この2回目の変換でも、WebPに対してSSIMULACRA2が60なら十分なのか。最初はそう考えました。60は60です。しかし、本当に変わっていたのはscoreではなくreference imageでした。
間違っていたのはmetricではなく、referenceが変わったこと
SSIMULACRA2はreference imageとdistorted imageを比較し、その2つの入力間の知覚差をscore化します。blur、ringing、人工的に追加されたedgeなど、圧縮由来の劣化に反応するよう設計されており、公開されている評価材料にはJPEG、WebP、AVIFなどのdistortionも含まれます。SSIMULACRA2のドキュメント](https://github.com/cloudinary/ssimulacra2/blob/main/README.md%22>SSIMULACRA2のドキュメント)にはmetricとおおまかなquality anchorが説明されています。
良好なsourceから直接AVIFを作る場合、比較は実質的にsource → AVIFです。このとき60というscoreは、その変換で新たに加わった劣化を表します。
一方、すでに非可逆圧縮されたWebPの履歴はこうです。
original
↓ first lossy encode
WebP
↓ second lossy encode
AVIF
ところがSSIMULACRA2が見るのはWebP → AVIFだけです。WebPより前に存在したoriginalについては何も知りません。WebPにすでに含まれているartifactはreferenceの一部になります。
つまり60というscoreから分かるのは、AVIFが現在のWebPから大きく離れていないということです。失われたmasterから最終AVIFがどれだけ離れたかは分かりません。
非可逆transcodingでは2つ目のquality budgetが生まれる
originalにきれいなgradientがあるとします。最初のencoderで少しbandingが入っても、WebPはまだ十分きれいに見える。そのWebPをさらにAVIFへencodeすると、SSIMULACRA2はAVIF encoderが追加した劣化は評価できますが、referenceに最初から含まれている劣化は評価対象にできません。
そのため、すでにlossyな画像からencodeすることと、利用可能な最良のsourceから直接encodeすることは同じではありません。libavifプロジェクトのdiscussion](https://github.com/AOMediaCodec/libavif/discussions/2640%22>libavifプロジェクトのdiscussion)でも、入力がすでに圧縮されている場合、既存のcompression artifactが新しいAVIFへ引き継がれ得るという同様の論点が示されています。
これはAVIFがWebPのartifactを必ず悪化させるという意味でも、transcodingを常に避けるべきという意味でもありません。2つ目のencoderは、元のquality budgetが一部消費された後から仕事を始める、ということです。
最終的に採用したpolicy
- Canonicalまたは高品質なsource: target 60、1 sampleのfloor 58。
- Lossless WebP: target 60、floor 58。
- 既知のlossy derivative: target 65、floor 63。
重要なのはJPEGかWebPかではありません。canonical sourceか、既知のlossy derivativeかです。
WebPにはlossless modeがあります。WebP](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification%22>WebP) lossless specificationではpixel valueを正確に復元するmodeが定義されており、この場合は補うべき前世代のlossy degradationがありません。逆に、すでに複数回の非可逆変換を受けていると分かっているJPEGなら、圧縮済みWebPと同じように慎重に扱うべきです。
なぜ65なのか
SSIMULACRA2には「2世代目の非可逆圧縮なら5ポイント上げる」というルールはありません。探しても見つからないのは、そうした規則自体が存在しないからです。65はengineering policyであって、metricの性質ではありません。
公開されているquality anchorは判断材料になります。おおまかには50がmedium/fair quality、70がhigh/good qualityに対応します。つまり60はvisually losslessな領域ではなく、Web用途としてはかなり積極的な圧縮側です。
良好なsourceから直接変換する場合、file sizeを小さくする代わりにそのperceptual budgetを使うことは受け入れています。しかし2世代目のlossy encodeでは、追加で許容するdistortionを小さくしたいと考えました。
70も検討しましたが、すべてのtranscoded imageをかなり厳しいquality領域へ押し上げることになります。大量の画像を読み込むページ、特にmobile connectionでは追加byteが効きます。すでに圧縮済みの全画像に70を強制するだけの根拠はなかったため、保守的な中間点として65を選びました。
なぜ65/62ではなく65/63なのか
元のpolicyは60/58でした。代表outlierを1枚だけmain targetより2ポイント低く許可する設計です。main targetを65へ上げても同じpolicyを保つなら、自然に65/63になります。
60 - 58 = 2
65 - 63 = 2
62にすると例外幅は3ポイントです。通常sampleは厳しくする一方で、最悪の1枚だけを以前より緩くすることになります。すでにlossy圧縮済みの入力に対してだけ例外幅を広げる技術的な理由は見つかりませんでした。
63も65も魔法の数字ではありません。意味があるのはpolicyの一貫性です。
2 MBが100 KBになっただけでは見た目の品質は分からない
compression ratioからSSIMULACRA2 thresholdを決めることはしていません。2 MBから100 KBまで縮むと極端に見えますが、file sizeだけでは知覚上の劣化をほとんど判断できません。
resolution、image entropy、noise、単色領域、line art、chroma subsampling、変換前のformatなどが圧縮効率に大きく影響します。GoogleのWebP](https://developers.google.com/speed/webp/docs/webp_study%22>GoogleのWebP) compression studyも、同じfile sizeなら同じvisual qualityだとは仮定せず、おおむね同等qualityでcodecを比較しています。
そのため20×小さい → target 65のようなルールは使いません。policy上重要なのは、そのfileが既知のlossy derivativeかどうかであり、圧縮率の見た目の大きさではありません。
originalが残っているならWebPをtranscodeしない
高品質なoriginalと小さいlossy WebPの両方が残っている場合は、originalから直接AVIFを作り、通常の60/58 policyを使います。
preferred:
original → AVIF
avoid when possible:
original → lossy WebP → AVIF
2世代目のtargetを厳しくしても、最初のencodeで失われた情報は戻りません。65はAVIFをWebPにより近づけるだけで、70ならさらに近づきます。どちらも失われたoriginalを復元しません。
62か63かより、implementation bugの方が重要だった
policyを見直している途中で、encoder logicにより危険な問題を見つけました。codeにはformatごとのtarget定数と、WebPに別targetを返せるhelperがすでにありました。そのためWebPを60から65へ変えるだけなら簡単に見えました。
実際は違いました。adaptive quality decisionは依然としてglobal targetとglobal worst-score thresholdを使っていました。format-specific targetは後段で各sampleをpassやbelow-targetと表示するためには使われても、final AVIF qualityを決める判定を必ずしも制御していませんでした。
その結果、WebP sampleを「target 65未満」と正しく表示しながら、adaptive search自体はglobal pass conditionが60なので同じqualityをacceptする、という微妙なfailureが起こり得ます。
intended WebP target: 65
actual score: 61.2
format-aware label: below target
global search rule: pass if target is still 60
実際のencoded outputを決める判定に使われないthresholdには意味がありません。
thresholdをsample policyそのものに含める方が安全
現在はthresholdを単なるformat定数ではなく、source側のpropertyとして扱う方が良いと考えています。単純化したpseudocodeなら次の通りです。
if sample is a known lossy derivative:
target = 65
floor = 63
else:
target = 60
floor = 58
reject if any sample is below its floor
allow at most one sample below its target
重要なのは、resultを説明するthresholdと、resultをacceptするかどうかを決めるthresholdが同じであることです。
Sampling policyも重要
すべてのcandidate AVIF qualityについて、すべての画像を試す必要はありません。pipelineではbytes-per-pixelの分布から、最大10枚の代表的なJPEG、PNG、WebP sampleを選びます。
画像が10枚以下なら、全sampleが通常targetを満たす必要があります。より大きなcollectionでは1 sampleだけ低いfloorを使えますが、それ以外はmain targetを満たす必要があります。
Samplingによって探索は現実的になります。ただし、これはoutlier ruleを必要以上に緩くしない理由でもあります。選ばれたsampleは代表ではあっても、sampleされていない全画像がまったく同じ挙動をする証明ではありません。
heuristicを置き換えられる実験
最も強い答えを得るなら、代表的なcorpusで本当のoriginalを保存し、次のcomplete chainを比較します。
A: original → AVIF, target 60
B: original → lossy WebP → AVIF, target 60
C: original → lossy WebP → AVIF, target 63
D: original → lossy WebP → AVIF, target 65
E: original → lossy WebP → AVIF, target 70
各variantについてfinal byte size、true originalに対するSSIMULACRA2、WebP intermediateに対するSSIMULACRA2、選択されたencoder qualityを記録し、難しい画像は目視でも確認します。
十分に代表的なoriginal保存済みデータセットでは、このcontrolled experimentをまだ実施していません。そのため65が普遍的にoptimalだとは主張できません。この制約は重要です。
AVIFだからといって再encodeが必ず得になるわけではない
唯一残っているsourceが100 KBのWebPで、65/63を満たしたAVIFが96 KBなら、私は変換そのものを疑います。4 KB節約するために、さらに1世代のlossy encodeと処理の複雑さを追加する価値があるとは限りません。
同じ100 KBのWebPがquality policyを満たしたまま65 KBのAVIFになるなら、画像が多いページではtrade-offがかなり魅力的になります。
Codec conversionでは2つの質問を分けるべきです。追加distortionは許容できるか。そしてsize reductionは十分大きいか。1つ目を満たしても、2つ目が自動的に満たされるわけではありません。
現在使っているルール
最良品質のoriginalがあるなら、そこから直接encodeし、このWeb workloadでは60/58を使います。WebPがlosslessなら同じく60/58です。唯一残っているfileが既知のlossy derivativeなら、2世代目にはより厳しいbudget、現在は65/63を使います。AVIFでほとんど小さくならないなら、既存WebPを残すことも検討します。
本質的な教訓は、WebPに特別なSSIMULACRA2の数字が必要ということではありません。full-reference quality metricは、そのreference imageが表している問いにしか答えられません。
referenceがすでに情報を失っているなら、高いscoreは「このreferenceに近い」という意味であって、「その前に存在した画像に近い」という意味ではありません。Image provenanceをcompression policyの一部として扱うようになってから、thresholdは任意のcodec settingではなく、異なる世代のlossに割り当てるbudgetとして見えるようになりました。