TL; DR: il dithering triangolare in pdf 2 * 1LSB si rompe in un caso a 0 e 1 a causa del bloccaggio. Una soluzione è quella di aggrapparsi a una dithering uniforme a 1 bit in quei bordi.
Sto aggiungendo una seconda risposta, visto che si è rivelato un po 'più complicato di quanto pensassi inizialmente. Sembra che questo problema sia stato un "TODO: ha bisogno di essere bloccato?" nel mio codice da quando sono passato dal dithering normalizzato a quello triangolare ... nel 2012. Mi sento bene a guardarlo finalmente :) Codice completo per soluzione / immagini utilizzate in tutto il post: https://www.shadertoy.com/view/llXfzS
Prima di tutto, ecco il problema che stiamo osservando, quando si quantizza un segnale a 3 bit con dithering triangolare in pdf 2 * 1LSB:
- essenzialmente ciò che ha mostrato hotmultimedia.
Aumentando il contrasto, l'effetto descritto nella domanda diventa evidente: l'output non raggiunge la media in bianco e nero nelle edgecase (e in realtà si estende ben oltre lo 0/1 prima di farlo).
Guardare un grafico fornisce un po 'più di comprensione:
(le linee grigie indicano 0/1, anche in grigio è il segnale che stiamo cercando di emettere, la linea gialla è la media dell'uscita retinata / quantizzata, il rosso è l'errore (media del segnale)).
È interessante notare che non solo l'output medio non è 0/1 ai limiti, ma non è anche lineare (probabilmente a causa del pdf triangolare del rumore). Osservando l'estremità inferiore, ha un senso intuitivo il motivo per cui l'uscita diverge: quando il segnale retinato inizia a includere valori negativi, il serraggio sull'uscita cambia il valore delle parti retinate inferiori dell'uscita (cioè i valori negativi), quindi aumentando il valore della media. Un'illustrazione sembra essere in ordine (dithering 2LSB uniforme, simmetrica, media ancora in giallo):
Ora, se usiamo solo un dithering normalizzato da 1LSB, non ci sono problemi ai bordi dei casi, ma ovviamente perdiamo le belle proprietà del dithering triangolare (vedi ad esempio questa presentazione ).
Una soluzione (pragmatica, empirica) (hack), quindi, è di tornare a [-0,5; 0,5 [dithering uniforme per l'edgecase:
float dithertri = (rnd.x + rnd.y - 1.0); //note: symmetric, triangular dither, [-1;1[
float dithernorm = rnd.x - 0.5; //note: symmetric, uniform dither [-0.5;0.5[
float sizt_lo = clamp( v/(0.5/7.0), 0.0, 1.0 );
float sizt_hi = 1.0 - clamp( (v-6.5/7.0)/(1.0-6.5/7.0), 0.0, 1.0 );
dither = lerp( dithernorm, dithertri, min(sizt_lo, sizt_hi) );
Il che risolve le edgecase mantenendo intatto il dithering triangolare per l'intervallo rimanente:
Quindi, per non rispondere alla tua domanda: non so se esiste una soluzione matematicamente più solida e sono altrettanto desideroso di sapere cosa hanno fatto i Masters of Past :) Fino ad allora, almeno abbiamo questo orribile hack per mantenere il nostro codice funzionante.
EDIT
Probabilmente dovrei coprire il suggerimento-soluzione fornito in The Question, sulla semplice compressione del segnale. Poiché la media non è lineare nelle edgecase, la semplice compressione del segnale di input non produce un risultato perfetto, sebbene risolva gli endpoint:
Riferimenti