Phone Vision 10 – Histogram Equalization

7 02 2011

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?

Uniform Distribution

In an image with perfect, uniformly distributed intensities, the probability of any intensity would be the same as the probability of any other intensity.  This generally isn’t possible on a per intensity basis, however, if we consider ranges of intensities, we can get close.  For instance, a random pixel in a high contrast image would have roughly the same probability of being in the light or dark ranges of intensities.  This is achievable through, what I like to call, mathemagic.

Probability

When we roll a die we have six possibilities: 1,2,3,4,5, or 6.  What are the odds that 1 will come up?  1 in 6 right?  In this case our probability is P(1)= .1667.  What about 2? P(2)=.1667 as well, right?  What is the probability of 1 or 2 coming up?  P(1 or 2) = P(1) + P(2) = .3333.  Note that the chances of any side coming up = P(1)+P(2)+P(3)+P(4)+P(5)+P(6)=1.

Back to our situation, what is the probability of a randomly selected pixel having a specific intensity?   Using the histogram, this is trivial to calculate.  Simply take the total number of pixels at the intensity divided by the total number of pixels.

 

image

Okay, so what?  This is where things get a little tricky.

Probability Density Function

If you were to divide every value in a histogram by the number of pixels you would end up with the probability for every intensity.  This is a discrete probability density function (PDF).

The PDF for our wintery tree from last time:

image

Cumulative Distribution Function

If we have a given intensity k, what is the probability that a pixel will have an intensity between 0 and k?  I hope it is fairly apparent that P(intensity≤k)=P(0)+P(1)+…+P(k).  In our case, the possible intensities range from 0 to 255. 

So, for 0≤k≤255 the CDF(k) = P(intensity≤k).

image

Why does this matter?  Well, if we map the current intensities to the CDF, it will evenly distribute our intensities across the spectrum which is exactly what we want.

The Code

private double[] CalculateCDF(WriteableBitmap grayscale)

{

    int[] histogramData = new int[256];

 

    // here we are just calculating the histogram

    // this should be old hat by now

    for (int pixelIndex = 0;

        pixelIndex < grayscale.Pixels.Length;

        pixelIndex++)

    {

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

        histogramData[intensity]++;

    }

 

 

    double[] cumulativeDistributionFunction = new double[256];

    //the first value is just the probability of the first intensity

    //notice we divide the histogram value by the total number of pixels

    //to get the probability

    cumulativeDistributionFunction[0] =

        (((double)histogramData[0]) / grayscale.Pixels.Length);

    for (int intensity = 1; intensity < 256; intensity++)

    {

        //now we just add the current probability

        //to the previous CDF

        cumulativeDistributionFunction[intensity] =

            (((double)histogramData[intensity]) / grayscale.Pixels.Length)

            + cumulativeDistributionFunction[intensity – 1];

    }

 

    return cumulativeDistributionFunction;

}

 

public WriteableBitmap Equalize(

    WriteableBitmap grayscale,

    double[] cumulativeDistFunction)

{

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

    int[] histogramData = new int[256];

    for (int pixelIndex = 0;

        pixelIndex < grayscale.Pixels.Length;

        pixelIndex++)

    {

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

        histogramData[intensity]++;

    }

 

    histogramData = new int[256];

    int maxCount = 0;

    for (int pixelIndex = 0;

        pixelIndex < grayscale.Pixels.Length;

        pixelIndex++)

    {

        //this is where the magic happens

        //first we get the old intensity

        //then we map the old intensity to

        //the CDF and scale it up to 255

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

        byte newIntensity =

            (byte)(cumulativeDistFunction[oldintensity] * 255 + .5);

 

        equalized.Pixels[pixelIndex] =

            255 << 24

            | newIntensity << 16

            | newIntensity << 8

            | newIntensity;

 

        histogramData[newIntensity]++;

               

        if (histogramData[newIntensity] > maxCount)

        {

            maxCount = histogramData[newIntensity];

        }

    }

 

    PlotHistogram(histogramData, maxCount);

    return equalized;

}

 

image

image

Notice how the CDF is now a straight line?  This means that we have done a pretty good job of distributing the intensities.  If we were to run this procedure again it would not change.  While these results are similar to our contrast stretched tree notice how the spacing is not equal.  In fact higher occurring intensities are spaced further apart than lower occurring intensities.

