กลับไปที่บล็อก
20 สิงหาคม 2569Sergei Solod15 นาทีในการอ่าน

การใช้ SSIMULACRA2 เมื่อแปลง WebP เป็น AVIF: ทำไมผมใช้ 60 กับต้นฉบับ และ 65 กับไฟล์ที่ผ่านการบีบอัดแบบ lossy มาแล้ว

เมื่อสร้าง AVIF จาก WebP ที่ผ่านการบีบอัดแบบ lossy มาแล้ว SSIMULACRA2 จะวัดเฉพาะความเสียหายของการเข้ารหัสรุ่นที่สอง ผมจึงใช้ 60/58 กับ source ที่สะอาด และ 65/63 กับ known lossy derivative

AVIFWebPSSIMULACRA2การบีบอัดภาพประสิทธิภาพเว็บ

กฎการบีบอัดของผมในตอนแรกดูง่ายมาก: encode AVIF ด้วย quality ต่ำที่สุดที่ยังผ่าน SSIMULACRA2 target 60 สำหรับชุดภาพขนาดใหญ่ ผมยอมให้ representative sample หนึ่งภาพลงไปได้ถึง 58 ขณะที่ sample อื่นทั้งหมดต้องอยู่ที่ 60 หรือสูงกว่า

กับ PNG และ source image ทั่วไป กฎนี้ใช้ได้ดี จนกระทั่งผมเริ่มได้รับไฟล์ WebP ที่ถูกบีบอัดแบบ lossy มาจาก original คุณภาพสูงกว่าก่อนจะมาถึงมือผม

higher-quality original: ~2 MB
          ↓
      lossy WebP: ~100 KB
          ↓
          AVIF

การแปลงครั้งที่สองควรยังผ่านด้วยคะแนน 60 เมื่อเทียบกับ WebP หรือไม่ ตอนแรกผมคิดว่าควร เพราะ 60 ก็คือ 60 แต่สิ่งที่เปลี่ยนจริง ๆ คือ reference image

Metric ไม่ได้ผิด แต่ reference เปลี่ยนไปแล้ว

SSIMULACRA2 เปรียบเทียบ reference image กับ distorted image และให้คะแนน perceptual difference ระหว่าง input สองตัวนั้นโดยตรง มันถูกออกแบบให้ตอบสนองต่อความเสียหายจาก compression เช่น blur, ringing และ edge ที่ถูกสร้างเพิ่มขึ้น รวมถึงมีข้อมูลประเมินจาก JPEG, WebP, AVIF และ codec อื่น ๆ เอกสารของ](https://github.com/cloudinary/ssimulacra2/blob/main/README.md%22>เอกสารของ) SSIMULACRA2 อธิบาย metric และ quality anchor โดยประมาณไว้

ถ้าผม encode AVIF โดยตรงจาก source ที่ดี การเปรียบเทียบก็คือ source → AVIF โดยประมาณ คะแนน 60 จึงอธิบายความเสียหายที่เกิดจาก conversion ครั้งนั้น

แต่ถ้า WebP เป็น lossy อยู่แล้ว ประวัติจริงจะเป็นแบบนี้:

original
   ↓ first lossy encode
WebP
   ↓ second lossy encode
AVIF

SSIMULACRA2 เห็นแค่ WebP → AVIF เท่านั้น มันไม่รู้จัก original ที่มีอยู่ก่อน WebP artifact ที่มีอยู่แล้วใน WebP จึงกลายเป็นส่วนหนึ่งของ reference

ดังนั้นคะแนน 60 บอกได้ว่า AVIF ไม่ได้ห่างจาก WebP มากเกินไป แต่บอกไม่ได้ว่า AVIF สุดท้ายห่างจาก master ที่สูญหายไปเท่าไร

Lossy transcoding สร้าง quality budget ชุดที่สอง

สมมติ original มี gradient ที่สะอาด encoder ตัวแรกเพิ่ม banding เล็กน้อย แต่ WebP ยังดูใช้ได้ จากนั้นผม encode WebP นั้นเป็น AVIF SSIMULACRA2 สามารถลงโทษ degradation เพิ่มเติมจาก AVIF encoder ได้ แต่ไม่สามารถลงโทษ damage ที่มีอยู่ใน reference ก่อนแล้ว

นี่คือเหตุผลที่การ encode จากภาพที่ lossy อยู่แล้วไม่เท่ากับการ encode โดยตรงจาก source ที่ดีที่สุด การสนทนาใน โปรเจกต์](https://github.com/AOMediaCodec/libavif/discussions/2640%22>โปรเจกต์) libavif ก็อธิบายหลักการเดียวกันว่า compression artifact เดิมสามารถถูกพาไปยัง AVIF ใหม่ได้เมื่อ input ถูกบีบอัดมาก่อนแล้ว

ไม่ได้หมายความว่า AVIF จะทำให้ artifact ทุกอย่างของ WebP แย่ลงโดยอัตโนมัติ หรือไม่ควร transcode เลย แต่หมายความว่า encoder ตัวที่สองเริ่มทำงานหลังจาก quality budget ของ original ถูกใช้ไปแล้วบางส่วน

Policy ที่ผมเลือกใช้

  • Canonical หรือ high-quality source: target 60, floor 58 สำหรับ sample เดียว
  • Lossless WebP: target 60, floor 58
  • Known lossy derivative: target 65, floor 63

ความแตกต่างสำคัญไม่ใช่ JPEG เทียบกับ WebP แต่คือ canonical source เทียบกับ known lossy derivative

WebP สามารถเป็น lossless ได้ WebP](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification%22>WebP) lossless specification อธิบาย mode ที่สามารถสร้างค่า pixel กลับมาได้ตรงเดิม ดังนั้นจึงไม่มี lossy generation ก่อนหน้าให้ต้องชดเชย ในทางกลับกัน JPEG ที่ผมรู้ว่าผ่าน lossy transformation มาหลายรอบ ก็ควรถูกจัดการด้วยความระมัดระวังแบบเดียวกับ WebP ที่ถูกบีบอัดมาก่อน

