Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
RGB Werte aller Pixel eines Bildes überprüfen mit Lockbits
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

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

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 6.779
Herkunft: Waidring

beantworten | zitieren | melden

Hallo Elias1994,
Zitat
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ü
Attachments

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!"
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5.649
Herkunft: Leipzig

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
CoLo
myCSharp.de - Member



Dabei seit:
Beiträge: 224

beantworten | zitieren | melden

@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:
Zitat
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.
private Nachricht | Beiträge des Benutzers
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
LaTino
myCSharp.de - Experte

Avatar #avatar-4122.png


Dabei seit:
Beiträge: 3.003
Herkunft: Thüringen

beantworten | zitieren | melden

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)
private Nachricht | Beiträge des Benutzers
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

beantworten | zitieren | melden

Alles klar danke Latino.

@gfoidl
Zitat
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.
private Nachricht | Beiträge des Benutzers
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

beantworten | zitieren | melden

Zitat
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;
                    }

                }
            }
private Nachricht | Beiträge des Benutzers
Richard95
myCSharp.de - Member



Dabei seit:
Beiträge: 10

beantworten | zitieren | melden

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;
}

Dieser Beitrag wurde 4 mal editiert, zum letzten Mal von Richard95 am .
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15.500

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.176

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

beantworten | zitieren | melden

Zitat
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.
private Nachricht | Beiträge des Benutzers
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

beantworten | zitieren | melden

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?
private Nachricht | Beiträge des Benutzers
Taipi88
myCSharp.de - Member

Avatar #avatar-3220.jpg


Dabei seit:
Beiträge: 1.029
Herkunft: Mainz

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
Cat
myCSharp.de - Member

Avatar #avatar-3070.jpg


Dabei seit:
Beiträge: 771

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

beantworten | zitieren | melden

Hallo Cat,
Zitat
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.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Elias1994 am .
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15.500

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

beantworten | zitieren | melden

Zitat
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
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.176

beantworten | zitieren | melden

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().
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Th69 am .
private Nachricht | Beiträge des Benutzers
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

beantworten | zitieren | melden

Zitat
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?
private Nachricht | Beiträge des Benutzers
Spook
myCSharp.de - Member



Dabei seit:
Beiträge: 241
Herkunft: Esslingen a.N.

beantworten | zitieren | melden

Hallo Elias,
Zitat
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
private Nachricht | Beiträge des Benutzers
Elias1994
myCSharp.de - Member



Dabei seit:
Beiträge: 54

Themenstarter:

beantworten | zitieren | melden

Zitat

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);
private Nachricht | Beiträge des Benutzers
Spook
myCSharp.de - Member



Dabei seit:
Beiträge: 241
Herkunft: Esslingen a.N.

beantworten | zitieren | melden

Es müsste

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

Grüße
spooky
private Nachricht | Beiträge des Benutzers