Dithering/Feholerstreuung bei Imagequantisierung zu 8bit indexed
-
Ich biete in meinem Programm das Umwandeln zu 256 Farben an. Das Projekt findet sich hier: http://rebenstudio-it.ch/QuellcodenetBildbearbeitungsprogramm.zip
Weiss jemand wie ich beim "Umwandeln in 256 Farben" Dithering mit einbeziehen kann? Es wird zwar durch den OctreeQuantizer eine gute Palette erzeugt, aber da keine Fehlerstreuung eingesetzt wird sehen die Bilder etwas wie Gemälde aus...
-
Weiss jemand wie ich beim "Umwandeln in 256 Farben" Dithering mit einbeziehen kann?
Ja, z.B. über Error-Diffusion.
http://en.wikipedia.org/wiki/Error_diffusion
http://en.wikipedia.org/wiki/Floyd_SteinbergDie Artikel gehen zwar von nur 2-Farben bzw. Paletten mit fixen Abständen (z.B. 16 Graustufen) aus, aber der Algorithmus lässt sich auch auf komplett freie Paletten anwenden - es ändert sich nur die Funktion mittels derer du die Farbe für den aktuellen Pixel aus der Palette auswählst.
Was in den Artikeln nicht erwähnt ist: du solltest mit Gamma 1.0 dithern. Also wandel das Bild vor dem dithern z.B. in float-Darstellung mit Gamma 1.0 um. Bei einer 256 Farben Palette wahrscheinlich nicht wirklich nötig, aber je grösser die "Abstände" zwischen den Farben der Palette sind, desto wichtiger dass man mit linearen Helligkeiten rechnet.
-
Danke; da ist schonmal ein Code in C.
Doch wie "verbinde" ich den mit dem OctreeQuantizer? Diese tiefergehenden Funktionen verstehe ich leider zuwenig um sowas einfach implementieren zu können, das ist noch etwas zu hoch; es muss ja während dem Quantisieren geschehen. Gibt es eine fertige Klasse dazu?
-
Der Quantisierungvorgang bildet jede Quellfarbe Deines Bildes auf eine von "n" (zb 256) moeglichen Zielfarben mit moeglichst kleiner Abweichung ab und erzeugt damit eine "optimale" Palette.
Ohne Dithering wird jede Farbe durch den Farbindex ersetzt der der Originalfarbe am naechsten liegt.
Fuer Dithering brauchst Du jeweils zwei Indizes, einen ober- und einen unterhalb der geforderten Farbe. Der Ditheralgorithmus entscheidet lediglich jeweils, ob die eine oder die andere Farbe verwendet werden soll und laesst ggf die tatsaechliche Abweichung in die umliegenden Pixel einfliessen.
-
Ja auf 256 Farben wird das Bild reduziert, die Palette ist ziemlich optimal.. 2 Indizes also die nächste Farbe in der Palette unterhalb und oberhalb der geforderten Farbe? Dachte das braucht eine riesige Umstellung in der Quantizer-Funktion! Aber es kann ja gut sein dass die Farbe nicht aus den 2 umliegenden Farben kombiniert werden kann (kommt ja auch immer drauf an was dort liegt)
-
Fuer Dithering brauchst Du jeweils zwei Indizes, einen ober- und einen unterhalb der geforderten Farbe. Der Ditheralgorithmus entscheidet lediglich jeweils, ob die eine oder die andere Farbe verwendet werden soll und laesst ggf die tatsaechliche Abweichung in die umliegenden Pixel einfliessen.
Nein. Für Error-Diffusion Dithering braucht man auch nur die ganz normale "wähle die nächste Farbe" Funktion. Man braucht weder eine speziell angepasste Palette noch sonst irgendwas besonderes. Man braucht auch keine "zwei Farben die man mischen kann" -- das Auswählen der passenden Farben die "zusammengemischt" werden erfolgt ganz von selbst durch das Verteilen des Fehlers.
Und man muss auch nix in der Funktion ändern/anpassen die den "besten Farbindex" findet.
Im Prinzip bleibt alles gleich, nur dass man nach dem Auswählen der "optimalen" Farbe den dadurch entstandenen Fehler ausrechnet, und diesen auf die Nachbarpixel verteilt.
Beispiel (1 Dimensional, graustufen):
// ohne dithern: for (int i = 0; i < 100; i++) { out[i] = select_color(in[i]); } // mit dithern: for (int i = 0; i < 100; i++) { out[i] = select_color(in[i]); // fehler ausrechnen int original = in[i]; int quantized = palette_lookup(out[i]); // genaue farbe des farbindex nachschlagen int error = original - quantized; // differenz "original - output" ausrechnen // fehler verteilen in[i + 1] += error; }
Das Ganze auf mehrere Kanäle auszuweiten sollte kein Problem darstellen, beim Fehler Ausrtechnen/Verteilen behandelt man die drei Kanäle einfach unabhängig voneinander.
Es auf zwei Dimensionen auszuweiten sollte mit Hilfe der Wikipedia-Artikel auch einfach sein. Das einzige was da dazukommt, ist, dass man den Fehler nicht einfach zu 100% auf den nächsten Pixel draufrechnet, sondern zu verschiedenen Teilen auf mehrere Pixel "verteilt".p.S.: das einzige was man wirklich braucht ist "Headroom" in den Bilddaten. Es kommt beim Dithern dauernd vor dass durch das Verteilen des Fehlers der Original-Wertebereich gesprengt wird. Also dass bei 8 Bit Bildern (0-255) z.B. Werte wie -20 oder +300 für einen Pixel gespeichert werden müssen. Das erreicht man einfach indem man das Bild vorher in einen Puffer mit "breiteren" Pixeln umkopiert.
-
Ach so, das klingt einfach aber wenn ich den gesamten Code zum Quantisieren ansehe; das muss wohl in diesem Bereich geschehen?
// Lock the output bitmap into memory outputData = output.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); // Define the source data pointers. The source row is a byte to // keep addition of the stride value easier (as this is in bytes) byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer(); Int32* pSourcePixel = (Int32*)pSourceRow; Int32* pPreviousPixel = pSourcePixel; // Now define the destination data pointers byte* pDestinationRow = (byte*)outputData.Scan0.ToPointer(); byte* pDestinationPixel = pDestinationRow; // And convert the first pixel, so that I have values going into the loop byte pixelValue = QuantizePixel((Color32*)pSourcePixel); // Assign the value of the first pixel *pDestinationPixel = pixelValue; // Loop through each row for (int row = 0; row < height; row++) { // Set the source pixel to the first pixel in this row pSourcePixel = (Int32*)pSourceRow; // And set the destination pixel pointer to the first pixel in the row pDestinationPixel = pDestinationRow; // Loop through each pixel on this scan line for (int col = 0; col < width; col++, pSourcePixel++, pDestinationPixel++) { // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimisation. if (*pPreviousPixel != *pSourcePixel) { // Quantize the pixel pixelValue = QuantizePixel((Color32*)pSourcePixel); // And setup the previous pointer pPreviousPixel = pSourcePixel; } // And set the pixel in the output *pDestinationPixel = pixelValue; } // Add the stride to the source row pSourceRow += sourceData.Stride; // And to the destination row pDestinationRow += outputData.Stride; } }
-
Ja genau da drinnen.
-
Super, habe mal folgendes versucht:
if (*pPreviousPixel != *pSourcePixel) { // Quantize the pixel pixelValue = QuantizePixel((Color32*)pSourcePixel); // fehler ausrechnen int original = *pSourcePixel; int quantized = QuantizePixel((Color32*)pSourcePixel); int error = original - quantized; byte *DestPixel = pDestinationPixel + 1; DestPixel+=error; //pDestinationPixel = DestPixel; pixelValue = *DestPixel; // And setup the previous pointer pPreviousPixel = pSourcePixel; } // And set the pixel in the output *pDestinationPixel = pixelValue;
Da kommt aber eine AccesViolation bei Zuweisung an pixelValue.
Ein Problem hatte ich; die Funktion welche "palette_lookup" entspricht habe ich nicht gefunden, ich müsste ja eigentlich nicht 2 Mal QuantizePixel aufrufen...
-
Die Funktion "palette_lookup" soll genau das Gegenteil von QuantizePixel machen, nämlich aus dem 8 Bit Palette-Index wieder eine 24 Bit Farbe machen.
Weiters musst du das Verteilen des Fehlers für die Kanäle R, G und B getrennt machen, sonst wird das Ergebnis nicht das sein was du willst.
Und natürlich musst du die "Corner Cases" behandeln, also hier die Fälle dass die Pixel auf die ne den Fehler verteilen willst bereits ausserhalb des Bildes liegen.
-
Ahaa ja diese Methode ist natürlich in der Klasse noch nicht vorhanden.
Wie komme ich denn dort an die verschiedenen Kanäle? Ich glaube eine getrennte Behandlung findet wie ich gesehen habe an einer anderen Stelle im Code oder der Basisklasse (Quantizer) statt. Kompliziert; wahrscheinlich muss diese Funktionalität leider noch etwas warten