ทำไมต้อง 65?

ไม่มี rule ของ SSIMULACRA2 ที่บอกว่าการ lossy รุ่นที่สองต้องเพิ่มอีกห้าคะแนนพอดี ผมหา rule แบบนั้นไม่เจอเพราะมันไม่มีอยู่จริง 65 เป็น engineering policy ไม่ใช่คุณสมบัติของ metric

Quality anchor ที่เผยแพร่ช่วยให้เห็นบริบท โดยคร่าว ๆ 50 อยู่ในระดับ medium หรือ fair quality ส่วน 70 อยู่ในระดับ high หรือ good quality ดังนั้น 60 ยังอยู่ในช่วง web compression ที่ค่อนข้าง aggressive ไม่ใช่ช่วง visually lossless

สำหรับการแปลงโดยตรงจาก source ที่ดี ผมยอมใช้ perceptual budget ระดับนี้เพื่อแลกกับไฟล์ที่เล็กลง แต่สำหรับ lossy generation ที่สอง ผมต้องการให้ budget ของ distortion เพิ่มเติมเล็กลง

ผมพิจารณา 70 เช่นกัน แต่จะทำให้ภาพที่ transcode ทั้งหมดเข้าสู่ช่วง quality ที่เข้มงวดขึ้นมาก บนหน้าที่โหลดภาพจำนวนมาก โดยเฉพาะผ่าน mobile connection จำนวน byte เพิ่มเติมมีผล ผมไม่มีหลักฐานว่าการบังคับให้ภาพที่ถูกบีบอัดมาแล้วทุกภาพขึ้นไปถึง 70 คุ้มกับต้นทุน จึงเลือก 65 เป็นจุดกึ่งกลางแบบ conservative

ทำไม 65/63 ไม่ใช่ 65/62?

Policy เดิมของผมคือ 60/58 หมายความว่า representative outlier หนึ่งภาพลงได้ต่ำกว่า main target สองคะแนน เมื่อเพิ่ม target เป็น 65 และคง policy แบบเดิมไว้ จึงได้ 65/63 อย่างเป็นธรรมชาติ

60 - 58 = 2
65 - 63 = 2

