Laden...

RGB Werte aller Pixel eines Bildes überprüfen mit Lockbits

Erstellt von Elias1994 vor 7 Jahren Letzter Beitrag vor 7 Jahren 6.358 Views
E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren
RGB Werte aller Pixel eines Bildes überprüfen mit Lockbits

Hallo zusammen,

das Thema kam bereits schon öfters auf. Ich habe ein Bild das im Normalfall komplett schwarz ist (R=0, G=0, B=0), wobei die Werte eine gewissen Toleranz haben dürfen (maximal den Wert 22). Mein Bild hat immer einer Größe von 1280x720. Das Pixelformat kenne ich leider nicht. Kann man das herausfinden oder muss man das gar nicht spezifisch angeben?

Um zu überprüfen, ob das Bild wirklich schwarz ist möchte ich von jedem Pixel die RGB-Werte auslesen und mit dem Grenzwert vergleichen. Sobald ein Pixel außerhalb de Toleranz liegt wird die Methode beendet.

Bisher mache ich das ganze mit GetPixel. Das ganze ist bekanntermaßen sehr träge. Habe jetzt auch schon sämtliche Foren durchforscht und versucht mich mit dem Thema Lockbits auseinander zusetzten. Unteranderem auch das Thema GetPixel und SetPixel um Längen geschlagen. 800 mal schneller gelesen und die Klasse BitmapHelper verwendet. Mir ist aber nicht klar wie ich das jetzt für meine bisherige Code verwenden kann. Help me please 😉

private void Check_RGB_values_Click(object sender, EventArgs e)
        {
            int countPixel = 0;
            Bitmap myBitmap = new Bitmap("C:\\blackscreen.bmp");

            for (int x = 0; x < myBitmap.Width; x++) //1280
            {
                for (int y = 0; y < myBitmap.Height; y++) //720
                {
                    countPixel++;
                    Color pixelColor = myBitmap.GetPixel(x, y);

                    string rotAnteil = pixelColor.R.ToString();
                    string gelbAnteil = pixelColor.G.ToString();
                    string blauAnteil = pixelColor.B.ToString();

                    if (pixelColor.R > 22)
                    {
                        MessageBox.Show("Red from pixel No." + countPixel + " is out of range. " + rotAnteil + "> 22");
                        return;
                    }

                    if (pixelColor.G > 22)
                    {
                        MessageBox.Show("Yellow from pixel No." + countPixel + " is out of range. " + gelbAnteil + "> 22");
                        return;
                    }

                    if (pixelColor.B > 22)
                    {
                        MessageBox.Show("Blue from pixel No." + countPixel + " is out of range. " + blauAnteil + "> 22");
                        return;
                    }
                }
            }
        }

Beste Grüße,
Elias

6.911 Beiträge seit 2009
vor 7 Jahren

Hallo Elias1994,

Das Pixelformat kenne ich leider nicht. Kann man das herausfinden oder muss man das gar nicht spezifisch angeben?

Z.B. so


bool isAlphaBitmap = bitmap.PixelFormat == (bitmap.PixelFormat | PixelFormat.Alpha);

Siehe ggf. [Artikel] Bitoperationen in C#

Für GetPixel und SetPixel hab ich mir angehängte Klasse erstellt -- in Anlehnung an andere Klassen die es dazu gibt (wie die von dir verlinkte).
Schau dir angehängt einmal an, dann siehst du wie das geht. Du kannst du Klasse auch direkt verwenden.

Anmerkung: ist schon ein alter Code -- IDisposable könnte auf jeden noch implementiert werden.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

5.657 Beiträge seit 2006
vor 7 Jahren

Die schnellste Variante wäre in dem Anwendungsfall, das Bild einfach in ein Byte-Array zu konvertieren, und dann die Werte im Array auf die gegebene Toleranz zu prüfen - dadurch kann man komplett auf Bitmap-Operationen verzichten.

Weeks of programming can save you hours of planning

C
224 Beiträge seit 2009
vor 7 Jahren

@MrSparkle:

kann sein, dass ich einen Gedankenfehler hier habe (dann einfach ignorieren):

  1. sofern kein Alpha verwendet wird.
  2. Stride Overhead ignorieren.

Zum Stride:

Eine Bildzeile besteht aus den Pixeln einer Bildzeile + ggf. zusätzliche Füllbytes:

Auszug aus MSDN - BitmapData.Stride:

