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.

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.

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.

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),
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

Intensity Level Slicing With C# – Explore Image Processing

In this tutorial we will be talking about intensity level slicing in image processing. What is Intensity Level Slicing It’s a process that highlights pixels in an arbitrary...

Posted on by Andraz Krzisnik

How To Make Skeletonization With C#

Skeletonization is a morphological process in image processing, which extracts the center lines of all shapes, which look like their skeleton

Posted on by Andraz Krzisnik