Stagione 93 / Episodio 3
Il disegno a mano libera
La compressione con perdita ereditata da VP8: luminanza e crominanza sottocampionate, macroblocchi, predizione, trasformata, quantizzazione, segmenti, filtro e codifica aritmetica, con i difetti tipici e i parametri che li correggono.
Note dell'episodio
Bentornati in bottega. Oggi guardiamo il mosaicista che lavora più in fretta. Quello che non posa ogni singola tessera dove l'originale ha un pixel, ma osserva la scena, ne coglie la forma e la ricostruisce con meno materiale. Se vi avvicinate, notate qualche imprecisione. Da lontano, l'immagine è quella.
È la compressione con perdita, il modo in cui WebP comprime la maggior parte delle fotografie del web. E la sua logica è identica a quella del fotogramma chiave di VP8. Vediamola passo per passo, perché ogni passaggio spiega un parametro che poi troverete negli strumenti.
L'occhio umano è molto più sensibile alla luminosità che al colore. Vediamo benissimo un bordo netto tra chiaro e scuro, molto meno la differenza tra due rossi vicini.
Per questo il primo passo non comprime niente: cambia rappresentazione. I pixel in rosso, verde e blu vengono convertiti in tre componenti diverse. Una è la luminanza, indicata con Y, che porta quasi tutta l'informazione sulla forma. Le altre due, chiamate crominanze, portano il colore.
E qui avviene la prima rinuncia. Le crominanze vengono sottocampionate: si tiene un solo valore di colore ogni quattro pixel, un quadrato due per due. Si chiama sottocampionamento quattro-due-zero, e in un colpo solo riduce i dati del colore a un quarto. WebP con perdita usa sempre questo schema, non può farne a meno.
Nella maggior parte delle foto non ve ne accorgete. Ve ne accorgete invece sui bordi netti tra colori saturi: un testo rosso su sfondo blu, un logo, una linea sottile colorata. Lì il colore sembra sbavare. L'encoder di riferimento offre un'opzione chiamata sharp YUV che fa una conversione più accurata e riduce molto questo difetto, al costo di un po' di tempo in più.
Ora l'immagine viene divisa in macroblocchi. Ogni macroblocco copre sedici per sedici pixel di luminanza e, per effetto del sottocampionamento, otto per otto di ciascuna crominanza. Sono le tessere grandi del nostro mosaico.
I macroblocchi vengono elaborati in ordine, da sinistra a destra e dall'alto in basso, come si legge una pagina. Questo ordine conta, perché ogni blocco può guardare quelli già fatti.
Qui c'è l'idea più bella di tutta la compressione con perdita: la predizione.
Invece di descrivere direttamente un blocco, l'encoder prova a indovinarlo partendo dai pixel già codificati, quelli sopra e a sinistra. È come un mosaicista che dice: questa zona continua il cielo che ho appena fatto sopra, quindi parto da lì.
Per un intero blocco sedici per sedici ci sono quattro modi di indovinare. Riempire tutto con la media dei vicini. Prolungare verso il basso la riga di pixel sopra. Prolungare verso destra la colonna di pixel a sinistra. Oppure una modalità chiamata TrueMotion, che combina riga sopra, colonna a sinistra e angolo per seguire le sfumature.
Se il blocco è troppo dettagliato perché un'unica previsione funzioni, l'encoder lo divide in sedici sotto-blocchi da quattro per quattro, e per ognuno sceglie tra dieci direzioni di predizione, incluse varie diagonali. Questo serve per i dettagli fini: capelli, foglie, testo.
Dopo la previsione resta la differenza tra ciò che si era indovinato e ciò che c'è davvero. Si chiama residuo, ed è l'unica cosa che bisogna davvero trasmettere. Se la previsione è buona, il residuo è fatto di numeri piccoli, vicini allo zero.
Il residuo viene diviso in blocchetti quattro per quattro e trasformato. La trasformata è una parente stretta di quella del JPEG: invece di descrivere sedici pixel, li descrive come combinazione di sedici pattern che vanno dal più uniforme al più frastagliato. Il primo coefficiente dice il livello medio, gli altri dicono quanta variazione c'è, e di che tipo.
C'è un dettaglio raffinato. Nei blocchi predetti per intero, i sedici coefficienti medi dei sotto-blocchi di luminanza vengono raccolti e trasformati una seconda volta, con una trasformata di Walsh-Hadamard. Così anche la parte più uniforme del blocco costa pochissimo.
Fin qui non si è perso quasi nulla. La perdita avviene adesso, nella quantizzazione. Ogni coefficiente viene diviso per un passo e arrotondato. Un passo grande significa pochi valori possibili, quindi molti coefficienti che diventano zero e un file piccolo. Un passo piccolo significa più fedeltà e più byte.
Il parametro di qualità che passate all'encoder, quel numero da zero a cento, alla fine decide proprio questi passi. VP8 ha centoventotto livelli di quantizzazione, e la qualità viene tradotta in uno di questi.
Siccome i coefficienti delle variazioni fini sono quelli che l'occhio nota meno, sono anche quelli che vengono sacrificati per primi. Il disegno a mano libera perde la grana, non la forma.
Un cielo uniforme e un prato pieno di fili d'erba non meritano lo stesso trattamento. Il cielo mostra subito ogni difetto; il prato nasconde gli errori nella sua confusione.
VP8 permette di dividere i macroblocchi in un massimo di quattro segmenti, ognuno con il proprio livello di quantizzazione e di filtro. L'encoder analizza l'immagine e assegna le zone ai segmenti. Nell'encoder di riferimento questa analisi si chiama modellazione spaziale del rumore, e il parametro che la regola si chiama sns. Più è alto, più l'encoder sposta byte dalle zone confuse alle zone lisce.
Quantizzare blocco per blocco ha un effetto collaterale ben noto: i bordi dei blocchi diventano visibili. È l'effetto a quadretti che conoscete dai JPEG troppo compressi.
VP8 include un filtro che, in fase di decodifica, ammorbidisce i confini tra i blocchi. È un filtro "in loop", cioè fa parte del processo: i blocchi già filtrati sono quelli usati per predire i successivi. L'encoder sceglie quanto filtrare, e lo potete regolare voi, o lasciare che sia l'encoder a cercare il valore migliore con l'opzione di filtro automatico.
Troppo filtro e l'immagine diventa pastosa, come un mosaico visto attraverso un vetro smerigliato. Troppo poco e tornano i quadretti.
I coefficienti quantizzati, i modi di predizione e tutte le scelte fatte vanno scritti nel file nel modo più compatto possibile. VP8 usa un codificatore aritmetico booleano: codifica ogni decisione sì o no usando una probabilità, e le decisioni molto probabili costano frazioni di bit.
Le probabilità sono scritte nell'intestazione dell'immagine e possono essere aggiornate rispetto a valori predefiniti. Più l'encoder lavora per stimarle bene, più il file diventa piccolo. È uno dei motivi per cui il parametro "metodo", da zero a sei, cambia sia la velocità sia la dimensione.
Conoscere la catena aiuta a riconoscere i difetti. I quadretti nelle zone lisce: troppa quantizzazione, poco filtro. Le sbavature di colore sui bordi saturi: il sottocampionamento, che sharp YUV attenua. Le bande nei gradienti, per esempio nei cieli al tramonto: otto bit e quantizzazione forte, e qui WebP non ha rimedi magici. L'effetto pastoso su pelle e tessuti: filtro e quantizzazione che cancellano la grana.
E c'è un caso in cui il disegno a mano libera è proprio la scelta sbagliata: screenshot, grafici, interfacce, testo. Colori netti e bordi precisi sono il peggior nemico di sottocampionamento e trasformata. Per quelli, come vedremo nel prossimo episodio, la tessera esatta funziona meglio.
Ricapitolando la catena: conversione in luminanza e crominanza con sottocampionamento, macroblocchi sedici per sedici, predizione dai blocchi vicini, trasformata del residuo, quantizzazione, segmenti, filtro e codifica aritmetica. Ogni anello corrisponde a un parametro dell'encoder, e adesso sapete quale.
Nel prossimo episodio cambiamo completamente tecnica. Lasciamo VP8 e la perdita, ed entriamo nel laboratorio della tessera esatta: la compressione senza perdita di WebP, un algoritmo pensato da zero. Ci sentiamo lì.