find a color in an image in c#

30,426

Solution 1

Well, you can always use the GDI+ method.

Bitmap b = new Bitmap( "some path" );
Color x = b.GetPixel( x, y );

However, GetPixel is actually pretty slow. Try it that way first, but if you need to scan many relatively large images it may not work for you. In that case, use LockBits to get a pointer to a contiguous memory chunk. You can then loop through the image quickly, but you have to know how to manipulate pointers, though it's not terribly complicated.

EDIT: Using LockBits to look at each pixel:

Bitmap b = new Bitmap( "some path" );
BitmapData data = b.LockBits( new Rectangle( 0, 0, b.Width, b.Height ),
ImageLockMode.ReadOnly, b.PixelFormat );  // make sure you check the pixel format as you will be looking directly at memory

unsafe
{         
    // example assumes 24bpp image.  You need to verify your pixel depth
    // loop by row for better data locality
    for( int y = 0; y < data.Height; ++y )
    {
        byte* pRow = (byte*)data.Scan0 + y * data.Stride;
        for( int x = 0; x < data.Width; ++x )
        {
            // windows stores images in BGR pixel order
            byte r = pRow[2];
            byte g = pRow[1];
            byte b = pRow[0];

            // next pixel in the row
            pRow += 3;
        }
    }
}

b.UnlockBits(data);

If your images are padded at the end you can use the BitmapData.Stride property to get to the start of each new row (otherwise you will be reading some junk and your offsets will be screwy).

Solution 2

In addition to Ed Swangren´s answer with LockBits you can avoid pointers all together. If you are using the WPF (or have access to) you can do something like this:

var bitmap = new BitmapImage(uri);

//Pixel array
byte[] pixels = new byte[width * height * 4]; //account for stride if necessary

bitmap.CopyPixels(..size, pixels, fullStride, 0); 

And now you can just do a regular array-scan with pixels without pointers. This snippet is a few years old and from a project where I developed my own AmbiLight solution so it can be pretty fast. Again, this is a few years old, so new classes like WriteableBitmap etc. may be better and avoid a array-copy. In my initially solution I used pointers, but when I figured out that it wasn't necessary I switched.

Share:
30,426
Randster
Author by

Randster

Updated on September 14, 2020

Comments

  • Randster
    Randster almost 4 years

    I stumbled across this youtube video here http://www.youtube.com/watch?v=Ha5LficiSJM that demonstrates someone doing color detection using the AForge.NET framework. I'd like to duplicate what that author has done but I'm not sure how to do some image processing.

    It would appear that the AForge.NET framework allows you to pull down from the video source the image in a Bitmap format. My questions is could anyone point me in the direction or provide some guidance on how to interrogate a Bitmap object to find specific colors in it? (for example - if there is 'Red' or 'Purple' in the image for X seconds, I'd like to raise an event 'ColorDetected' or so...)

    Does anyone have any suggestions on where to start?

    Thanks,

    -R.

    EDIT: Would I need to walk the entire Bitmap object and interrogate each pixel for the color? Like so: http://msdn.microsoft.com/en-us/library/system.drawing.bitmap.getpixel.aspx

  • Randster
    Randster over 13 years
    Thanks for the tip; it looks like LockBits method would be the way to go. Do you have any other pointers that may be useful? The webcam I'm using is able to provide 30fps; so I'm obviously going to be threading the image detection. Are there any other suggestions you might have to improve speed?
  • Ed S.
    Ed S. over 13 years
    Sure, I can provide an example. However, it isn't clear from your post how you will be using this. Are you going to be looking at each pixel in each image, or one, a small area, etc.?
  • Ed S.
    Ed S. over 13 years
    Yeah, I agree. Start with the easiest method and do some timing tests. If it works, great, if not, move onto a different method.
  • Randster
    Randster over 13 years
    Thanks for the example! I'll definitely look into this.
  • Randster
    Randster over 13 years
    Agreed that I should measure first and see what works best. Thanks to the both of you for the tips.
  • Lasse Espeholt
    Lasse Espeholt over 13 years
    You're welcome :) I just simplified the code a bit, BitmapImage also have .CopyPixels()...
  • Callum Rogers
    Callum Rogers over 13 years
    You need to wrap this in unsafe { } or put the unsafe keyword in the method/class definition, then also enable unsafe code in the project's properties.
  • Olivier Leneveu
    Olivier Leneveu over 9 years
    You also need to make a call to b.UnlockBits(data) at the end
  • Nachiappan R
    Nachiappan R almost 9 years
    Hi Lasse, can we handle this method for EPS image format?
  • Lasse Espeholt
    Lasse Espeholt almost 9 years
    @NachiappanR This answer is outdated. I'm sure there is a better way now.
  • Nachiappan R
    Nachiappan R almost 9 years
    @LasseEspeholt, thanks for your intimation, did you tried any latest method.
  • Nyerguds
    Nyerguds almost 7 years
    You should really add the stride to the code; with 24bpp it's guaranteed to mess up on some images. Just calculate the ptr for each pixel, or at least reset it to origPtrSrc + y*stride. at the start of each line.
  • Nyerguds
    Nyerguds almost 7 years
    You can do the same without wpf by using Marshal.Copy to copy the bytes from the ptr of the BitmapData object into a simple byte array.
  • Ed S.
    Ed S. almost 7 years
    @Nyerguds: Yep, will do