Ein Schritt ist die Breite einer einzelnen Zeile von Pixeln (einer Scanzeile), aufgerundet auf eine 4-Byte-Begrenzung. Wenn der Schritt positiv ist, verläuft die Bitmap von oben nach unten. Wenn der Schritt negativ ist, verläuft die Bitmap von unten nach oben.

E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren

Moin moin,

das Pixelformat konnte ich inzwischen ermitteln. Bekomme für GDI, Format24bppRgb und DontCare ein true. Mit der FastBitmap.cs bin ich leider nicht ganz zurecht gekommen wie das funktioniert die Pixelwerte abzufragen. Habe mich stattdessen für die Variante von MrSparkle entschieden und das so gelöst:


public byte[] CreateBitmapArray(Bitmap blackBitmap)
        {

               BitmapData bmpdata = blackBitmap.LockBits(new Rectangle(0, 0, blackBitmap.Width, blackBitmap.Height), ImageLockMode.ReadOnly, blackBitmap.PixelFormat);
                anzahlPixel = bmpdata.Stride * blackBitmap.Height;
                dataByte = new byte[anzahlPixel];
                IntPtr intPtr = bmpdata.Scan0;

                Marshal.Copy(intPtr, dataByte, 0, anzahlPixel);

                return dataByte;

                if (bmpdata != null)
                blackBitmap.UnlockBits(bmpdata);
        }


        private void button1_Click(object sender, EventArgs e)
        {
            Bitmap blackscreen = new Bitmap("c:\\blackscreen.bmp");

            BitmapToByteArray(blackscreen);

            for (int x = 0; x < anzahlPixel; x++)
            {
                if (dataByte[x] > 22)
                {
                    MessageBox.Show("Pixel "+x+" is not black");
                    return; // beende die Methode sobald der erste Pixel außerhalb der Toleranz liegt
                }
            }
        }

Trotzdem würde mich auch noch die andere Lösung interessieren ohne das Bild in ein Array umzuwandeln.

3.003 Beiträge seit 2006
vor 7 Jahren

Deine Lösung ist (meines Wissens) schon die zweitschnellste[1]. Ohne das Bitmap als Array zu behandeln, liefe wieder auf die GetPixel-Methode im ursprünglichen Posting hinaus, und die ist für so etwas eben nicht wirklich gut geeignet.

LaTino

[1] lässt sich unter Verwendung von unsafe code nochmal etwas beschleunigen.

"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)

E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren

Alles klar danke Latino.

@gfoidl

Für GetPixel und SetPixel hab ich mir angehängte Klasse erstellt -- in Anlehnung an andere Klassen die es dazu gibt (wie die von dir verlinkte).
Schau dir angehängt einmal an, dann siehst du wie das geht. Du kannst du Klasse auch direkt verwenden.

Danke. Deinen Ansatz konnte ich jetzt auch nachvollziehen und habe beide Lösungen nun funktionsfähig.

E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren

Die schnellste Variante wäre in dem Anwendungsfall, das Bild einfach in ein Byte-Array zu konvertieren, und dann die Werte im Array auf die gegebene Toleranz zu prüfen - dadurch kann man komplett auf Bitmap-Operationen verzichten

Danke hat wunderbar geklappt und ist vor allem rasend schnell. Funktioniert das Ganze auch für Videos? Also ich möchte überprüfen, ob in dem Video ein Pixel schwarz geworden ist.

