Phone Vision – 09 Contrast Stretching

1 02 2011

Now that we have a histogram, it’s time to have some fun.  Michael Reichmann at Luminous Landscape uses the following picture for a great post on Understanding Histograms for digital photographers.  He graciously allowed to me to use it here and it’s perfect for what we want to accomplish.

image 

This is a fantastic snow scene, but it would be nice if we could increase the contrast to help us pick out features.  In other words, can we ‘stretch’ the intensities to use the whole spectrum?  Of course we can!

Determining Boundaries

Which intensities do we want to become the new black and white?  You could probably eyeball it and come up with a pretty good approximation, but we’d rather automate this process whenever possible.

Let’s pick a threshold, say, 5% of the highest occurring intensity and use it to determine our lower (black) and upper (white) bounds.  If the highest occurring intensity has 100 pixels, then any intensities with fewer than 5 occurrences will be below our threshold.  Allow the code to clear up any confusion.

public Point DetermineBounds(WriteableBitmap grayscale)

{

    //we will use the x value for

    //the left boundary and the

    //y value for right the boundary

    Point bounds = new Point();

 

    int maxIntensity = 0;

    int threshold = 0;

 

    //once again, we are determining

    //the histogram

    int[] histogramData = new int[256];

    for (int pixelIndex = 0;

        pixelIndex < grayscale.Pixels.Length;

        pixelIndex++)

    {

        byte intensity = (byte)(grayscale.Pixels[pixelIndex] & 0xFF);

        histogramData[intensity]++;

        if (histogramData[intensity] > maxIntensity)

        {

            maxIntensity = histogramData[intensity];

        }

    }

 

    // set the threshold = 5% of max

    threshold = maxIntensity / 20;

 

    //first, let’s find the left bound

    //we start at one to simplify our math

    //if we hit the boundary straight away

    //it will be recorded as zero

    //which is what we want

    for (int intensity = 1;

        intensity < histogramData.Length;

        intensity++ )

    {

        //if the number of occurrences of an intensity

        //is greater than our threshold then we’ve found our boundary

        if (histogramData[intensity] >= threshold)

        {

            //the current intensity is where we

            //crossed the threshold

            //we need to store the previous

            //intensity

            bounds.X = intensity – 1;

            break;

        }

    }

 

    //now let’s find the right bound

    //so we’ll start on the right side.

    for (int intensity = histogramData.Length – 1;

        intensity > 0;

        intensity–)

    {

        //same as before

        if (histogramData[intensity] >= threshold)

        {

            //add 1 this time instead of

            //subtract

            bounds.Y = intensity + 1;

            break;

        }

    }

 

    return bounds;

}

 

And just so we can see what we’re doing, we’ll put in a method to draw the boundaries. 

 

public void DrawBounds(Point bounds)

{

    //double threshold = maxCount * .05;

 

    Line lowIntensity = new Line();

    lowIntensity.Height = HistogramPlot.Height;

    lowIntensity.Width = HistogramPlot.Width;

    lowIntensity.Stroke = new SolidColorBrush(Colors.Blue);

    lowIntensity.StrokeThickness = 2;

    lowIntensity.Y1 = 0;

    lowIntensity.Y2 = HistogramPlot.Height;

    lowIntensity.X1 = (bounds.X / 256.0) * HistogramPlot.Width;

    lowIntensity.X2 = (bounds.X / 256.0) * HistogramPlot.Width;

    HistogramPlot.Children.Add(lowIntensity);

 

    Line highIntensity = new Line();

    highIntensity.Height = HistogramPlot.Height;

    highIntensity.Width = HistogramPlot.Width;

    highIntensity.Stroke = new SolidColorBrush(Colors.Blue);

    highIntensity.StrokeThickness = 2;

    highIntensity.Y1 = 0;

    highIntensity.Y2 = HistogramPlot.Height;

    highIntensity.X1 = (bounds.Y / 256.0) * HistogramPlot.Width;

    highIntensity.X2 = (bounds.Y / 256.0) * HistogramPlot.Width;

    HistogramPlot.Children.Add(highIntensity);

}

 

image

That looks like it accurately portrays the boundaries to me.

Shifting the Intensities

