Qual è un modo corretto per bloccare il rumore di dithering?


9

Quando si riduce la profondità del colore e il dithering con un rumore a 2 bit (con n =] 0,5,1,5 [e output = floor (input * (2 ^ bit-1) + n)), le estremità dell'intervallo di valori (input 0.0 e 1.0 ) sono rumorosi. Sarebbe desiderabile averli di colore solido.

Esempio: https://www.shadertoy.com/view/llsfz4

Gradiente di rumore (sopra è uno screenshot dello shadertoy, raffigurante una sfumatura ed entrambe le estremità che dovrebbero essere rispettivamente bianche e nere solide, ma invece sono rumorose)

Naturalmente il problema può essere risolto semplicemente comprimendo l'intervallo di valori in modo che le estremità vengano sempre arrotondate a valori singoli. Questo sembra un po 'un trucco, e mi chiedo se c'è un modo per implementare questo "correttamente"?


Per qualche motivo lo shadertoy non è stato eseguito sul mio browser. Potresti pubblicare una / alcune semplici immagini per dimostrare cosa intendi?
Simon F,

1
Non dovrebbe essere più simile a n =] - 1, 1 [?
JarkkoL,

@JarkkoL Bene, la formula per convertire il numero in virgola mobile in intero è output = floor (input * intmax + n), dove n = 0,5 senza rumore, perché si desidera (ad esempio)> = 0,5 per arrotondare per eccesso, ma <0,5 per difetto. Ecco perché il rumore è "centrato" a 0,5.
hotmultimedia

@SimonF aggiunta immagine dello shadertoy
hotmultimedia

1
Sembra che stavi troncando l'output anziché arrotondarlo (come fanno le GPU) - arrotondando invece, almeno ottieni i bianchi corretti: shadertoy.com/view/MlsfD7 (immagine: i.stack.imgur.com/kxQWl.png )
Mikkel Gjoel,

Risposte:


8

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:

uscite - 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).

inserisci qui la descrizione dell'immagine

Guardare un grafico fornisce un po 'più di comprensione:

inserisci qui la descrizione dell'immagine (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):

inserisci qui la descrizione dell'immagine

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 ).

inserisci qui la descrizione dell'immagine

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:

inserisci qui la descrizione dell'immagine

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: inserisci qui la descrizione dell'immagine

Riferimenti


È sorprendente che il lerp ai bordi dia un risultato perfetto. Mi aspetterei almeno una piccola deviazione: P
Alan Wolfe il

Sì, sono stato anche sorpreso positivamente :) Credo che funzioni perché stiamo ridimensionando linearmente la dither-magnitudo, allo stesso ritmo il segnale sta diminuendo. Quindi almeno la scala corrisponde ... ma concordo sul fatto che sia interessante che la fusione diretta delle distribuzioni non abbia effetti collaterali negativi.
Mikkel Gjoel,

@MikkelGjoel Sfortunatamente, la tua convinzione è errata a causa di un bug nel tuo codice. Hai riutilizzato lo stesso RNG per entrambi dithertrie dithernorminvece di uno indipendente. Dopo aver esaminato tutta la matematica e cancellato tutti i termini, scoprirai che non stai affatto scherzando! Al contrario, il codice si comporta come un limite sostanziale v < 0.5 / depth || v > 1 - 0.5/depth, passando istantaneamente alla distribuzione uniforme lì. Non che ti tolga il bel dithering che hai, è solo inutilmente complicato. Risolvere il bug è in realtà male, finirai con un dithering peggiore. Basta usare un taglio netto.
orlp

Dopo aver scavato ancora più a fondo ho trovato un altro problema nel tuo shadertoy in cui non esegui la correzione gamma mentre fai la media dei campioni (fai una media nello spazio sRGB che non è lineare). Se gestisci la gamma in modo appropriato, scopriamo che purtroppo non abbiamo ancora finito. Dobbiamo modellare il nostro rumore per gestire la correzione gamma. Ecco uno shadertoy che mostra il problema: shadertoy.com/view/3tf3Dn . Ho provato un sacco di cose e non sono riuscito a farlo funzionare, quindi ho pubblicato una domanda qui: computergraphics.stackexchange.com/questions/8793/… .
orlp

3

Non sono sicuro di poter rispondere pienamente alla tua domanda, ma aggiungerò alcuni pensieri e forse potremo arrivare a una risposta insieme :)

In primo luogo, il fondamento della domanda non è chiaro per me: perché ritieni opportuno avere un bianco / nero pulito quando ogni altro colore ha rumore? Il risultato ideale dopo il dithering è il segnale originale con un rumore completamente uniforme. Se il bianco e nero sono diversi, il rumore diventa dipendente dal segnale (il che potrebbe andare bene, dal momento che accade comunque quando i colori sono bloccati).

Detto questo, ci sono situazioni in cui il rumore nei bianchi o nei neri pone un problema (non sono a conoscenza di casi che richiedono che sia il bianco che il nero siano "puliti"): quando si esegue il rendering di una particella additivamente miscelata come un quad con una trama, non si desidera aggiungere rumore nell'intero quad, poiché ciò si manifesterebbe anche al di fuori della trama. Una soluzione è compensare il rumore, quindi piuttosto che aggiungere [-0,5; 1,5 [si aggiunge [-2,0; 0,0 [(cioè sottrarre 2 bit di rumore). Questa è una soluzione abbastanza empirica, ma non sono consapevole di un approccio più corretto. Pensandoci, probabilmente vorrai anche aumentare il tuo segnale per compensare l'intensità perduta ...

In qualche modo correlato, Timothy Lottes ha tenuto una conferenza GDC sulla modellatura del rumore nelle parti dello spettro in cui è maggiormente necessario, riducendo il rumore nella parte più luminosa dello spettro: http://32ipi028l5q82yhj72224m8j-wpengine.netdna-ssl.com/wp- content / uploads / 2016/03 / GdcVdrLottes.pdf


(scusate se premo invio per errore e il limite di tempo di modifica è scaduto) Il caso d'uso nell'esempio è una di quelle situazioni in cui avrebbe importanza: il rendering di un'immagine in scala di grigi in virgola mobile su un dispositivo di visualizzazione a 3 bit. Qui l'intensità cambia notevolmente cambiando semplicemente l'LSB. Sto cercando di capire se esiste un "modo corretto" per far sì che i valori finali vengano mappati su colori solidi, come comprimere l'intervallo di valori e saturare i valori finali. E qual è la spiegazione matematica di ciò? Nella formula di esempio, il valore di input di 1.0 non produce output con una media di 7, ed è quello che mi disturba.
hotmultimedia dal

1

Ho semplificato l'idea di Mikkel Gjoel di dithering con rumore triangolare a una semplice funzione che necessita solo di una singola chiamata RNG. Ho rimosso tutti i bit non necessari, quindi dovrebbe essere abbastanza leggibile e comprensibile cosa sta succedendo:

// Dithers and quantizes color value c in [0, 1] to the given color depth.
// It's expected that rng contains a uniform random variable on [0, 1].
uint dither_quantize(float c, uint depth, float rng) {
    float cmax = float(depth) - 1.0;
    float ci = c * cmax;

    float d;
    if (ci < 0.5 || ci >= cmax - 0.5) {
        // Uniform distribution on [-0.5, 0.5] for edges.
        d = rng - 0.5;
    } else {
        // Symmetric triangular distribution on [-1, 1].
        d = (rng < 0.5) ? sqrt(2.0 * rng) - 1.0 : 1.0 - sqrt(2.0 - 2.0*rng);
    }

    return uint(clamp(ci + d + 0.5, 0.0, cmax));
}

Per l'idea e il contesto ti rimando alla risposta di Mikkel Gjoel.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.