Für das Bild:


        public byte[] BitmapToByteArray(Bitmap bitmap)
        {

            BitmapData bmpdata = null;
            try
            {
                bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
                numbytes = bmpdata.Stride * bitmap.Height;
                bytedata = new byte[numbytes];
                IntPtr ptr = bmpdata.Scan0;

                Marshal.Copy(ptr, bytedata, 0, numbytes);

                return bytedata;
            }
            finally
            {
                if (bmpdata != null)
                    bitmap.UnlockBits(bmpdata);
            }

        }
 public void CheckScreenColor()
        {
            for (int counter = 0; counter < numbytes; counter++)
            {
                byte a = bytedata[counter];
                if (bytedata[counter] < farbgrenze)
                {
                    if (counter == 0 || (counter % 3) == 0)
                    {
                        MessageBox.Show("Pixel " + (counter + 1) + " (Value red) is not black.");
                        return;
                    }

                    else if ((counter == 1) || ((counter - 1) % 3) == 0)
                    {
                        MessageBox.Show("Pixel " + (counter + 1) + "(Value yellow) is not black.");
                        return;
                    }

                    else if ((counter == 2) || ((counter - 2) % 3) == 0)
                    {
                        MessageBox.Show("Pixel " + (counter + 1) + " (Value blue) is not black.");
                        return;
                    }

                }
            }
R
10 Beiträge seit 2016
vor 7 Jahren

Für ein Video würde ich folgendes probieren:

        

string path = "U:\\a.avi";

public byte[] GetBytesFromFile(string path)
{
            FileStream filestream = File.OpenRead(path);

                byte[] bytes = File.ReadAllBytes(path);
                filestream.Read(bytes, 0, Convert.ToInt32(filestream.Length));
                filestream.Close();

                return bytes;
}


16.806 Beiträge seit 2008
vor 7 Jahren

Davon abgesehen, dass der Stream unnötig ist, fehlt das sichere disposen, zB in Form eines using().
ReadAllByte alleine reicht vollkommen. Den Stream braucht man hier ja nicht.

4.931 Beiträge seit 2008
vor 7 Jahren

Das Byte-Array gibt aber noch keine Auskunft über den Inhalt der AVI-Datei (das ja nur ein Container für bestimmte Videotypen ist).

Um Videos zu analysieren, muß man die einzelnen Frames extrahieren, z.B. Extract Frames from Video Files.

E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren

Um Videos zu analysieren, muß man die einzelnen Frames extrahieren

Mit dem FileWriter erstelle ich ja mein Video aus mehreren Frames/Bitmaps:


void _NewFrame_Video(object sender, NewFrameEventArgs eventArgs)
        {
            video = (Bitmap)eventArgs.Frame.Clone();
            FileWriter.WriteVideoFrame(video);
            VideoToByteArray(video);
        }

Ich finde das irgendwie etwas umtändlich, wenn ich mein fertiges Video dann wieder in Frames zerstückeln muss. Das geht doch bestimmt einfacher alle RGB-Werte aller Pixel aller Frames auszulesen.

E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren

Hallo nochmal,

habe jetzt etwas Zeit in Recherche gesteckt und folgendes gefunden:

http://stackoverflow.com/questions/17825677/extracting-frames-of-a-avi-file

Dann scheint das richtige zu sein. Sieht hier jemand ein Problem darin?

1.029 Beiträge seit 2010
vor 7 Jahren

Hi,

ein Problem nicht - aber den Unterschied sehe ich bei deiner Recherche nun auch nicht 😉

Bei beiden Lösung wird das AVI-File letztendlich dekodiert und in Frames aufgeteilt - da du allerdings ohnehin schon AForge benutzt ist deine Recherche für dich sicher die beste Lösung.

LG

771 Beiträge seit 2009
vor 7 Jahren

Hi Elias1994,

aber wenn du doch sowieso das Video selbst erstellst, dann hast du doch jeden einzelnen Frame als Bitmap vorliegen. Und kannst dann wieder die Pixel überprüfen.

E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren

Hallo Cat,

aber wenn du doch sowieso das Video selbst erstellst, dann hast du doch jeden einzelnen Frame als Bitmap vorliegen. Und kannst dann wieder die Pixel überprüfen.

Das ist richtig. Das wäre mir sogar am liebsten. Überlege nur noch wie ich das am besten umsetzte.

Meine Idee wäre dann im newFrameHandler nach jedem Frame.Clone das Bitmap zu speichern. Die einzelnen Bitmaps will ich aus Dokumentationsgründen speichern. Und anschließend das Bild sofort zu analysieren mit meiner Methode VideoToByteArray();

Also so:


        void FinalVideo_NewFrame(object sender, NewFrameEventArgs eventArgs)
        {
            if (stopVideo==false)
            {
                video = (Bitmap)eventArgs.Frame.Clone();
                VideoToByteArray(video);
                video.Save(pathXY);
                FileWriter.WriteVideoFrame(video);
            }
//....
}

Aber habe ich da nicht ein riesen Performanceverlust, wenn ich das alles in den Handler schreibe? Nehmen mir mal an die Methode VideoToByteArray() benötigt 2s dann wird ja auch maximal alle 2s ein neuer Frame gecloned, gespeichert und analysiert.

16.806 Beiträge seit 2008
vor 7 Jahren

Das ist ein nahezu Bilderbuch-Fall für den Producer Consumer Pattern.

  • Ein Task liest die Bytes und schreibt sie in eine Queue
  • Ein Task verarbeitet nun die Bytes
  • Das Ergebnis geht an einen dritten Task

Prinzipielles Vorgehen sehr einfach erklärt unter
TPL Pipelines

Und im Fall der Fälle lässt sich eine einzelne Queue von mehrere Tasks verarbeiten -> einfache Skalierung.

E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren

Die schnellste Variante wäre in dem Anwendungsfall, das Bild einfach in ein Byte-Array zu konvertieren, und dann die Werte im Array auf die gegebene Toleranz zu prüfen - dadurch kann man komplett auf Bitmap-Operationen verzichten.

Liege ich der Annahme richtig, dass die Bytes nach folgendem Prinzip in das Array geschrieben werden:

  • Die RGB Werte werden beginnend links oben am Bild Zeile für Zeile bis
    einschließlich Bildende (rechts unten) ( also so wie man ein Buch liest 😉 )

  • ´Bytearray[0] =B-Wert des ersten Pixels

  • ´Bytearray[1] =G-Wert des ersten Pixels

  • ´Bytearray[2] =R-Wert des ersten Pixels

  • ´Bytearray[3] =B-Wert des ersten Pixels

  • ´Bytearray[4] =G-Wert des ersten Pixels

  • ´Bytearray[5] =R-Wert des ersten Pixels

Zumindest hat dies meine Analyse des Bytearrays ergeben. Hat mich etwas verwundert, da ich davon ausgegangen war, dass erst die R-Wert des ersten Pixels im Array steht und nicht der B-Wert.

Kann das jemand bestätigen?

Gruß,
Elias

4.931 Beiträge seit 2008
vor 7 Jahren

Es kommt auf das PixelFormat an, s. Windows Bitmap - Bilddaten (bei 24BPP stimmt deine Vermutung).

Edit: Sieht man auch am Quellcode von GetPixel und SetPixel um Längen geschlagen. 800 mal schneller in der Methode Format24BppRgb().

E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren

Es kommt auf das PixelFormat

Super danke. Stimmt daran sieht man sehr gut. Kann man das irgendwo nachlesen, wann welches Pixelformat benutzt wird. Ich weiß, dass ich das Pixelformat abfragen kann. Mich würde, dass nur interessieren, was wo seine Anwendung findet.

Und was mich noch interessieren würde:

Wenn ein Bild ein bestimmtes Pixelformat hat, lässt sich das Bild dann in jedes beliebige andere Pixelformat umwandeln?

S
248 Beiträge seit 2008
vor 7 Jahren

Hallo Elias,

Wenn ein Bild ein bestimmtes Pixelformat hat, lässt sich das Bild dann in jedes beliebige andere Pixelformat umwandeln?

du kannst das Format einfach beim Locken der Bitmap angeben. Du kannst sogar gleichzeitig die Konvertierung in einen von dir verwendeten Speicher vornehmen:

struct BGR
{
    public byte B;
    public byte G;
    public byte R;
}

static BGR[,] GetPixels(Bitmap bitmap)
{
    Contract.Requires(bitmap != null);

    BGR[,] pixels = new BGR[bitmap.Height, bitmap.Width];
    GCHandle handle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
    try
    {
        BitmapData data = new BitmapData();
        data.PixelFormat = PixelFormat.Format24bppRgb;
        data.Width = bitmap.Width;
        data.Height = bitmap.Height;
        data.Stride = bitmap.Width * 3;
        data.Scan0 = handle.AddrOfPinnedObject();
        data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly | ImageLockMode.UserInputBuffer, bitmap.PixelFormat, data);
        bitmap.UnlockBits(data);
    }
    finally
    {
        handle.Free();
    }
    return pixels;
}

Hier ist das Ausgabeformat 24bpp BGR. Du kannst natürlich die Struct für den einzelnen Pixel und das Format entsprechend anpassen (Stride nicht vergessen).
Ob die Konvertieren wirklich alle Formate beliebig konvertieren kann, kann ich dir nicht sagen.

Grüße
spooky

E
Elias1994 Themenstarter:in
54 Beiträge seit 2015
vor 7 Jahren
data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly | ImageLockMode.UserInputBuffer, bitmap.PixelFormat, data);  

Okay cool. D.h. das Pixelformat kann ich hier in Lockbits angeben?

Z.B.:
Format48bppRgb

data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly | ImageLockMode.UserInputBuffer, PixelFormat.Format48bppRgb);
S
248 Beiträge seit 2008
vor 7 Jahren

Es müsste

data.PixelFormat = PixelFormat.Format24bppRgb; // <--

sein.

Grüße
spooky