처음의 압축 규칙은 단순했습니다. SSIMULACRA2 target 60을 통과하는 범위에서 가능한 한 낮은 AVIF quality를 선택했습니다. 이미지가 많은 경우 representative sample 하나만 58까지 내려가는 것을 허용하고, 나머지는 모두 60 이상이어야 했습니다.
PNG와 일반 source image에는 이 규칙이 잘 맞았습니다. 문제는 제가 받기 전에 이미 더 높은 품질의 original에서 lossy 압축된 WebP를 다루기 시작하면서 생겼습니다.
higher-quality original: ~2 MB
↓
lossy WebP: ~100 KB
↓
AVIF
두 번째 변환도 WebP 기준으로 60이면 충분할까? 처음에는 그렇다고 생각했습니다. 60은 여전히 60이니까요. 하지만 실제로 달라진 것은 score가 아니라 reference image였습니다.
Metric이 틀린 것이 아니라 reference가 바뀐 것이었다
SSIMULACRA2는 reference image와 distorted image를 비교해 두 입력 사이의 perceptual difference를 점수화합니다. Blur, ringing, 인위적으로 생긴 edge 같은 compression damage에 반응하도록 설계되어 있고, 공개된 평가 자료에는 JPEG, WebP, AVIF 등 여러 codec의 distortion이 포함됩니다. SSIMULACRA2](https://github.com/cloudinary/ssimulacra2/blob/main/README.md%22>SSIMULACRA2) 문서에는 metric과 대략적인 quality anchor가 설명되어 있습니다.
좋은 source에서 AVIF를 직접 만들면 비교는 사실상 source → AVIF입니다. 이때 score 60은 해당 변환에서 추가된 손실을 나타냅니다.
이미 lossy WebP인 경우 실제 이력은 다릅니다.
original
↓ first lossy encode
WebP
↓ second lossy encode
AVIF
하지만 SSIMULACRA2가 보는 것은 WebP → AVIF뿐입니다. WebP 이전의 original은 알 수 없습니다. WebP에 이미 존재하던 artifact가 reference의 일부가 됩니다.
따라서 60은 AVIF가 현재 WebP에서 너무 멀어지지 않았다는 사실은 보여줄 수 있지만, 최종 AVIF가 잃어버린 master에서 얼마나 멀어졌는지는 알려주지 못합니다.
Lossy transcoding에는 두 번째 quality budget이 생긴다
original에 깨끗한 gradient가 있다고 가정해 보겠습니다. 첫 encoder가 약간의 banding을 만들지만 WebP는 여전히 괜찮아 보입니다. 그 WebP를 다시 AVIF로 encode하면, SSIMULACRA2는 AVIF encoder가 추가한 degradation은 penalize할 수 있지만 reference에 이미 포함된 damage는 penalize할 수 없습니다.
그래서 already-lossy image에서 encode하는 것은 best available source에서 직접 encode하는 것과 같지 않습니다. libavif](https://github.com/AOMediaCodec/libavif/discussions/2640%22>libavif) 프로젝트 discussion에서도 비슷한 원리를 설명합니다. 입력이 이미 압축되어 있으면 기존 compression artifact가 새 AVIF로 이어질 수 있습니다.
이 말은 AVIF가 모든 WebP artifact를 자동으로 더 심하게 만든다거나 transcoding을 절대 하면 안 된다는 뜻은 아닙니다. 두 번째 encoder가 일을 시작할 때는 original quality budget 일부가 이미 소모된 상태라는 뜻입니다.
결국 선택한 policy
- Canonical 또는 high-quality source: target 60, 한 sample의 floor 58.
- Lossless WebP: target 60, floor 58.
- Known lossy derivative: target 65, floor 63.
중요한 구분은 JPEG와 WebP가 아닙니다. canonical source와 known 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 generation이 없습니다. 반대로 이미 여러 번 lossy transformation을 거쳤다고 알고 있는 JPEG라면 이전에 압축된 WebP와 같은 주의가 필요합니다.
왜 65인가
SSIMULACRA2에는 두 번째 lossy generation이면 정확히 5점을 올려야 한다는 규칙이 없습니다. 그런 규칙은 존재하지 않기 때문에 찾을 수도 없었습니다. 65는 engineering policy이지 metric 자체의 속성이 아닙니다.
공개된 quality anchor는 맥락을 제공합니다. 대략 50은 medium 또는 fair quality, 70은 high 또는 good quality에 해당합니다. 따라서 60은 visually lossless 영역이 아니라 비교적 공격적인 web-compression 영역입니다.
좋은 source에서 직접 변환할 때는 더 작은 file을 얻기 위해 이 정도 perceptual budget을 쓰는 것을 받아들입니다. 하지만 두 번째 lossy generation에서는 추가 distortion budget을 줄이고 싶었습니다.
70도 고려했지만, 모든 transcoded image가 훨씬 엄격한 quality 영역으로 올라갑니다. 이미지가 많은 페이지에서는 특히 mobile connection에서 추가 byte가 중요합니다. 이미 압축된 모든 이미지를 70으로 강제해야 할 근거는 없었기 때문에 보수적인 중간값으로 65를 선택했습니다.
왜 65/62가 아니라 65/63인가
원래 policy는 60/58이었습니다. representative outlier 하나만 main target보다 2점 낮게 허용했습니다. main target을 65로 올리면서 같은 policy를 유지하면 자연스럽게 65/63이 됩니다.
60 - 58 = 2
65 - 63 = 2
62를 쓰면 예외 폭이 3점이 됩니다. 일반 sample은 더 엄격하게 만들면서 최악의 sample 하나에는 오히려 더 큰 여유를 주는 셈입니다. 이미 lossy 압축된 input에 대해서만 예외 폭을 넓힐 기술적 이유는 찾지 못했습니다.
63과 65 모두 magic number는 아닙니다. 중요한 것은 policy가 내부적으로 일관된다는 점입니다.
2 MB가 100 KB가 되었다고 visual quality를 알 수는 없다
Compression ratio로 SSIMULACRA2 threshold를 정하지 않습니다. 2 MB에서 100 KB로 줄어드는 것은 극적으로 보이지만, file size만으로는 perceptual degradation을 거의 판단할 수 없습니다.
Resolution, image entropy, noise, flat area, line art, chroma subsampling, 이전 format 등이 compression efficiency에 큰 영향을 줍니다. Google](https://developers.google.com/speed/webp/docs/webp_study%22>Google) WebP compression study도 동일한 file size를 동일한 visual quality로 가정하지 않고, 대략 비슷한 quality에서 codec을 비교합니다.
그래서 20× smaller → target 65 같은 규칙은 쓰지 않습니다. 중요한 것은 현재 file이 known lossy derivative인지 여부이지, size reduction 비율이 얼마나 커 보이는지가 아닙니다.
Original이 남아 있다면 WebP를 transcode하지 않는다
고품질 original과 작은 lossy WebP가 둘 다 있다면 original에서 바로 AVIF를 만들고 일반적인 60/58 policy를 씁니다.
preferred:
original → AVIF
avoid when possible:
original → lossy WebP → AVIF
두 번째 generation의 target을 더 엄격하게 해도 첫 encode에서 사라진 정보는 돌아오지 않습니다. 65는 AVIF를 WebP에 더 가깝게 유지할 뿐이고, 70은 더 가깝게 유지합니다. 어느 쪽도 잃어버린 original을 복원하지는 못합니다.
62와 63의 차이보다 implementation bug가 더 중요했다
Policy를 검토하는 과정에서 encoder logic에 더 위험한 문제가 있음을 발견했습니다. Code에는 format별 target constant와 WebP에 다른 target을 반환할 수 있는 helper가 이미 있었습니다. 그래서 WebP를 60에서 65로 바꾸는 일은 단순해 보였습니다.
실제로는 아니었습니다. Adaptive quality decision은 여전히 global target과 global worst-score threshold를 사용하고 있었습니다. Format-specific target은 나중에 individual result를 pass 또는 below-target으로 표시하는 데 쓰였지만 final AVIF quality를 고르는 결정 자체를 반드시 제어하지는 않았습니다.
그 결과 subtle failure가 가능합니다. WebP sample이 target 65 미만이라고 올바르게 표시되면서도, global pass condition이 여전히 60이기 때문에 adaptive search는 같은 quality를 accept할 수 있습니다.
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 constant가 아니라 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
결과를 설명할 때 사용하는 threshold와 실제 accept 여부를 결정하는 threshold가 같아야 한다는 점이 핵심입니다.
Sampling policy도 중요하다
모든 candidate AVIF quality에서 모든 이미지를 검사할 필요는 없습니다. Pipeline은 bytes-per-pixel 분포에서 최대 10개의 representative JPEG, PNG 또는 WebP sample을 고릅니다.
이미지가 10개 이하인 collection에서는 모든 sample이 normal target을 만족해야 합니다. 더 큰 collection에서는 sample 하나만 낮은 floor를 쓸 수 있고, 나머지는 main target을 만족해야 합니다.
Sampling은 search를 현실적으로 만들어 주지만 outlier rule을 과도하게 느슨하게 만들지 말아야 하는 이유이기도 합니다. 선택된 sample은 대표일 뿐, sampling하지 않은 모든 이미지가 같은 방식으로 동작한다는 증거는 아닙니다.
Heuristic을 대체할 수 있는 실험
가장 강한 답을 얻으려면 representative corpus의 true original을 보존한 채 전체 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를 기록하고 까다로운 이미지는 직접 눈으로 확인해야 합니다.
충분히 representative한 preserved-original set에서 이 controlled experiment를 수행한 것은 아니므로 65가 전 세계적으로 optimal하다고 주장할 수는 없습니다. 이 한계는 중요합니다.
AVIF라고 해서 추가 encode가 자동으로 가치 있는 것은 아니다
남은 source가 100 KB WebP뿐이고 65/63을 통과한 AVIF가 96 KB라면 conversion 자체를 다시 생각할 것입니다. 4 KB를 줄이기 위해 또 한 번 lossy generation과 추가 processing complexity를 감수할 가치가 없을 수 있습니다.
반대로 같은 100 KB WebP가 quality policy를 통과하면서 65 KB AVIF가 된다면 이미지가 많은 페이지에서는 trade-off가 훨씬 매력적입니다.
Codec conversion은 두 질문에 따로 답해야 합니다. 추가 distortion이 허용 가능한가? 그리고 size reduction이 충분히 큰가? 첫 번째를 통과했다고 두 번째도 자동으로 만족하는 것은 아닙니다.
지금 사용하는 규칙
가장 좋은 quality의 original이 있으면 직접 encode하고 이 web workload에서는 60/58을 사용합니다. WebP가 lossless라면 역시 60/58입니다. 남은 file이 known lossy derivative뿐이라면 두 번째 generation에는 더 엄격한 budget, 현재는 65/63을 적용합니다. AVIF가 거의 작아지지 않는다면 기존 WebP를 유지하는 것도 고려합니다.
더 중요한 교훈은 WebP에 특별한 SSIMULACRA2 숫자가 필요하다는 것이 아닙니다. Full-reference quality metric은 reference image가 표현하는 질문에만 답할 수 있습니다.
Reference가 이미 정보를 잃었다면 높은 score는 “이 reference와 가깝다”는 뜻이지 “그 이전에 존재한 image와 가깝다”는 뜻이 아닙니다. Image provenance를 compression policy의 일부로 보기 시작한 뒤 threshold는 임의의 codec setting이 아니라 서로 다른 generation of loss에 배정하는 budget으로 보이기 시작했습니다.