Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Selbstprogrammierter Floyd-Steinberg-Filter sieht seltsam aus...

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 <
000
11.02.2017, 02:55 Uhr
Yadgar



Hi(gh)!

Mittlerweile habe ich es tatsächlich geschafft, im Rahmen meines Kommandozeilen-Bildbearbeitungsprogramms "YIP" (Yadgar's Image Processor - ImageMagick für Arme...) so etwas wie Floyd-Steinberg-Rasterung zu programmieren, nach dem Artikel "Floyd-Steinberg" in der englischen Wikipedia... in Pseudocode wird der Algorithmus dort so dargestellt:


Code:
for each y from top to bottom
   for each x from left to right
      oldpixel  := pixel[x][y]
      newpixel  := find_closest_palette_color(oldpixel)
      pixel[x][y]  := newpixel
      quant_error  := oldpixel - newpixel
      pixel[x + 1][y    ] := pixel[x + 1][y    ] + quant_error * 7 / 16
      pixel[x - 1][y + 1] := pixel[x - 1][y + 1] + quant_error * 3 / 16
      pixel[x    ][y + 1] := pixel[x    ][y + 1] + quant_error * 5 / 16
      pixel[x + 1][y + 1] := pixel[x + 1][y + 1] + quant_error * 1 / 16



Bei mir in C++ sieht das so aus:


C++:
void floydsteinberg(vector<vector<pixel> > &img, vector<rgb> &pal)
{
  unsigned short h = img.size();
  unsigned short w = img[0].size();
  unsigned short r, c, i;
  unsigned short p = pal.size();
  rgb t0, t1, t2, t3, t4, closest=pal.at(0);
  rgb triple, dist;
  float newred, newgreen, newblue;
  
  for (r=0; r<h; r++)
  {
    for (c=0; c<w; c++)
    {
      img[r].at(c).get_all(triple);
      t0 = triple;
      t1 = {-1, -1, -1};
      t2 = {-1, -1, -1};
      t3 = {-1, -1, -1};
      t4 = {-1, -1, -1};
      if (c < w-1)
      {
          img[r].at(c+1).get_all(triple);
          t1 = triple;
      }
      if (c > 0 && r < h-1)
      {
        img[r+1].at(c-1).get_all(triple);      
        t2 = triple;
      }
      if (r < h-1 )
      {
          img[r+1].at(c).get_all(triple);
        t3 = triple;      
      }
      if (c < w-1 && r < h-1 )
      {
          img[r+1].at(c+1).get_all(triple);
        t4 = triple;      
      }          
      for (i=0; i<p; i++)
      {
          if (coldist(t0, pal.at(i)) < coldist(t0, closest))
            closest = pal.at(i);
      }
      img[r].at(c).set_all(closest.red, closest.green, closest.blue);
      dist.red = t0.red - closest.red;
      dist.green = t0.green - closest.green;
      dist.blue = t0.blue - closest.blue;
      if (t1.red > -1)
      {
          img[r].at(c+1).get_all(triple);
          newred = triple.red + dist.red*0.4375;
          newgreen = triple.green + dist.green*0.4375;
          newblue = triple.blue + dist.blue*0.4375;
        img[r].at(c+1).set_all(mround(newred), mround(newgreen), mround(newblue));
      }
      if (t2.red > -1)
      {
          img[r+1].at(c-1).get_all(triple);
          newred = triple.red + dist.red*0.1875;
          newgreen = triple.green + dist.green*0.1875;
          newblue = triple.blue + dist.blue*0.1875;
        img[r+1].at(c-1).set_all(mround(newred), mround(newgreen), mround(newblue));
      }
      if (t3.red > -1)
      {
          img[r+1].at(c).get_all(triple);
          newred = triple.red + dist.red*0.3125;
          newgreen = triple.green + dist.green*0.3125;
          newblue = triple.blue + dist.blue*0.3125;
        img[r+1].at(c).set_all(mround(newred), mround(newgreen), mround(newblue));
      }
      if (t4.red > -1)
      {
          img[r+1].at(c+1).get_all(triple);
          newred = triple.red + dist.red*0.0625;
          newgreen = triple.green + dist.green*0.0625;
          newblue = triple.blue + dist.blue*0.0625;
        img[r+1].at(c+1).set_all(mround(newred), mround(newgreen), mround(newblue));
      }
      
    }
  }


  cout << "Floyd-Steinberg-Rasterung wird berechnet!" << endl;
}



"pixel" ist eine selbst entwickelte Klasse, "rgb" ein struct-Objekt für Farbtripel.

Das Programm kompiliert problemlos (bis auf ein paar Warnungen wegen nur in C++11 verfügbarer Funktionen), es gibt auch keinen Laufzeitfehler... aber wenn ich mir die gerasterte Bilddatei (von RGB nach 1-bit-Schwarzweiß) ansehe, frage ich mich, was da falsch gelaufen ist - wie Floyd-Steinberg-Schwarzweiß z. B. in GIMP sieht es nämlich nicht aus!

Hier das Original... und das hier ist die schwarzweiße gerasterte Version!

Bis bald im Khyberspace!

Yadgar
--
Flagmaker - ein Programmier-Blog

Dieser Post wurde am 11.02.2017 um 02:57 Uhr von Yadgar editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
11.02.2017, 20:21 Uhr
Hans
Library Walker
(Operator)


Hi,

ohne jetzt wirklich Ahnung oder Deinen Code überprüft zu haben, vermute ich einfach mal, dass der Fehler in dem Teil liegt, der im Pseudocode als


Code:
      newpixel  := find_closest_palette_color(oldpixel)



dargestellt ist.

Dann wäre es ganz nett, wenn Du neben dem Ergebnis, dass Dein Programnm liefert, auch das Ergebnis von GIMP daneben stellen würdest, um die Sachen vergleichen zu können. - Und wenn Du schon mit GIMP hantierst, dann kannst Du auch gleich alle Bilder incl. Beschriftungen zu einem grossen Bild zusammenfassen. - Zumindest für die Publikation.

Eine Erfahrung von mir, allerdings bei den Bildbearbeitungsfunktionen von IrfanView ist, das es gerade bei der Umwandlung oder Farbreduktion von RGB-Bildern sinnvoll ist, Schrittweise oder iterativ vorzugehen. Also anstatt sofort von RGB (24 Bit pro Pixel) nach S/W (1 Bit pro Pixel) zu wandeln, mehrere Zwischenschritte durchführen, etwa von 24Bit/Pixel erst einmal nach 16 Bit/Pixel, dann nach 8Bit/Pixel, dann 4Bit/Pixel und schliesslich 1 Bit pro Pixel. Ich weis nicht warum, (vermutlich durch die Ergebnisse der Rundungen bedingt) aber zumindest in IrfanView sehen die Bilder bei dieser Vorgehensweise am Ende besser aus, als wenn ich die Farbtiefe in einem Durchgang von 24 Bit auf 1 Bit reduziere. - In GIMP kann das alllerdings auch anders sein...

Hans
--
Man muss nicht alles wissen, aber man sollte wissen, wo es steht. Zum Beispiel hier: Nachdenkseiten oder Infoportal Globalisierung.

Dieser Post wurde am 11.02.2017 um 20:21 Uhr von Hans editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
12.02.2017, 08:32 Uhr
Yadgar



Hi(gh)!


Zitat von Hans:
Hi,

ohne jetzt wirklich Ahnung oder Deinen Code überprüft zu haben, vermute ich einfach mal, dass der Fehler in dem Teil liegt, der im Pseudocode als


Code:
      newpixel  := find_closest_palette_color(oldpixel)





Die jeweils nächste Farbe in der Palette wird hier ermittelt:


C++:
float coldist(rgb c1, rgb c2)
{
  double reddpow, greendpow, bluedpow, powsum;
  reddpow = pow((c2.red-c1.red), 2);
  greendpow = pow((c2.green-c1.green), 2);  
  bluedpow = pow((c2.blue-c1.blue), 2);
  powsum = reddpow + greendpow + bluedpow;
  
  return sqrt(powsum);    
}



Das RGB-System ist ja sozusagen ein dreidimensionaler Farbraum-Würfel, so dass man mit dem Satz des Pythagoras zwischen zwei beliebigen Farben die Raumdiagonale bilden und deren Länge ermitteln kann!


Zitat von Hans:

Dann wäre es ganz nett, wenn Du neben dem Ergebnis, dass Dein Programnm liefert, auch das Ergebnis von GIMP daneben stellen würdest, um die Sachen vergleichen zu können. - Und wenn Du schon mit GIMP hantierst, dann kannst Du auch gleich alle Bilder incl. Beschriftungen zu einem grossen Bild zusammenfassen. - Zumindest für die Publikation.



Bittesehr!


Zitat von Hans:

Eine Erfahrung von mir, allerdings bei den Bildbearbeitungsfunktionen von IrfanView ist, das es gerade bei der Umwandlung oder Farbreduktion von RGB-Bildern sinnvoll ist, Schrittweise oder iterativ vorzugehen. Also anstatt sofort von RGB (24 Bit pro Pixel) nach S/W (1 Bit pro Pixel) zu wandeln, mehrere Zwischenschritte durchführen, etwa von 24Bit/Pixel erst einmal nach 16 Bit/Pixel, dann nach 8Bit/Pixel, dann 4Bit/Pixel und schliesslich 1 Bit pro Pixel. Ich weis nicht warum, (vermutlich durch die Ergebnisse der Rundungen bedingt) aber zumindest in IrfanView sehen die Bilder bei dieser Vorgehensweise am Ende besser aus, als wenn ich die Farbtiefe in einem Durchgang von 24 Bit auf 1 Bit reduziere. - In GIMP kann das alllerdings auch anders sein...



Also, ich habe vorhin bei IrfanView festgestellt, dass es keinen Unterschied macht, ob ich direkt oder über die von dir genannten Zwischenschritte konvertiere...

Bis bald im Khyberspace!

Yadgar
--
Flagmaker - ein Programmier-Blog
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
12.02.2017, 14:35 Uhr
Hans
Library Walker
(Operator)


Hi,

hast Du schon mal versucht, alle Berechnungen mit double auszuführen und erst ganz zum Schluss, die Pixeldaten dann von double zu int zu wandeln. Ich vermute jetzt mal , dass sich die Artefakte bei Deinem Programm aus Rundungsfehlern ergeben, die beim hin- und her konvertieren zwischen float und double entstehen.

Ich hab dazu jetzt gerade kein Beispiel parat, aber in Lehrbüchern über numerische Mathematik steht meisst auch ein Kapitel über Rundungsfehler und Fehlerfortpflanzung drin. Da gibt es so schöne Beispiele, wie Ergebnisse bei iterativen Berechnungen durch Rundung immer mehr gültige Stellen verlieren, und am Ende nur noch Unsinn heraus kommt. Das zeigt sich insbesondere bei solchen Zahlen, die sich nicht glatt von Dezimal in die binäre Fliesskommaform umwandeln lassen.

Und Floyd-Steinberg ist ja auch insofern eine iterative Methode als dass viele Pixel mehrfach bearbeitet werden. Hab dazu gestern ein nettes Beispiel gefunden, wo die schrittweisen Änderungen verdeutlicht werden. Und wie schon geschrieben, vermute ich, dass sich durch die konvertierungen zwischen float und double zu früh die Festlegung auf Null oder eins ergibt, die das Verfahren eigentlich umgehen soll.

Hans
--
Man muss nicht alles wissen, aber man sollte wissen, wo es steht. Zum Beispiel hier: Nachdenkseiten oder Infoportal Globalisierung.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
004
12.02.2017, 23:31 Uhr
Yadgar



Hi(gh)!


Zitat von Hans:
Hi,

hast Du schon mal versucht, alle Berechnungen mit double auszuführen und erst ganz zum Schluss, die Pixeldaten dann von double zu int zu wandeln. Ich vermute jetzt mal , dass sich die Artefakte bei Deinem Programm aus Rundungsfehlern ergeben, die beim hin- und her konvertieren zwischen float und double entstehen.



Ich habe erst die Klasse "pixel" vollständig von unsigned char nach short umgestellt; das allein behob den Fehler noch nicht, dann fiel mir auf, dass in main() die Funktion mround noch float (statt double) übergeben bekam und unsigned char zurückgab... ich änderte das in double bzw. short - und jetzt funktioniert es einwandfrei! Zumindest dem Augenschein nach:

Floyd-Steinberg-Rasterung mit yip

Leider habe ich bis jetzt noch keine Funktionen für Bildarithmetik implementiert, sonst könnte ich mal überprüfen, ob dieses Bild identisch ist mit dem, was die Floyd-Steinberg-Funktion von GIMP liefert...

Trotzdem danke für deinen Hinweis - ich habe dich auch gleich in der Dankesliste in den Kommentaren am Code-Anfang aufgeführt!

Als Nächstes kommt eine Skalierungs-Funktion - ich will nämlich Bilder in den Monochrom-Modus des Atari ST (640 x 400 Pixel) konvertieren, langfristig sollen die Bilder sogar für den realen Atari ST lesbar sein - z. B. via Omikron-BASIC!

Bis bald im Khyberspace!

Yadgar
--
Flagmaker - ein Programmier-Blog

Dieser Post wurde am 12.02.2017 um 23:32 Uhr von Yadgar editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 <     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: