Deband Filter Algorithmus



  • Puuuuuuuuush



  • Habs mal selber probiert, damit meine GLSL Kenntnisse nicht ganz verrosten.
    Wenn man darüber hinweg schaut, dass das Bildformat nicht mehr ganz stimmt, lässt sich mein Ergebnis schon ganz gut sehen:

    http://img42.imageshack.us/img42/513/resultatdebanshader.png

    http://img42.imageshack.us/img42/513/resultatdebanshader.png

    Ich habe ein selektives Antialiasing mit weit auseinander liegenden Punkten genutzt.

    Zum einstellen sind mdRGB und sampleStep vorgesehen.

    uniform sampler2D texture0;
    
    void main(void)
    {
        float mdRGB = 0.02;
        float sampleStep = 12.0; //This is the sample step distance from the base pixel
    
        vec4 maxDifference = vec4(mdRGB, mdRGB, mdRGB, 1.0);
    
        //I cut the image size to 1024x1024
        float pixelStepX = 1.0 / 1024.0;
        float pixelStepY = 1.0 / 1024.0;
        float sampleStepX = pixelStepX * sampleStep;
        float sampleStepY = pixelStepY * sampleStep;
    
        vec2 tcord = gl_TexCoord[0].st;
    
    	vec4 cbase = texture2D(texture0, tcord);
    
    	vec4 c1  = texture2D(texture0, tcord + vec2(sampleStepX/2, 0));
    	vec4 c2  = texture2D(texture0, tcord + vec2(-sampleStepX/2, 0));
        vec4 c3  = texture2D(texture0, tcord + vec2(0, sampleStepY/2));
        vec4 c4  = texture2D(texture0, tcord + vec2(0, -sampleStepY/2));
    
        vec4 c11 = texture2D(texture0, tcord + vec2(sampleStepX, 0));
        vec4 c22 = texture2D(texture0, tcord + vec2(-sampleStepX, 0));
        vec4 c33 = texture2D(texture0, tcord + vec2(0, sampleStepY));
        vec4 c44 = texture2D(texture0, tcord + vec2(0, -sampleStepY));
    
        //Selective anti aliasing
        c1 = mix(cbase, c1, vec4(all(lessThan(abs(vec4(cbase - c1)), maxDifference))));
        c2 = mix(cbase, c2, vec4(all(lessThan(abs(vec4(cbase - c2)), maxDifference))));
        c3 = mix(cbase, c3, vec4(all(lessThan(abs(vec4(cbase - c3)), maxDifference))));
        c4 = mix(cbase, c4, vec4(all(lessThan(abs(vec4(cbase - c4)), maxDifference))));
    
        c11 = mix(cbase, c11, vec4(all(lessThan(abs(vec4(cbase - c11)), maxDifference))));
        c22 = mix(cbase, c22, vec4(all(lessThan(abs(vec4(cbase - c22)), maxDifference))));
        c33 = mix(cbase, c33, vec4(all(lessThan(abs(vec4(cbase - c33)), maxDifference))));
        c44 = mix(cbase, c44, vec4(all(lessThan(abs(vec4(cbase - c44)), maxDifference))));
    
    	gl_FragColor = (cbase + c1 + c2 + c3 + c4 + c11 + c22 + c33 + c44) / (1 + 4 + 4);
    }
    


  • Hui, danke! 🙂

    Wenn ich das richtig verstehe, in Worten:

    Du nimmst 4 Pixel, jeweils einen um "sampleStep" nach links, rechts, oben und unten vom original-Pixel verschoben.
    Dann nimmst du nochmal 4 Pixel nach dem selben Schema mit der halben Distanz.

    Und dann addierst du alle Farben zusammen, wobei du alle Farben die zu weit von der Farbe des original-Pixels entfernt sind, durch die Farbe des original-Pixels ersetzt.
    Und dann dividierst du halt durch 9, weils ja 9 Farben waren.

    Soweit korrekt?

    ----

    Ich hab' gestern selbst noch ein wenig gegoogelt, den da gefunden:

    Thread: http://forum.doom9.org/showthread.php?t=161411
    Beschreibung: https://raw.github.com/SAPikachu/flash3kyuu_deband/1.4.2/flash3kyuu_deband.txt

    Der Beschreibung der Filter-Parameter nach macht der das ziemlich ähnlich, nur dass er nur 4 Pixel nimmt, und die Distanz zufällig variiert.

    Was mich gleich zur nächsten Frage führt: kann man in Shadern irgendwie Zufallszahlen bekommen (ohne alles stark zu verlangsamen)? Oder wäre da die beste Variante eine "Zufallstextur" zu verwenden, wo man die Werte einfach ausliest (z.B. 256x256x8 und AddressMode = repeat)?

    ----

    Achja, Hintergrund (falls es jmd. interessiert): ich spiele mich mit dem Gedanken das ganze in den XBMC-Renderer einzubauen. Weil XBMC cool ist, und das so ziemlich die einzige Funktion ist, die mir beim Renderer abgeht. Wobei ich noch nicht nachgefragt habe ob seitens der Maintainer überhaupt interesse besteht, wollte erstmal wissen ob ich das überhaupt hinbekommen würde.



  • Richtig!

    Ich habe acht Samples verwendet, da man recht große Abstände benötigt um die großen Strukturen verlaufen zu lassen, dann aber die Artefakte an den Grenzen zu Kontrasten nicht gut genug aussahen.

    Ich habe mir mal eine "Zufallsfunktion" für GLSL ergoogeld.
    Vier Samples sehen auch gut aus mit Zufallsabstand.

    Von der Performance her sollte man es halt wie du beschrieben hast mit einer Rauschtextur realisieren.
    Und um nicht immer die Selben "Rauschwerte" auf der selben Bildschirmposition zu haben sollte man diese bei jedem neuen Renderbild zufällig Verschieben.

    uniform sampler2D texture0;
    
    float rand(vec2 co){
        return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
    }
    
    void main(void)
    {
        float mdRGB = 0.02;
        vec4 maxDifference = vec4(mdRGB, mdRGB, mdRGB, 1.0);
        float stepMin = 5.0;
        float stepMax = 20.0;
    
        //I cut the image size to 1024x1024
        float pixelStepX = 1.0 / 1024.0;
        float pixelStepY = 1.0 / 1024.0;
    
        vec2 tcord = gl_TexCoord[0].st;
    
        vec4 cbase = texture2D(texture0, tcord);
    
        float rndX = pixelStepX*(stepMin + (rand(tcord) * (stepMax-stepMin)));
        float rndY = pixelStepY*(stepMin + (rand(tcord) * (stepMax-stepMin)));
    
        vec4 c1  = texture2D(texture0, tcord + vec2(rndX, 0));
        vec4 c2  = texture2D(texture0, tcord + vec2(-rndX, 0));
        vec4 c3  = texture2D(texture0, tcord + vec2(0, rndY));
        vec4 c4  = texture2D(texture0, tcord + vec2(0, -rndY));
    
        //Selective anti aliasing
        c1 = mix(cbase, c1, vec4(all(lessThan(abs(vec4(cbase - c1)), maxDifference))));
        c2 = mix(cbase, c2, vec4(all(lessThan(abs(vec4(cbase - c2)), maxDifference))));
        c3 = mix(cbase, c3, vec4(all(lessThan(abs(vec4(cbase - c3)), maxDifference))));
        c4 = mix(cbase, c4, vec4(all(lessThan(abs(vec4(cbase - c4)), maxDifference))));
    
        gl_FragColor = (cbase + c1 + c2 + c3 + c4) / (1 + 4);
    }
    


  • Das ist ein interessantes Thema. Sehr viele (denke sogar der Grossteil) von selbst AAA-Schmieden verzichtet aus irgendeinem Grund auf derlei Sachen. Dabei würde es die Bildqualität enorm erhöhen.

    Das Problem ist, dass die paar bit die man üblicherweise für RGB benutzt für dunkle und helle Farben gleichverteilt sind, obwohl das menschliche Auge viel mehr Details in dunklen Farben erkennen kann. Deshalb fällt einem dieses Banding besonders stark bei dunklen Farbtönen auf.

    Ich habe vor kurzem dazu eine interessante Präsentation von einem Naughty Dog Engineprogrammierer im Bereich Grafik gesehen, wo er auf derartiges Zeug eingangen ist und erklärt hat, wie ihnen das bei Uncharted 3 geholfen hat.

    Mal schauen ob ich die wieder irgendwo finden kann.



  • Hmja, sehr cool.
    Danke herzlichst!

    Mal sehen ob ich mich aufraffen kann das auch wirklich einzubauen 🙂

    Achja: Dithering fehlt natürlich noch bei deinem Beispiel, aber das bekomm ich dann schon hin, das ist ja einigermassen trivial. Ich denke da lässt sich auch die Rauschetextur wiederverwenden, bzw. man könnte natürlich auch ne extra Dither-Textur machen (wenn man geordnet dithern will).



  • Warum baut man in Videos den eigendlich Dithering ein?
    Ich kenne das eigentlich nur aus Zeiten wo man mit 16 oder 256 Farben gearbeitet hat und nur so eine "größere Farbpalette" zur Verfügung stand.

    Ist aber recht einfach zu realisieren:

    uniform sampler2D texture0;
    
    float rand(vec2 co){
        return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
    }
    
    void main(void)
    {
        float mdRGB = 0.02;
        vec4 maxDifference = vec4(mdRGB, mdRGB, mdRGB, 1.0);
        float stepMin = 5.0;
        float stepMax = 20.0;
    
        //I cut the image size to 1024x1024
        float pixelStepX = 1.0 / 1024.0;
        float pixelStepY = 1.0 / 1024.0;
    
        vec2 tcord = gl_TexCoord[0].st;
    
        vec4 cbase = texture2D(texture0, tcord);
    
        float rndX = pixelStepX*(stepMin + (rand(tcord) * (stepMax-stepMin)));
        float rndY = pixelStepY*(stepMin + (rand(tcord) * (stepMax-stepMin)));
    
        vec4 c1  = texture2D(texture0, tcord + vec2(rndX, 0));
        vec4 c2  = texture2D(texture0, tcord + vec2(-rndX, 0));
        vec4 c3  = texture2D(texture0, tcord + vec2(0, rndY));
        vec4 c4  = texture2D(texture0, tcord + vec2(0, -rndY));
    
        //Selective anti aliasing
        c1 = mix(cbase, c1, vec4(all(lessThan(abs(vec4(cbase - c1)), maxDifference))));
        c2 = mix(cbase, c2, vec4(all(lessThan(abs(vec4(cbase - c2)), maxDifference))));
        c3 = mix(cbase, c3, vec4(all(lessThan(abs(vec4(cbase - c3)), maxDifference))));
        c4 = mix(cbase, c4, vec4(all(lessThan(abs(vec4(cbase - c4)), maxDifference))));
    
        vec4 minColor = min(cbase, min(c1, min(c2, min(c3, c4))));
        vec4 maxColor = max(cbase, max(c1, max(c2, max(c3, c4))));
    
        float ditherRnd = rand(tcord + vec2(0.33));
    
        gl_FragColor = (minColor*ditherRnd) + (maxColor*(1-ditherRnd));
    }
    


  • Naja... mit dem "deband" Filter entfernt man erstmal schön die Bänder, und durch die folgende Quantisierung auf 8 Bit pro Kanal holt man sie sich wieder zurück 🙂
    => also dithern.

    Speziell bei manchen Zeichentrick-Serien ist ohne Dithering nämlich das Banding in bestimmten Szenen deutlich sichtbar (meistens dunkle Szenen mit "langsamen" Farbübergängen). Und das auch ohne Sprünge > 1 im YUV Raum.

    Daher wurden auch Videostandards wie H.264 auf 9 bzw. 10 Bit pro Farbkanal erweitert. (Das ist ausnahmsweise mal eine Erweiterung die wirklich Sinn macht, nicht so wie 192/24 bei Audio-gedöns, wo kein Mensch den Unterschied zu 96/24 hören kann - und vermutlich nichtmal zu 48/24).


  • Mod

    TravisG schrieb:

    Das ist ein interessantes Thema. Sehr viele (denke sogar der Grossteil) von selbst AAA-Schmieden verzichtet aus irgendeinem Grund auf derlei Sachen. Dabei würde es die Bildqualität enorm erhöhen.

    was meinst du mit 'derlei sachen'?

    Das Problem ist, dass die paar bit die man üblicherweise für RGB benutzt für dunkle und helle Farben gleichverteilt sind, obwohl das menschliche Auge viel mehr Details in dunklen Farben erkennen kann. Deshalb fällt einem dieses Banding besonders stark bei dunklen Farbtönen auf.

    normalerweise benutzt man heutzutage ein HDR format zum rendern, das ist weit mehr als dein auge wahrnehmen koennte, am ende muss man natuerlich wieder auf 8bit tonemappen in den meisten faellen (auf manchen grakas kann man 10bit ausgeben, z.b. matrox).

    Ich habe vor kurzem dazu eine interessante Präsentation von einem Naughty Dog Engineprogrammierer im Bereich Grafik gesehen, wo er auf derartiges Zeug eingangen ist und erklärt hat, wie ihnen das bei Uncharted 3 geholfen hat.

    Mal schauen ob ich die wieder irgendwo finden kann.

    waere nett, bin immer noch gespannt was du mit 'derartiges' meinst 🙂



  • TravisG schrieb:

    Das Problem ist, dass die paar bit die man üblicherweise für RGB benutzt für dunkle und helle Farben gleichverteilt sind, obwohl das menschliche Auge viel mehr Details in dunklen Farben erkennen kann. Deshalb fällt einem dieses Banding besonders stark bei dunklen Farbtönen auf.

    Da ist nix gleichverteilt, man verwendet üblicherweise nen Gamma von 2.2.

    D.h. du hast bei dunklen Farben deutlich mehr Auflösung als bei hellen. Anders gesagt: der Helligkeitssprung (Energie/Fläche) von z.B. 11,11,11 auf 12,12,12 ist VIEL kleiner als der Sprung von 250,250,250 auf 251,251,251.

    Blöderweise ist das menschliche Auge um so viel empfindlicher bei dunklen Farben, dass im Bereich von ca. 0-10 IRE (0 bis 10% auf der Gamma 2.2 Skala) die Auflösung trotz Gamma 2.2 noch "zu gering" ist.

    Verschärft wird das ganze noch dadurch, dass viele nicht (gut) kalibrierte LCDs gerade bei dunklen Farben sehr ungenau sind, und dort einen "steileren" Anstieg haben als sie haben sollten. Wenn das LCD ein MVA/PVA ist, und man dann noch leicht schräg draufguckt (5-10° reichen schon), wird dieser Effekt nochmal ordentlich verstärkt.

    Langer Rede kurzer (Un)sinn: wenn man ne dunkle Stelle in einem Film auf einem heute üblichen Fernseher guckt (=grösstenteils LCDs mit MVA oder PVA Panel), dann springt einem das Banding nur so ins Gesicht.

    Und noch schnell dazu...

    Das ist ein interessantes Thema. Sehr viele (denke sogar der Grossteil) von selbst AAA-Schmieden verzichtet aus irgendeinem Grund auf derlei Sachen. Dabei würde es die Bildqualität enorm erhöhen.

    Weiss nicht ob das bei Spielen SO wichtig wäre. Bei manchen Spielen würde Dithering sicher nen sichtbaren Unterschied in manchen Szenen machen. "Bildqualität enorm erhöhen" halte ich im Allgemeinen aber für leicht übertrieben 😉


  • Mod

    hustbaer schrieb:

    TravisG schrieb:

    Das Problem ist, dass die paar bit die man üblicherweise für RGB benutzt für dunkle und helle Farben gleichverteilt sind, obwohl das menschliche Auge viel mehr Details in dunklen Farben erkennen kann. Deshalb fällt einem dieses Banding besonders stark bei dunklen Farbtönen auf.

    Da ist nix gleichverteilt, man verwendet üblicherweise nen Gamma von 2.2.
    ...
    Blöderweise ist das menschliche Auge um so viel empfindlicher bei dunklen Farben, dass im Bereich von ca. 0-10 IRE (0 bis 10% auf der Gamma 2.2 Skala) die Auflösung trotz Gamma 2.2 noch "zu gering" ist.

    das gamma kommt durch die crt monitore (und das lcds das wegen der kompatibilitaet emulieren). das hat nicht wirklich was mit dem auge zu tun, wenn du linear die analoge signalstaerke bei input eines alten crt steigern wuerdest, haettest du die 2.2 gamma curve.

    [edit]hmm, wikipedia behauptet es liegt am auge, aber viele paper die ich las behaupten es liegt am phosphor von monitoren?
    zudem behauptet wikipedia "Gamma encoding of floating point images is not required (and may be counterproductive) because the floating point format already provides a pseudo-logarithmic encoding." was ein wenig strange is, zwar ist float von der genaugikeit her logarithmic, aber das hat nichts mit der ausgabe zum monitor zu tun.[/edit]



  • @rapso
    Ich glaube dass die alten CRT-Schirme der Grund sind warum von Anfang an nicht linear aufgezeichnet wurde. Und der Umstand dass Gamma 2.2 sich zumindest besser als lineare Kodierung mit dem deckt, was das menschliche Auge macht, wird vermutlich der Grund sein, warum auch heute noch fast überall Gamma 2.2 verwendet wird. (Bzw. der Grund für den genauen Wert, also 2.2 und nicht 1.8 oder 2.0 oder sonstwas, ist vermutlich, dass es Windows-Standard war, und es als sRGB standardisiert wurde einfach viel mehr Windows-Monitore gab als Mac-Monitore *g*. Vom technischen Standpuntk aus betrachtet hätte vermutlich Gamma 2.0 mehr Sinn gemacht, weil viel viel einfacher umzurechnen.)

    Und mit der Floating-Point Geschichte ist vermutlich gemeint: wenn man Floats statt Integer verwendet, dann besteht keine Notwendigkeit mehr das Bild mit Gamma 2.2 im Speicher zu halten, als Datei zu speichern bzw. zu bearbeiten.
    Gamma 2.2 hat ja auch einige Nachteile. z.B. dass man das Bild dann für fast jede Operation erstmal linearisieren, bearbeiten und dann wieder zurückwandeln müsste. Darauf wird leider all zu oft verzichtet, siehe z.B.:
    http://www.4p8.com/eric.brasseur/gamma.html

    Dass man es dann zur Ausgabe wieder in das Format bringen muss, das das Display-Device erwartet (also fast meistens Gamma 2.2), ist natürlich klar.

    ----

    Was mich immer wieder wundert, ist dass die Gamma-Geschichte so viele Leute nicht wissen/kennen/verstehen. Klar, ein Maurer muss das nicht wissen, aber von Grafikern hätte ich es mir erwartet. Trotzdem wissen es viele nicht 🙂



  • Soooooo...

    Ich hab' mal mit dem XBMC Jungs Kontakt aufgenommen. Die wären soweit auch interessiert.

    Als nächstes hab' ich einen Test mit MediaPlayer Classic gemacht - der kann ja beliebige Shader im Renderer laufen lassen (so lange diese keine zusätzlichen Texturen als Input brauchen).

    Dummerweise hat sich dabei gezeigt, dass die Performance enorm einbricht, wenn man mit zufälligen Offsets aus der Textur liest. Ein Problem das ich irgendwie fast erwartet habe - hab' mir nur gedacht "mach dir keinen Kopf bevor du nicht weisst dass es wirklich zu langsam ist". Naja, jetzt weiss ich es 🙂

    Bei 5-10 Pixel Radius geht alles noch halbwegs, darüber wird es dann wirklich langsam. Mit meiner Grafikkarte (G210) degradiert das Video zur Diashow.
    Zugegeben, ne G210 ist nicht die flotteste, aber viele HTPCs verwenden ION Boards, und es wäre natürlich sehr wünschenswert wenn der Spass auch mit ION/ION2 funktionieren würde.

    Ich wäre also über weitere Vorschläge dankbar, wie man sowas GPU schonend umsetzen kann.



  • Hmm... hast du es jetzt im media player classic noch mit der "GLSL-Zufallsfunktion" getestet? Weil du sagst, dass du keine extra Texturen mitgeben kannst, und meines Verständnisses nach somit keine Rauschtextur möglich ist.
    Weil die Zufallsfunktion auch nicht ganz ohne Performance Einbußen sein wird.

    Und wenn es von der Performance her nur an dem zufälligem Lesezugriff gehen sollte, würde ich vorschlagen dass du mal meinen ersten Shader mit den 8 Prüfpunkten ausprobierst.
    Die Dithering-Erzeugung kann man dort auch noch einbauen. Der benötigte Zufall würde dann ja nicht mehr die Lesezugriffe beeinflussen.



  • Ja, ich hab' mit dem frac(sin())-Zufall getestet. Der bleibt sich ja aber immer gleich, d.h. wenn ich mit Radius=3 akzeptable Geschwindigkeit hab und mit Radius=50 Diashow, dann liegts mal nicht an der Zufalls-Funktion.

    Und die erste Variante... naja. Klar wird die schneller laufen. Bloss die macht keine schönen Bilder 🙂

    Wobei ich deinen Shader auch leicht modifiziert hatte, und zwar was die Koordinaten angeht. Hab aber im Moment grad schlecht Zeit, ich melde mich wenn ich mehr weiss 🙂



  • Dann wird es wie du sagtest der zufällige Zugriff sein.

    Was heißt hier mein erster Versuch macht keine schönen Bilder? 😞
    Aus deinem Referenzbild hat mir der erste Shader schon akzeptable Ergebnisse geliefert und wenn man dort noch Dithering einbaut sollte es noch besser ausschauen.
    Oder hast du diesen schon selber ausprobiert und bei anderen Bildern deutlich schlechtere Ergebnisse erhalten?

    Ich habe jetzt leider nur die eine Grafikkarte hier zum Testen. Denn ich hätte noch eine Idee wie man den Shader umbauen könnte um die Performance zu retten.
    Dazu würde ich die 4 Linienbereich ganz auslesen welche als Prüfpunkte in frage kommen.

    Das währen
    (Byte pro Pixel)(Länge einer Linie)(Anzahl Linien)
    4 * (20-5) * 4 = 240 Byte

    Ich weiß nicht wie viel Cache man pro GPU Kern bei dem ION erwarten darf.
    Zu meiner Radeon 5770 habe ich z.B. 8 KiB Cache pro SIMD + 16 KiB Shared pro Block gefunden. Und die ist schon gehobene Mittelklasse.

    Wenn das aber alles in den Cache eines GPU Kernes passen sollte, anschließend einfach aus dieser Shadervariable die Werte zufällig herauspicken.

    Übrigens, das ist alles reine Theorie! 😛



  • Hmmmmm...

    Ich weiss auch nicht wie das mit ION/ION2 aussieht. Ich glaube nur zu wissen dass meine G210 eng verwandt mit nem ION2 ist. Also wenns auf der nicht gut läuft, dann stehen die Chancen mit dem ION2 vermutlich auch nicht gut.

    Und Performance-Testen mit Media-Player Classic ist auch nicht das Wahre, ich fürchte da muss wohl ein kleines Testprogramm her.

    Was den Cache angeht: da müsste man wohl genau wissen wie Grafikkarten so rendern tun, und wie das mit den Texture-Fetches läuft.
    Ich kann da nur Vermutungen anstellen.

    z.B. vermute ich dass die Grafikkarte immer kleine Quadrate aus der Textur fetchen tut, vielleicht 4x4 oder 8x8 Texel. Möglicherweise tut die Texturing-Unit dann auch prefetchen. Aber alles nur geraten.

    Fakt ist: ich kenn' mich mit Shader-Optimieren so-gut-wie gar nicht aus 🙂
    Was mich wieder zum Testprogramm bringt: wenn ich da schnell & einfach rumprobieren kann, an ein paar Werten schrauben etc., und schön ablesbare Zahlen als Ergebnis bekomme kann das vielleicht was werden. So, einfach ins Blaue geraten vermutlich weniger 🙂


Anmelden zum Antworten