The next two steps should be combined, but it’s a little easier to understand when it’s broken into two separate components.  The first step is to make the left bound the new zero.  This means let’s shift the intensities to the dark side of the spectrum.

public WriteableBitmap ShiftLeft(WriteableBitmap grayscale, Point bounds)

{

    WriteableBitmap leftShifted =

        new WriteableBitmap(grayscale.PixelWidth,grayscale.PixelHeight);

 

    int maxIntensity = 0;

    int[] histogramData = new int[256];

 

    for (int pixelIndex = 0;

        pixelIndex < grayscale.Pixels.Length;

        pixelIndex++)

    {

        byte intensity = (byte)(grayscale.Pixels[pixelIndex] & 0xFF);

 

        //here we just subtract every intensity by the

        //left boundary being careful not to let any

        //negative intensities slip through

        intensity = (byte)Math.Max(intensity – bounds.X, 0);

 

        //the rest of this stuff is just fluff

        leftShifted.Pixels[pixelIndex] =

            255 << 24

            | intensity << 16

            | intensity << 8

            | intensity;

 

        histogramData[intensity]++;

        if (histogramData[intensity] > maxIntensity)

        {

            maxIntensity = histogramData[intensity];

        }

    }

    PlotHistogram(histogramData, maxIntensity);

    return leftShifted;

}

 

image

That was easy and already I feel I can pick out more detail, but we aren’t using any more of the spectrum than we were before.

Stretching the Contrast

The final piece is to use the gamut of intensities.  Note that we are not adding information here.  We are just going to spread out the information we already have.  Let’s get to it.

public WriteableBitmap Stretch(

    WriteableBitmap leftShifted,

    Point bounds)

{

    WriteableBitmap stretched =

        new WriteableBitmap(

            leftShifted.PixelWidth,

            leftShifted.PixelHeight);

 

    int maxIntensity = 0;

    int[] histogramData = new int[256];

 

    for (int pixelIndex = 0;

        pixelIndex < leftShifted.Pixels.Length;

        pixelIndex++)

    {

        byte intensity = (byte)(leftShifted.Pixels[pixelIndex] & 0xFF);

 

        //here’s where the magic happens

        //in our example, our low bound was

        //184 and our high bound was 245

        //we have already shifted everything left

        //now we just need to spread it out

        //our target range is 255 intensities,

        //but our current intensity range is only

        //61 (245 – 184).  So we want to scale up

        //our intensities appropriately.  Our scaling

        //factor becomes 255 / 61 ~ 4.18.

        // 0 -> 0

        // i -> i * 4.18

        // 61 -> 255

        // > 61 -> 255

        intensity = (byte)Math.Min

            (intensity * 255 / (bounds.Y – bounds.X), 255);

 

        stretched.Pixels[pixelIndex] =

            255 << 24

            | intensity << 16

            | intensity << 8

            | intensity;

 

        histogramData[intensity]++;

        if (histogramData[intensity] > maxIntensity)

        {

            maxIntensity = histogramData[intensity];

        }

    }

    PlotHistogram(histogramData, maxIntensity);

    return stretched;

}

 

See that’s not so bad.

image

Now, that is a picture!  Notice how the histogram bars have spaces between them?  This is because we aren’t actually adding information.  We’re just making it easier to see.

Summary

I really like this picture.  It works out great for a project like this and we are finally starting to do some useful image processing.  The beauty of this lesson is that it took no human interaction to perform and the results are pretty spectacular.

If you have any questions find me on Twitter (@azzlsoft).

Download Code

http://cid-88e82fb27d609ced.office.live.com/embedicon.aspx/Blog%20Files/PhoneVision/PhoneVision%2009%20-%20Contrast%20Stretching.zip

Up Next: Histogram Equalization

 

Advertisements

Actions

Information

2 responses

1 02 2011
Phone Vision 08 – Intro to Histograms «

[…] Up next: Contrast Stretching […]

11 02 2011
Phone Vision 10 – Histogram Equalization «

[…] Last time we stretched the intensities of a specific range of values.  The method for determining the bounds was somewhat arbitrary and we simply spread the intensities equally.  Stretching uses more of the intensity spectrum and, hence, increases contrast, but it may not be the best approach to preserve information.  Could we be a little more intelligent about how we distribute the intensities? […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: