How To Make Adaptive Thresholding Algorithm With C#

Adaptive thresholding operation computes thresholds for each pixel locally and therefore segments images more accurately.


Andraz Krzisnik
How To Make Adaptive Thresholding Algorithm...

Adaptive thresholding adds next level complexity by computing thresholds for every pixel. In order to do that we’ll to compute histogram values for each pixel by taking into account its surrounding pixels.

In general, noise and nonuniform illumination in the image affect thresholding process performance the most. Therefore, sometimes we need to use a different method for thresholding the image, rather than using a global threshold.

We’re going to use elements of convolution with this one, since we need to isolate image data into pixel neighborhoods. In other words, we’re going to iterate through a neighborhood of each pixel and compute its own threshold.

Before we decide to use this process, you could also try and apply image smoothing or use its edge information to solve your problem. These processes are helpful for reducing noise and separating objects and background more clearly.

How does adaptive thresholding work?

As we mentioned already, we need to compute threshold for every pixel neighborhood. Therefore, we’re going to produce an image of threshold values, which will be the same size as the input image.

However, since we’re taking into account surrounding pixels at each point we we’ll need to use zero padding. The reason for this is so we can compute threshold values for pixels in the borders as well.

In case it’s not important if data round the image border is lost to this process, we can also skip the padding step. Because of this, our resulting image is going to have a border of black pixels, since we won’t add data to them.

Let’s get to what we actually need to compute at every pixel in order to get to its threshold. We’re going to use mean and standard deviation for each neighborhood separately. These are the main parts of the formula, which are also the descriptors of average intensity and contrast.

And in case you’re not familiar with the following formulas, the pSxy stands for normalized histogram value for ri intensity.

formulas for local mean and standard deviation for adaptive thresholding
Formulas for local mean and local standard deviation

As you can see in the following formula for local threshold, we also need additional parameters. The reason for this is because we need to adjust them so we can threshold the whole image optimally. In other words, each image will have different noise levels and illumination, so we need to find these parameters experimentally.

Threshold formula for adaptive thresholding
Local threshold formula

The mg parameter in the formula above stands for global mean intensity value. This is just an average intensity value of the whole image.

Improving adaptive thresholding

We can further improve adaptive thresholding by using additional conditions when we’re thresholding the image. The following formula demonstrates how to do it. Just keep in mind that pixels that will yield TRUE will become white and those that will yield FALSE, will become black.

Conditions for improving adaptive thresholding
Conditions for improving adaptive thresholding

Code

public static Bitmap VariableThresholdingLocalProperties(this Bitmap image, double a, double b)
     {
         int w = image.Width;
         int h = image.Height;

         BitmapData image_data = image.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.ReadOnly,
             PixelFormat.Format24bppRgb);

         int bytes = image_data.Stride * image_data.Height;
         byte[] buffer = new byte[bytes];
         byte[] result = new byte[bytes];

         Marshal.Copy(image_data.Scan0, buffer, 0, bytes);
         image.UnlockBits(image_data);

         //Get global mean - this works only for grayscale images
         double mg = 0;
         for (int i = 0; i < bytes; i+=3)
         {
             mg += buffer[i];
         }
         mg /= (w * h);

         for (int x = 1; x < w - 1; x++)
         {
             for (int y = 1; y < h - 1; y++)
             {
                 int position = x * 3 + y * image_data.Stride;
                 double[] histogram = new double[256];

                 for (int i = -1; i <= 1; i++)
                 {
                     for (int j = -1; j <= 1; j++)
                     {
                         int nposition = position + i * 3 + j * image_data.Stride;
                         histogram[buffer[nposition]]++;
                     }
                 }

                 histogram = histogram.Select(l => l / (w * h)).ToArray();

                 double mean = 0;
                 for (int i = 0; i < 256; i++)
                 {
                     mean += i * histogram[i];
                 }

                 double std = 0;
                 for (int i = 0; i < 256; i++)
                 {
                     std += Math.Pow(i - mean, 2) * histogram[i];
                 }
                 std = Math.Sqrt(std);

                 double threshold = a * std + b * mg;
                 for (int c = 0; c < 3; c++)
                 {
                     result[position + c] = (byte)((buffer[position] > threshold) ? 255 : 0);
                 }
             }
         }

         Bitmap res_img = new Bitmap(w, h);
         BitmapData res_data = res_img.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.WriteOnly,
             PixelFormat.Format24bppRgb);
         Marshal.Copy(result, 0, res_data.Scan0, bytes);
         res_img.UnlockBits(res_data);

         return res_img;
     }

Conclusion

I hope this tutorial helped you understand how adaptive thresholding works. There are also other posts about thresholding and other image processing operations on this blog.

You can also download the demo project and try it out yourself.

Related Articles

C# Tutorial

Histogram Equalization And Magic Behind It

This tutorial is meant to demonstrate how histogram equalization works. This is a continuation on contrast stretch articles I’ve made in the past. An article on histogram...

Posted on by Andraz Krzisnik
Queues

How To Use Queues In C# – Data Structures Made Easy

Queues are a limited access data structure, which works similarly to stacks. The difference between the two are their logistic principles.

Posted on by Andraz Krzisnik