C# Tutorial: How To Apply Contrast Stretch To An Image


Andraz Krzisnik
C# Tutorial: How To Apply Contrast Stretch...

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.

How it works?

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.

The process used is described with equation below

Contrast Stretch
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).

We are not done yet

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.

Code for contrast stretch/normalization

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

Complete project is available here (Made with Visual Studio 2015)

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.

bfaf

Show Comments (1)

Comments

Related Articles

Line Detection

How To Make Line Detection Algorithm With C#

Line detection is a segmentation technique in image processing, with which we can extract thin lines with respect to each filter kernel.

Posted on by Andraz Krzisnik
Morphological Processes

How To Make Image Dilation Work With C#

Image dilation is one of the fundamental morphological processes, which we demonstrate it here in C# programming language.

Posted on by Andraz Krzisnik