Laden...

Umwandlung nach Schwarz-Weiss bringt auch farbige Pixel

Erstellt von TimFranke vor 6 Jahren Letzter Beitrag vor 6 Jahren 3.110 Views
T
TimFranke Themenstarter:in
15 Beiträge seit 2014
vor 6 Jahren
Umwandlung nach Schwarz-Weiss bringt auch farbige Pixel

Hallo zusammen,

nachdem ich die Performancenachteile von GetPixel/SetPixel beim Umwandeln
eines Bitmaps erfahren durfte, habe ich mich mit meinem Laienverstand durch
ein paar Webseiten gearbeitet, die sich für ähnliche Zwecke der LockBits-Methode
bedienen. Hier ist die Methode, die ich erstellt habe.

 public Bitmap BlackWhite(Bitmap bmp)
        {

            Bitmap image = new Bitmap(bmp);
            
            BitmapData imageData = image.LockBits(new Rectangle(0, 0, image.Width,
              image.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            byte[] imageBytes = new byte[Math.Abs(imageData.Stride) * image.Height];
            IntPtr scan0 = imageData.Scan0;

            Marshal.Copy(scan0, imageBytes, 0, imageBytes.Length);

            for (int i = 0; i < imageBytes.Length; i += 3)
            {
                byte pixelB = imageBytes[i];
                byte pixelR = imageBytes[i + 2];
                byte pixelG = imageBytes[i + 1];

                int grauwert = (int) (pixelR * 0.3 + pixelG * 0.59 + pixelB * 0.1);

                if (grauwert > schwellwert)
                    imageBytes[i] = imageBytes[i + 1] = imageBytes[i + 2] = 255;
                else
                    imageBytes[i] = imageBytes[i + 1] = imageBytes[i + 2] = 0;
            }

            Marshal.Copy(imageBytes, 0, scan0, imageBytes.Length);

            image.UnlockBits(imageData);

            return image;
        }

Grundsätzlich funktioniert sie auch, weist aber im Ergebnisbild ein paar farbige Pixel auf, deren
Vorhandensein ich mir nicht erklären kann. (siehe Anhang)
An den Kanten der Symbole (ausser an den horizontalen) liegen farbige Pixel.

Kann jemand einen Tipp geben, woran das liegt und wie es zu beheben wäre?
Kann jemand zudem noch einen guten Einstieg in diese Bitmap-Materie empfehlen.
(Mein Kenntnisstand in C# basiert bisher nur auf einen Einführungsbuch, welches diese
Details nicht behandelt hat. Die Codeschnipsel, die ich verwendet habe, verstehe ich nicht wirklich.)

Gruss,
Tim

3.003 Beiträge seit 2006
vor 6 Jahren

Der Code, den du gepostet hast, produziert nur schwarz oder weiß. Das Bild, das du gepostet hast, enthält (mal von den Artefakten abgesehen, die dich stören) auch Graustufen, also Informationen, die in den Bilddaten nicht vorkommen. Dein Problem liegt also nicht an den Bilddaten, sondern an der Art und Weise, wie du sie darstellst (nämlich mit weichgezeichneten Übergängen: das geht mit ausschließlich schwarz und weiß nicht).

Der Punkt, an dem es bei dir hängt, ist der hier:


newBitmap.Clone(new RectangleF(0.0f, 0.0f, bm.Width, bm.Height), PixelFormat.Format1bppIndexed);

, dh du solltest die benutzte Palette auf die Farben einschränken, die du darzustellen gedenkst.

Ich persönlich finde ja den Umgang mit Colormatrix schöner als den Marshal-Voodoo, den du da veranstaltest; ich würde auch kaum Performanceunterschiede erwarten, aber das musst du wissen.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

4.938 Beiträge seit 2008
vor 6 Jahren

Hallo,

du hast einen kleinen Fehler in deiner Grauwertberechnung:


int grauwert = (int) (pixelR * 0.3 + pixelG * 0.59 + pixelB * 0.1);

Die Summe der Faktoren sollte genau 1 ergeben.
Die richtige Formel lautet:


int grauwert = (int) (pixelR * 0.299 + pixelG * 0.587 + pixelB * 0.114);

s.a. Grauwert - In der Bildverarbeitung.
Häufig wird auch einfach


int grauwert = (int) (pixelR * 0.3 + pixelG * 0.59 + pixelB * 0.11);

verwendet.

PS: Und was LaTino mit Colormatrix meint: What would be a good TRUE black and white colormatrix?

T
TimFranke Themenstarter:in
15 Beiträge seit 2014
vor 6 Jahren

Hi,
danke für die Tipps. Ich befürchte aber, dass ich sie nicht ganz verstanden habe.

Das Bild, welches ich einlade in Schwarz-Weiss. Gibt es jetzt in meinem Ergebnisbild
Graustufen?

Und woher kommen die farbigen Artefakte um den Kanten herum?

Mir ist nicht ganz klar, wie ich Deine gepostet Code-Zeile verwenden soll.

Wenn ich in meinem Code das Pixelformat in "PixelFormat.Format1bppIndexed"
ändere, werden die Symbole im Zielbild zu einer Ansammlung von schwarzen Balken.

Aber wie gesagt, weiss ich eigentlich garnicht so richtig, was ich da tue.

Gruss,
Tim

5.658 Beiträge seit 2006
vor 6 Jahren

Hi TimFranke,

was die von LaTino gepostete Methode macht, kannst du doch in der Doku nachlesen. Siehe [Hinweis] Bitte schau in die SDK-/MSDN-Doku. In Verbindung mit dem Pixelformat sollte sie jedoch einfach ein S/W-Version des Eingabebildes zurückgeben. Dabei kann man allerdings keinen Schwellwert angeben, wie in deinem Code.

Wenn dein Code nicht funktioniert, wie erwartet, dann mußt du ihn debuggen und testen.

Wahrscheinlich liegt das Problem daran, daß du den Stride nicht beachtest, daher gibt es dann eine zeilenweise Verschiebung zwischen deinen gewünschten und den realen Farbwerten von (in deinem Fall) einem Byte.

@LaTino: Die Grauwerte werden sicherlich durch deinen Image Viewer erzeugt. Bei mir sind die Pixel entweder schwarz, weiß, rot, grün, blau, cyan oder gelb 😃

Weeks of programming can save you hours of planning

T
TimFranke Themenstarter:in
15 Beiträge seit 2014
vor 6 Jahren

Werde nochmal über Stride im Internet lesen.
Habe zwischenzeitlich rausgefunden, dass die Artefakte verschwinden,
wenn Breite und Höhe meines Bildes durch 4 teilbar sind.

Kann mangels Wissen aber leider noch keine Rückschluesse auf zu ändernde
Codezeilen bei mir ziehen.

Ich melde mich nochmal mit Fragen nach mehr Recherche.

Gruss,
Tim

4.938 Beiträge seit 2008
vor 6 Jahren

Schau dir mal den Code in Bitmap mit Alphawerten hat falsche Farben an (also zwei verschachtelte Schleifen über Height und Width und den Index entsprechend anpassen, da du ja nur 3 Bytes (RGB) anstatt 4 (ARGB) hast).

3.003 Beiträge seit 2006
vor 6 Jahren

@LaTino: Die Grauwerte werden sicherlich durch deinen Image Viewer erzeugt. Bei mir sind die Pixel entweder schwarz, weiß, rot, grün, blau, cyan oder gelb 😃

Whoops, da hast du sowas von Recht. Auch wegen des Schwellwerts (dass man keinen angeben kann, wenn man einfach 'ne 1Bit-Palette setzt), weshalb der vollständige Code eben noch die Transformation mittels ColorMatrix vorweg hat. Ich benutz das für die Optimierung von erzeugten QR-Barcodes, die als 32Bit-PNG mit Skalierung hier ankommen.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

D
985 Beiträge seit 2014
vor 6 Jahren

Probier es mal damit (hier wird der Stride auch korrekt beachtet)


public static Bitmap BlackWhite(Bitmap bmp)
{
    var image = new Bitmap(bmp);

    var imageData = image.LockBits(
        new Rectangle(0, 0, image.Width, image.Height),
        ImageLockMode.ReadWrite,
        PixelFormat.Format24bppRgb);

    byte[] imageBytes = new byte[Math.Abs(imageData.Stride) * image.Height];
    IntPtr scan0 = imageData.Scan0;

    Marshal.Copy(scan0, imageBytes, 0, imageBytes.Length);

    for (int y = 0; y < imageData.Height; y++)
    {
        for (int x = 0; x < imageData.Width; x++)
        {
            var idx = imageData.Stride * y + x * 3;

            byte pixelB = imageBytes[idx];
            byte pixelR = imageBytes[idx + 2];
            byte pixelG = imageBytes[idx + 1];

            int grauwert = (int)(pixelR * 0.3 + pixelG * 0.59 + pixelB * 0.1);

            if (grauwert > schwellwert)
                imageBytes[idx] = imageBytes[idx + 1] = imageBytes[idx + 2] = 255;
            else
                imageBytes[idx] = imageBytes[idx + 1] = imageBytes[idx + 2] = 0;

        }
    }

    Marshal.Copy(imageBytes, 0, scan0, imageBytes.Length);

    image.UnlockBits(imageData);

    return image;
}

T
TimFranke Themenstarter:in
15 Beiträge seit 2014
vor 6 Jahren

Super funktioniert wie verrückt.
Ich hatte zwischenzeitlich durch die Kommentare bemerkt,
dass Stride keine kontinuierliche Funktion der Bitmapbreite ist,
und so meine einfache Schleife für Verschiebungen gesorgt hat.
Hatte dann probiert den Scheifenzähler dann entsprechend zu korrigieren.
Hatte mich da aber wohl auch verhauen.
Mit den zwei Schleifen geht es jetzt ja super.
Vielen Dank,
Tim