Jaki jest właściwy sposób na ograniczenie szumu?

9

Podczas zmniejszania głębi kolorów i ditheringu za pomocą 2-bitowego szumu (przy n =] 0,5,1,5 [i wyjściowego = floor (wejście * (2 ^ bity-1) + n)), końce zakresu wartości (wejścia 0,0 i 1,0 ) są głośne. Pożądane byłoby, aby miały jednolity kolor.

Przykład: https://www.shadertoy.com/view/llsfz4

Gradient hałasu (powyżej jest zrzut ekranu shadertoy, przedstawiający gradient i oba końce, które powinny być odpowiednio białe i czarne, ale zamiast tego są głośne)

Problem można oczywiście rozwiązać, po prostu kompresując zakres wartości, aby końce były zawsze zaokrąglane do pojedynczych wartości. To trochę hack i zastanawiam się, czy istnieje sposób na wdrożenie tego „poprawnie”?

hotmultimedia
źródło
Z jakiegoś powodu shadertoy nie działał w mojej przeglądarce. Czy możesz zamieścić kilka prostych obrazków, aby zademonstrować, co masz na myśli?
Simon F
1
Czy nie powinno być bardziej jak n =] - 1, 1 [?
JarkkoL
@JarkkoL Otóż formuła konwersji zmiennoprzecinkowej liczby całkowitej jest wyprowadzana = floor (input * intmax + n), gdzie n = 0,5 bez szumu, ponieważ chcesz (na przykład)> = 0,5 zaokrąglić w górę, ale <0,5 w dół. Dlatego hałas jest „wyśrodkowany” na poziomie 0,5.
hotmultimedia
@SimonF dodał obraz shadertoy
hotmultimedia
1
Wygląda na to, że przycinałeś dane wyjściowe zamiast zaokrąglać je (tak jak robią to procesory graficzne) - zamiast tego zaokrąglasz, przynajmniej otrzymujesz odpowiednie białe: shadertoy.com/view/MlsfD7 (image: i.stack.imgur.com/kxQWl.png )
Mikkel Gjoel,

Odpowiedzi:

8

TL; DR: dithering trójkątny-pdf 2 * 1LSB przerywa edgecases w 0 i 1 z powodu zaciśnięcia. Rozwiązaniem jest lerp do jednobitowego ditheringu w tych edgecases.

Dodaję drugą odpowiedź, ponieważ okazało się to nieco bardziej skomplikowane niż początkowo myślałem. Wygląda na to, że ten problem to „DO ZROBIENIA: wymaga mocowania?” w moim kodzie, odkąd przeszedłem ze znormalizowanego na trójkątny dithering ... w 2012 roku. Dobrze jest w końcu na to spojrzeć :) Pełny kod dla rozwiązania / obrazów używanych w całym poście: https://www.shadertoy.com/view/llXfzS

Po pierwsze, oto problem, na który patrzymy, przy kwantyzacji sygnału do 3 bitów przy ditheringu trójkątnym-pdf 2 * 1LSB:

wyjścia - zasadniczo to, co pokazała hotmultimedia.

Zwiększając kontrast, efekt opisany w pytaniu staje się widoczny: Moc wyjściowa nie jest średnia do czerni / bieli w krawędziach (i faktycznie wykracza znacznie poza 0/1 przed zrobieniem tego).

wprowadź opis zdjęcia tutaj

Spojrzenie na wykres zapewnia nieco więcej wglądu:

wprowadź opis zdjęcia tutaj (szare linie oznaczają 0/1, również szary jest sygnałem, który próbujemy wyprowadzić, żółta linia jest średnią z rozłożonego / skwantowanego wyjścia, czerwona to błąd (średni sygnał)).

Co ciekawe, nie tylko średnia moc wyjściowa nie wynosi 0/1 na granicach, ale także nie jest liniowa (prawdopodobnie z powodu trójkątnego pdf hałasu). Patrząc na dolny koniec, intuicyjne jest zrozumienie, dlaczego sygnał wyjściowy jest rozbieżny: w miarę jak sygnał przerywany zaczyna zawierać wartości ujemne, zaciskanie na wyjściu zmienia wartość dolnych odcinków wyjściowych (tj. Wartości ujemne), tym samym zwiększenie wartości średniej. Ilustracja wydaje się być w porządku (jednolity, symetryczny dither 2LSB, średnia wciąż na żółto):

wprowadź opis zdjęcia tutaj

Teraz, jeśli użyjemy znormalizowanego ditheringu 1LSB, nie będzie żadnych problemów na skrzynkach brzegowych, ale wtedy oczywiście tracimy ładne właściwości trójkątnego ditheringu (patrz np. Ta prezentacja ).

wprowadź opis zdjęcia tutaj

(Pragmatycznym, empirycznym) rozwiązaniem (hack) jest więc powrót do [-0,5; 0,5 [dithering jednolity dla 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) );

Które naprawia krawędzie przy jednoczesnym zachowaniu nienaruszonego trójkątnego ditheringu dla pozostałego zakresu:

wprowadź opis zdjęcia tutaj

Więc nie odpowiadaj na twoje pytanie: nie wiem, czy istnieje bardziej solidne matematycznie rozwiązanie, i równie chętnie wiem, co zrobili Mistrzowie Przeszłości :) Do tego czasu przynajmniej mamy straszny hack, aby utrzymać funkcjonowanie naszego kodu.

