I have been doing a bit of work lately analyzing images from both video streams and static pictures and apparently I have found the process interesting enough to write about it. It's not very often that I can get away from the standard CRUD type application and I really found it interesting to dig into the basics of image analysis.
For the tasks I have been working on I've needed to get down to the individual pixels of an image. There are various ways to do this, some of them possibly easier than what I am going to be walking through, but due to the circumstances of what I needed to do this was then best method for me to accomplish it.
First you need to get the jpeg into a BitmapImage object. It's a pretty simple task, especially if it is coming from a file.
BitmapImage sourceImage = new
BitmapImage(new
Uri("TestImage.jpg"));
Next we need to get the image into a byte array. This is a bit more complicated, but really isn't that bad
int stride = (sourceImage.PixelWidth * sourceImage.Format.BitsPerPixel + 7) / 8;
int size = sourceImage.PixelHeight * stride;
byte[] pixelData = new
byte[size];
sourceImage.CopyPixels(pixelData, stride, 0);
The most complex thing here is understanding what the stride is. Stride is the number of bytes in each row of the image. We basically take the number of pixels wide and multiple that by the number of bits per pixel which is a property off of the image. Since there are 8 bits in a byte we divide by 8 to get the number of bytes. The next key thing is the size of the byte array that we need which can be calculated by the PixelHeight multiplied by the stride. Finally we copy this into an array. We now have an array of bits that constitute the image.
Let's assume for explanation purposes that our image is a 32bit image. That means that each pixel is "described" using 4 bytes. 1 byte is for the alpha channel, and the other three are for the RGB values. To loop through each pixel we do the following:
for (int i = 0; i < pixelData.Length; i += 4)
{
byte blue = pixelData[i];
byte green = pixelData[i + 1];
byte red = pixelData[i + 2];
}
It's basically your standard for loop except we add 4 on each iteration of the loop. This is because we are looping though each pixel, but 1 pixel is represented by 4 bytes. Byte i is the blue channel, byte i + 1 is the green channel, byte i + 2 is the red channel, and finally byte i + 3 is the alpha channel. The table below represents the array for a 2 pixel wide by 8 pixel high image where each cell is a byte in the array and every four cells in 1 pixel. Hopefully this helps to visualize the layout of the data.
B | G | R | A | B | G | R | A |
B | G | R | A | B | G | R | A |
B | G | R | A | B | G | R | A |
B | G | R | A | B | G | R | A |
B | G | R | A | B | G | R | A |
B | G | R | A | B | G | R | A |
B | G | R | A | B | G | R | A |
B | G | R | A | B | G | R | A |
Finally while in the loop you can set each byte to the proper RGB color values to manipulate the image how you want and then you convert the array back into an image using the below code.
WriteableBitmap wBmp = new
WriteableBitmap(sourceImage.PixelWidth, sourceImage.PixelHeight, sourceImage.DpiX,
sourceImage.DpiY, PixelFormats.Bgr32, null);
wBmp.WritePixels(new
Int32Rect(0, 0, sourceImage.PixelWidth, sourceImage.PixelHeight), pixelData, stride, 0);
That's the basics of how to access the individual RGB values for each pixel in an image. It's pretty simple once you wrap you head around how each pixel relates to a byte.