Summary

The math involved here is a little more intense than the other image enhancement techniques we performed.  This is one procedure that I did not find well explained on the internet.  If you have any difficulties understanding it get in touch with me @azzlsoft on Twitter or rich@azzlsoft.com.

Download Code

http://cid-88e82fb27d609ced.office.live.com/embedicon.aspx/Blog%20Files/PhoneVision/PhoneVision%2010%20-%20Histogram%20Equalization.zip

Up next: Intro to Spatial Filters





Project Euler 002 – F#

7 02 2011

This has content has been moved. Please find it here:

http://eulersolutions.com/2011/05/17/project-euler-002-f/





Project Euler 001 – JavaScript

5 02 2011

Problem 1

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

Solution

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

<html xmlns="http://www.w3.org/1999/xhtml"&gt;

<head>

    <title>Project Euler 001</title>

</head>

<body>

    <script type="text/javascript">

        //Add all the natural numbers below one

        //thousand that are multiples of 3 or 5.

        //this one is pretty similar to c#

        answer = 0

        for (three = 0; three < 1000; three += 3) { answer += three; }

        for (five = 0; five < 1000; five += 5) { answer += five; }

        for (fifteen = 0; fifteen < 1000; fifteen += 15)

          { answer -= fifteen; }

        document.write("<b>" + answer + "</b>");

    </script>

</body>

</html>

 

Discussion

Last solution of the week.    Nothing too special here.

If you have questions, leave a comment or find me on Twitter (@azzlsoft) or email (rich@azzlsoft.com).





Project Euler 001 – TSQL

4 02 2011

Problem 1

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

Solution

— I have a table called Number

— that has about 3 million

— integers starting at 1

— this problem is pretty

— simple from a query perspective

 

SELECT SUM(number) FROM Number

WHERE number < 1000

AND ((number % 3 = 0) OR (number % 5 = 0))

Discussion

When it’s possible to fill a table in a reasonable amount of time using brute force techniques, I will.  If the technique for filling the table is interesting I will demonstrate that.  I realize that in many cases this won’t be possible so I will have to use whatever “trick” is required to solve the problem before the sun burns out.

If you have questions, leave a comment or find me on Twitter (@azzlsoft) or email (rich@azzlsoft.com).





Project Euler 001 – IronRuby

2 02 2011

Problem 1

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

Solution

# Add all the natural numbers below one

# thousand that are multiples of 3 or 5.

 

# this is interesting

# I wasn’t sure how to create the arrays

# with a step so I just rejected the ones

# I didn’t want.  For small numbers this

# should work fine

threes = (01000).reject { |i| i % 3 != 0 }

fives = (01000).reject { |i| i % 5 != 0 }

fifteens = (01000).reject { |i| i % 15 != 0 }

 

answer = 0

threes.each { |i| answer += i }

fives.each { |i| answer += i }

fifteens.each { |i| answer -= i }

puts answer

 

Discussion

Remember, Ruby is brand new to me.  I wasn’t really sure how to create an array with a step so I used the “reject” feature.  It felt pretty natural.

If you have questions, leave a comment or find me on Twitter (@azzlsoft) or email (rich@azzlsoft.com).





Project Euler 001 – C#

1 02 2011

Problem 1

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

Solution

namespace ProjectEulerCSharp_001

{

    //Add all the natural numbers below one

    //thousand that are multiples of 3 or 5.

 

    //first, we will get all of

    //the numbers divisible by 3

    class Program

    {

        static void Main(string[] args)

        {

            int answer = 0;

 

            // add the 3’s, the 5’s, and subtract one set of 15’s

            for (int three = 0; three < 1000; three += 3)

              { answer += three; }

            for (int five = 0; five < 1000; five += 5)

              { answer += five; }

            for (int fifteen = 0; fifteen < 1000; fifteen += 15)

              { answer -= fifteen; }

 

            System.Console.WriteLine(answer);

        }

    }

}

 

Discussion

If you saw the F# solution then this should be a relatively easy translation.  The main difference is that I create the sum as I’m building the list.

If you have questions, leave a comment or find me on Twitter (@azzlsoft) or email (rich@azzlsoft.com).





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

 





Project Euler 001 – F#

1 02 2011

This has content has been moved.  Please find it here:

http://eulersolutions.com/2011/05/16/project-euler-001f/