EDYCJA
Prawdopodobnie powinienem uwzględnić sugestię obejścia podaną w pytaniu, dotyczącą po prostu kompresji sygnału. Ponieważ średnia nie jest liniowa w końcowych przypadkach, zwykła kompresja sygnału wejściowego nie daje idealnego wyniku - chociaż naprawia punkty końcowe: wprowadź opis zdjęcia tutaj

Bibliografia

Mikkel Gjoel
źródło
To niesamowite, że lerp na krawędziach daje perfekcyjnie wyglądający efekt. Spodziewałbym się co najmniej małego odchylenia: P
Alan Wolfe
Tak, też byłem pozytywnie zaskoczony :) Wierzę, że to działa, ponieważ zmniejszamy liniowo jasność, z tą samą prędkością sygnał maleje. Tak więc przynajmniej skala pasuje ... ale zgadzam się, że interesujące jest to, że bezpośrednie mieszanie dystrybucji wydaje się nie mieć żadnych negatywnych skutków ubocznych.
Mikkel Gjoel,
@MikkelGjoel Niestety Twoje przekonanie jest nieprawidłowe z powodu błędu w kodzie. Użyłeś ponownie tego samego RNG dla obu dithertrii dithernormzamiast niezależnego. Gdy przejdziesz przez całą matematykę i anulujesz wszystkie warunki, przekonasz się, że w ogóle nie trwasz! Zamiast tego kod działa jak twardy punkt odcięcia v < 0.5 / depth || v > 1 - 0.5/depth, natychmiast przełączając się na jednolity rozkład. Nie oznacza to, że odbiera to przyjemne dithering, ale jest to niepotrzebnie skomplikowane. Naprawienie błędu jest w rzeczywistości złe, skończysz gorzej. Po prostu użyj twardego odcięcia.
orlp
Po głębszym kopaniu znalazłem inny problem w twoim shadertoy, w którym nie korygujesz gamma podczas uśredniania próbek (przeciętnie w przestrzeni sRGB, która nie jest liniowa). Jeśli odpowiednio poradzisz sobie z gamma, okaże się, że niestety jeszcze nie skończyliśmy. Musimy kształtować hałas, aby radzić sobie z korekcją gamma. Oto shadertoy pokazujący problem: shadertoy.com/view/3tf3Dn . Próbowałem wielu rzeczy i nie udało mi się go uruchomić, więc zadałem pytanie tutaj: computergraphics.stackexchange.com/questions/8793/… .
lub
3

Nie jestem pewien, czy potrafię w pełni odpowiedzieć na twoje pytanie, ale dodam kilka przemyśleń i być może uda nam się wspólnie znaleźć odpowiedź :)

Po pierwsze, podstawa pytania jest dla mnie trochę niejasna: dlaczego uważasz, że pożądana jest czysta czerń / biel, gdy każdy inny kolor ma szum? Idealnym rezultatem po ditheringu jest twój oryginalny sygnał z całkowicie jednolitym szumem. Jeśli czerń i biel są różne, szum staje się zależny od sygnału (co może być w porządku, ponieważ dzieje się tak, gdy kolory są i tak zaciśnięte).

To powiedziawszy, istnieją sytuacje, w których hałas w bieli lub czerni stanowi problem (nie jestem świadomy przypadków użycia, które wymagają, aby zarówno czerń, jak i biel były jednocześnie „czyste”): Podczas renderowania dodatnio mieszanej cząstki jako poczwórnej tekstury, nie chcesz, aby hałas był dodawany w całym kwadracie, ponieważ pojawiłoby się to również poza teksturą. Jednym z rozwiązań jest wyrównanie szumu, a nie dodawanie [-0,5; 1,5 [dodajesz [-2,0; 0,0 [(tj. Odejmuje 2 bity hałasu). To dość empiryczne rozwiązanie, ale nie jestem świadomy bardziej poprawnego podejścia. Myśląc o tym, prawdopodobnie chcesz również wzmocnić swój sygnał, aby zrekompensować utraconą intensywność ...

W pewien sposób Timothy Lottes wygłosił przemówienie GDC na temat kształtowania hałasu w częściach widma, w których jest on najbardziej potrzebny, zmniejszając hałas na jasnym końcu widma: http://32ipi028l5q82yhj72224m8j-wpengine.netdna-ssl.com/wp- content / uploads / 2016/03 / GdcVdrLottes.pdf

Mikkel Gjoel
źródło
(przepraszam, że nacisnąłem klawisz Enter i upłynął limit czasu edycji). Przykładem zastosowania w tym przykładzie są sytuacje, w których miałoby to znaczenie: renderowanie zmiennoprzecinkowego obrazu w skali szarości na 3-bitowym urządzeniu wyświetlającym. Tutaj intensywność zmienia się znacznie po prostu poprzez zmianę LSB. Próbuję zrozumieć, czy istnieje jakikolwiek „prawidłowy sposób” odwzorowania wartości końcowych na kolory jednolite, na przykład kompresja zakresu wartości i nasycenie wartości końcowych. A jakie jest matematyczne wytłumaczenie tego? W przykładowej formule wartość wejściowa wynosząca 1,0 nie daje wyników średnio 7, a to mnie martwi.
hotmultimedia,
1

Uprościłem pomysł Mikkel Gjoel dotyczący ditheringu z trójkątnym hałasem do prostej funkcji, która wymaga tylko jednego wywołania RNG. Usunąłem wszystkie niepotrzebne fragmenty, więc to, co się dzieje, powinno być dość czytelne i zrozumiałe:

// 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));
}

Dla pomysłu i kontekstu odsyłam cię do odpowiedzi Mikkela Gjoela.

orlp
źródło