ถ้าใช้ 62 exception จะกลายเป็นสามคะแนน sample ปกติถูกทำให้เข้มงวดขึ้น แต่ sample ที่แย่ที่สุดกลับได้รับความยืดหยุ่นมากขึ้น ผมหาเหตุผลทางเทคนิคไม่เจอว่าทำไมควรขยาย exception นี้ให้กับ input ที่ผ่าน lossy compression มาแล้วโดยเฉพาะ

ทั้ง 63 และ 65 ไม่ใช่ตัวเลขมหัศจรรย์ สิ่งสำคัญคือ policy ยัง consistent ภายในตัวเอง

จาก 2 MB เหลือ 100 KB ไม่ได้บอกคุณภาพที่มองเห็น

ผมตั้งใจไม่คำนวณ SSIMULACRA2 threshold จาก compression ratio การลดจาก 2 MB เหลือ 100 KB ดูรุนแรง แต่ file size เพียงอย่างเดียวบอก perceptual degradation ได้น้อยมาก

Resolution, image entropy, noise, พื้นที่สีเรียบ, line art, chroma subsampling และ format ก่อนหน้า ล้วนมีผลต่อประสิทธิภาพ compression อย่างมาก การศึกษา](https://developers.google.com/speed/webp/docs/webp_study%22>การศึกษา) WebP compression ของ Google เปรียบเทียบ codec ที่ quality ใกล้เคียงกัน ไม่ได้สมมติว่า file size เท่ากันแปลว่า visual quality เท่ากัน

ดังนั้นผมไม่ใช้กฎอย่าง เล็กลง 20× → target 65 สิ่งสำคัญคือไฟล์ปัจจุบันเป็น known lossy derivative หรือไม่ ไม่ใช่ว่าอัตราการลดขนาดดูน่าประทับใจแค่ไหน

ถ้ายังมี original ผมจะไม่ transcode จาก WebP

ถ้ามีทั้ง original คุณภาพสูงและ WebP lossy ขนาดเล็ก ผมจะ encode AVIF โดยตรงจาก original และใช้ policy ปกติ 60/58

preferred:
original → AVIF

avoid when possible:
original → lossy WebP → AVIF

Target ที่เข้มงวดขึ้นใน generation ที่สองไม่สามารถกู้ข้อมูลที่หายไปจาก encode แรกได้ 65 แค่ทำให้ AVIF อยู่ใกล้ WebP มากขึ้น 70 ก็ยิ่งใกล้ขึ้น แต่ทั้งคู่สร้าง original ที่หายไปกลับมาไม่ได้

Implementation bug สำคัญกว่าการเถียง 62 กับ 63

ระหว่างตรวจ policy ผมพบปัญหาที่อันตรายกว่าภายใน logic ของ encoder Code มี format-specific target และ helper ที่คืน target ต่างกันสำหรับ WebP อยู่แล้ว จึงดูเหมือนว่าเปลี่ยน WebP จาก 60 เป็น 65 ก็พอ

แต่ไม่ใช่ Adaptive quality decision ยังใช้ global target และ global worst-score threshold อยู่ Format-specific target ถูกใช้ภายหลังเพื่อ label แต่ละ result ว่า pass หรือ below-target แต่ไม่ได้ควบคุม decision ที่เลือก final AVIF quality อย่างจำเป็น

จึงเกิด failure แบบละเอียดได้: WebP sample ถูก label อย่างถูกต้องว่าต่ำกว่า target 65 แต่ adaptive search ยัง accept quality เดิม เพราะ global pass condition ยังเป็น 60

intended WebP target: 65
actual score:         61.2

format-aware label:  below target
global search rule:  pass if target is still 60

Threshold ไม่มีความหมายถ้าไม่ได้มีส่วนใน decision ที่เลือก encoded output จริง

วิธีที่ปลอดภัยกว่าคือให้ threshold เป็นส่วนหนึ่งของ sample policy

ตอนนี้ผมชอบมอง threshold เป็น property ของ source มากกว่า format constant ที่ใช้เพื่อแสดงผล ตัวอย่าง 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 ที่ใช้บอกผลต้องเป็น threshold เดียวกับที่ควบคุมว่าจะ accept ผลนั้นหรือไม่

Sampling policy ก็มีผล

ผมไม่จำเป็นต้องทดสอบทุกภาพกับ candidate AVIF quality ทุกค่า Pipeline เลือก representative sample สูงสุดสิบภาพจาก JPEG, PNG หรือ WebP ตามการกระจาย bytes-per-pixel

ถ้ามีภาพสิบภาพหรือน้อยกว่า ทุก sample ต้องผ่าน normal target สำหรับ collection ใหญ่กว่านั้น sample หนึ่งภาพใช้ lower floor ได้ แต่ sample ที่เหลือต้องผ่าน main target

Sampling ทำให้การค้นหา practical แต่ก็เป็นเหตุผลที่ไม่ควรทำให้ outlier rule หลวมเกินไป Sample ที่เลือกเป็นตัวแทน ไม่ได้พิสูจน์ว่าภาพที่ไม่ได้ sample ทั้งหมดจะมีพฤติกรรมเหมือนกัน

Experiment ที่สามารถแทน heuristic ได้

คำตอบที่แข็งแรงที่สุดคือเก็บ true original ของ corpus ที่ representative แล้วทดสอบ 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, SSIMULACRA2 เทียบกับ true original, SSIMULACRA2 เทียบกับ WebP intermediate และ encoder quality ที่ถูกเลือก รวมถึงตรวจภาพที่ยากด้วยสายตา

ผมยังไม่ได้ทำ controlled experiment นี้กับชุด preserved originals ที่ representative มากพอ ดังนั้นจึงอ้างไม่ได้ว่า 65 เป็นค่าที่ optimal สำหรับทุกกรณี ข้อจำกัดนี้สำคัญ

AVIF ไม่ได้หมายความว่า encode เพิ่มอีกครั้งจะคุ้มเสมอ

ถ้า source ที่เหลือมีแค่ WebP 100 KB และ AVIF ที่ผ่าน 65/63 มีขนาด 96 KB ผมจะตั้งคำถามกับการ conversion การประหยัด 4 KB อาจไม่คุ้มกับ lossy generation เพิ่มอีกหนึ่งรอบและ processing complexity ที่มากขึ้น

แต่ถ้า WebP 100 KB เดิมกลายเป็น AVIF 65 KB และยังผ่าน quality policy trade-off จะน่าสนใจกว่ามากบนหน้าที่มีภาพจำนวนมาก

Codec conversion ต้องตอบสองคำถามแยกกัน: distortion เพิ่มเติมยอมรับได้หรือไม่ และ size reduction มากพอที่จะมีความหมายหรือไม่ ผ่านข้อแรกไม่ได้แปลว่าจะผ่านข้อสองโดยอัตโนมัติ

กฎที่ผมใช้ตอนนี้

ถ้ามี original ที่ดีที่สุด ผม encode โดยตรงจากมันและใช้ 60/58 สำหรับ web workload แบบนี้ ถ้า WebP เป็น lossless ก็ใช้ 60/58 เช่นกัน ถ้าไฟล์ที่เหลือมีเพียง known lossy derivative ผมใช้ second-generation budget ที่เข้มงวดขึ้น ปัจจุบันคือ 65/63 ถ้า AVIF ลดขนาดได้น้อยมาก ผมพิจารณาเก็บ WebP เดิมไว้

บทเรียนหลักไม่ใช่ว่า WebP ต้องมีตัวเลข SSIMULACRA2 พิเศษ Full-reference quality metric ตอบได้เฉพาะคำถามที่ reference image ของมันเป็นตัวแทน

ถ้า reference สูญเสียข้อมูลไปแล้ว score สูงหมายถึง “ใกล้กับ reference นี้” ไม่ใช่ “ใกล้กับภาพที่เคยมีอยู่ก่อนหน้านี้” เมื่อผมเริ่มรวม image provenance เข้าเป็นส่วนหนึ่งของ compression policy threshold ก็ไม่ดูเหมือน codec setting แบบ arbitrary อีกต่อไป แต่กลายเป็น budget สำหรับ generation of loss ที่แตกต่างกัน