C# Tutorial: How To Apply Contrast Stretch To An Image
Contrast stretch or otherwise known as normalization is a process where your image’s intensity is changed in such a way, that dark pixels become darker and light pixels...
Filter by Category
Contrast stretch or otherwise known as normalization is a process where your image’s intensity is changed in such a way, that dark pixels become darker and light pixels...
Grayscale images are basically black and white images. These images are good for programming, since their pixels hold only intensity values or in other words, shades of gray, they...
Why is image optimization important? There are a few different image formats that you can choose from to display it on your website. But it is not all the same even if it may look...
Contrast stretch or otherwise known as normalization is a process where your image’s intensity is changed in such a way, that dark pixels become darker and light pixels become lighter. In other words this phenomenon would be described as changing the range of intensities in your image.
First of all, code below will work only if an input is a grayscale image. As a result, it will output another grayscale image.
The intensity range will be changed to a full range of pixel intensities. As it has often been sufficient that images we use are 8-bit, this code works only with 8-bit images.
When we change the range from e.g. 36 – 130 to 0 – 255, firstly we subtract from each pixel intensity the minimal intensitiy that appears on our image, in this case that would be 36. So when we subtract 36 from every pixel intensity on our image, we are left with the new minimal intensity of the image of 0. At this point our entire image has become darker.
Next step is to set the maximum intensity on our image to 255. We do that by dividing 255 with the difference of first maximum and minimum intensities (130 minus 36, in this case this difference would be 94). After all this you are left with an image that has a full range of what an 8-bit pixel can have (0 to 255).
I take you through each step of the code with comments of what every piece of code actually does to an image.
Before we changed the range, we counted the number of pixels for each intensity in the image. True magic happens here, where we set a percent of pixels that will turn black and white.
When we counted those pixels and mapped them to each intensity, we actually take the pixels that hold the darkest shades and turn those black (intensity is turned to 0). And on the opposite side, we take the lightest and turn those to white (intensity is turned to 255).
We control the number of these pixels with percentage we set for each sides.
public static Bitmap Normalization(Bitmap srcImage, double blackPointPercent = 0.1, double whitePointPercent = 0.1) { //Lock bits for your source image into system memory Rectangle rect = new Rectangle(0, 0, srcImage.Width, srcImage.Height); BitmapData srcData = srcImage.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); //Create a bitmap to which you will write new pixel data Bitmap destImage = new Bitmap(srcImage.Width, srcImage.Height); //Lock bits for your writable bitmap into system memory Rectangle rect2 = new Rectangle(0, 0, destImage.Width, destImage.Height); BitmapData destData = destImage.LockBits(rect2, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); //Get the width of a single row of pixels in the bitmap int stride = srcData.Stride; //Scan for the first pixel data in bitmaps IntPtr srcScan0 = srcData.Scan0; IntPtr destScan0 = destData.Scan0; var freq = new int[256]; unsafe { //Create an array of pixel data from source image byte* src = (byte*)srcScan0; //Get the number of pixels for each intensity value for (int y = 0; y < srcImage.Height; ++y) { for (int x = 0; x < srcImage.Width; ++x) { freq[src[y * stride + x * 4]]++; } } //Get the total number of pixels in the image int numPixels = srcImage.Width * srcImage.Height; //Set the minimum intensity value of an image (0 = black) int minI = 0; //Get the total number of black pixels var blackPixels = numPixels * blackPointPercent; //Set a variable for summing up the pixels that will turn black int accum = 0; //Sum up the darkest shades until you reach the total of black pixels while (minI < 255) { accum += freq[minI]; if (accum > blackPixels) break; minI++; } //Set the maximum intensity of an image (255 = white) int maxI = 255; //Set the total number of white pixels var whitePixels = numPixels * whitePointPercent; //Reset the summing variable back to 0 accum = 0; //Sum up the pixels that are the lightest which will turn white while (maxI > 0) { accum += freq[maxI]; if (accum > whitePixels) break; maxI--; } //Part of a normalization equation that doesn't vary with each pixel double spread = 255d / (maxI - minI); //Create an array of pixel data from created image byte* dst = (byte*)destScan0; //Write new pixel data to the image for (int y = 0; y < srcImage.Height; ++y) { for (int x = 0; x < srcImage.Width; ++x) { int i = y * stride + x * 4; //Part of equation that varies with each pixel double value = Math.Round((src[i] - minI) * spread); byte val = (byte)(Math.Min(Math.Max(value, 0), 255)); dst[i] = val; dst[i + 1] = val; dst[i + 2] = val; dst[i + 3] = 255; } } } //Unlock bits from system memory srcImage.UnlockBits(srcData); destImage.UnlockBits(destData); return destImage; }
I would like to note that this software comes with modified grayscale conversion function, which accepts every kind of image format.
In this contrast stretch function I’ve set 2% of darkest pixels to turn black and 1% of the lightest pixels to turn white.
Comments