Navigation

Related Articles

Back to Latest Articles

How To Make Otsu Thresholding Algorithm With C#

Otsu thresholding is a global thresholding method, with which we find optimal threshold intensity to ensure the maximum separability.


Andraz Krzisnik
How To Make Otsu Thresholding Algorithm With...

Otsu thresholding is a slightly more complex operation for segmenting image pixels into two groups. Furthermore, its advantage is in finding the optimal threshold intensity.

In general, thresholding objective is to minimize average error when we’re assigning pixels into 2 or more groups. In our case, we’re dealing with global thresholding, so we’ll segment image into two groups or classes.

We’re going to use Bayesian decision function to find the optimal threshold. This simply implies that we’re going to use a separability function to measure which intensity yields the biggest separability. It also means, we’re going to do computation only on histogram values.

This function is based on probability density function of intensities for each group of pixels. Therefore, we’re going to normalize histogram values, so the sum of its values will be equal to 1. In other words, we’re going to divide each histogram value with the number of all pixels in the image.

How does Otsu thresholding work?

As we mentioned above, we’re going to need a function for finding optimal threshold intensity. Therefore, we’re using Otsu’s method, which maximizes between-class variance.

What is between-class variance?

Between-class variance is a variable, which tells us how good is the separability between the two classes of pixels. However, it’s not a separability measure, it’s its parameter.

But we can still use it to find the optimal intensity. The reason for that is because the other parameter, global variance, is a constant. And the separability measure is just a ratio between the two, which yields a value between 0 and 1.

What’s the process sequence?

First of all, as we mentioned already, we need to get normalized histogram. Secondly, we need to get cumulative sums of each classes. In other words, we split the normalized histogram at the threshold and sum together each part separately.

Formula for cumulative sum for Otsu thresholding
Formula for cumulative sum – k defines the threshold intensity

Next step is to get the cumulative means, which are simply sums of products between each intensity and its probability. We’ll also need the global mean intensity value, which is just the average intensity on the entire image.

Formula for cumulative mean for Otsu thresholding
Formula for cumulative mean

Next step is most important for this process, which is calculation of between-class variance. In order to find the optimal threshold, we need to check for all possible intensities and find which one yields the largest between-class vairance.

Formula for between-class variance for Otsu thresholding
Formula for between-class variance

And finally once we have the optimal threshold intensity, we apply thresholding process as we did in other segmentation operations. Therefore, pixels that have larger intensity than threshold turn white and those that have lower or equal turn black.

Code

public static Bitmap OtsuThresholding(this Bitmap image)
     {
         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 histogram values
         double[] histogram = new double[256];
         for (int i = 0; i < bytes; i+=3)
         {
             histogram[buffer[i]]++;
         }

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

         //Global mean
         double mg = 0;
         for (int i = 0; i < 255; i++)
         {
             mg += i * histogram[i];
         }

         //Get max between-class variance
         double bcv = 0;
         int threshold = 0;
         for (int i = 0; i < 256; i++)
         {
             double cs = 0;
             double m = 0;
             for (int j = 0; j < i; j++)
             {
                 cs += histogram[j];
                 m += j * histogram[j];
             }

             if (cs == 0)
             {
                 continue;
             }

             double old_bcv = bcv;
             bcv = Math.Max(bcv, Math.Pow(mg * cs - m, 2) / (cs * (1 - cs)));

             if (bcv > old_bcv)
             {
                 threshold = i;
             }
         }

         for (int i = 0; i < bytes; i++)
         {
             result[i] = (byte)((buffer[i] > 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 was helpful in understanding in how Otsu thresholding works.

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

Related Articles

C# Tutorial

How To Use Gaussian Highpass Filter – C# Guide

Gaussian highpass filter processes images in frequency domain. It attenuates low frequencies without creating ringing artifacts.

Posted on by Andraz Krzisnik
Grayscale Morphology

How To Make Morphological Smoothing Work With C#

Morphological smoothing is an image processing technique, which includes grayscale erosion and dilation, and grayscale opening and closing

Posted on by Andraz Krzisnik