OpenCV #007 Thresholding

OpenCV #007 Thresholding

Digital Image Processing using OpenCV (Python & C++)

Highlights: In this post we will learn about Thresholding. This method is essential in many computer vision applications. The purpose of this post is to show and benchmark performance of several different approaches, and also to provide you with the necessary knowledge to implement it.

Tutorial Overview:

  1. Thresholding
  2. Simple thresholding
  3. Adaptive thresholding
  4. Otsu’s Binarization

1. Thresholding

In the last post we explained why edges are important for better understanding of the image. Here we will present one new method, which can help us to find them.
This method is known as Thresholding.

Thresholding is both simple and effective method for image segmentation. Generally, what we do when we look for thresholds is we take a histogram of the intensities. In the \(x\) direction, it can go from 0 to 255 (pixel values), and in \(y\) direction we have the number of pixels that have those intensity values. Then, we will create a new binary image depending where we have set a threshold value.

thresholding-histogram

There are three main types of thresholding, based on how threshold value is calculated.

Types of Thresholding:

  • Simple thresholding
  • Adaptive Thresholding
  • Otsu’s Binarization

2. Simple thresholding

This process is pretty easy. If a pixel value is greater than a threshold value, it is set to one value. Maybe it will become white pixel. On the other hand, if it is lower than a threshold value it may be set to a black value. The function used for this is called threshold, both in Python and C++.

Different types of a thresholding algorithm are supported:

  • THRESH_BINARY
  • THRESH_BINARY_INV
  • THRESH_TRUNC
  • THRESH_TOZERO
  • THRESH_TOZERO_INV

Python

C++

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	// Loading the actual image
    Mat image, gray;
    Mat thresh1, thresh2, thresh3, thresh4, thresh5;

    image=imread("car.jpg", IMREAD_COLOR);
    cv::imshow("Original image", image);
    cv::waitKey();

    // Edge cases
    if(image.empty()){
    	cout << "Error loading image" << endl;
    	return -1;
    }

    // converting the color image into grayscale
    cvtColor(image, gray, COLOR_BGR2GRAY);
    
    // First type of Simple Thresholding is Binary Thresholding
    // After thresholding the image with this type of operator, we will
    // have image with only two values, 0 and 255.
    threshold( gray, thresh1, 127, 255, THRESH_BINARY );
    
    // Inverse binary thresholding is just the opposite of binary thresholding.
    threshold( gray, thresh2, 127, 255, THRESH_BINARY_INV );
    
    // Truncate Thresholding is type of thresholding where pixel
    // is set to the threshold value if it exceeds that value.
    // Othervise, it stays the same.
    threshold( gray, thresh3, 127, 255, THRESH_TRUNC );
    
    // Threshold to Zero is type of thresholding where pixel value stays the same
    // if it is greater than the threshold. Otherwise it is set to zero.
    threshold( gray, thresh4, 127, 255, THRESH_TOZERO );
    
    // Inverted Threshold to Zero is the opposite of the last one.
    // Pixel value is set to zero if it is greater than the threshold.
    // Otherwise it stays the same.
    threshold( gray, thresh5, 127, 255, THRESH_TOZERO_INV );
    // Displaying the result
    cv::imshow("THRESH_BINARY", thresh1);
    cv::imshow("THRESH_BINARY_INV", thresh2);
    cv::imshow("THRESH_TRUNC", thresh3);
    cv::imshow("THRESH_TOZERO", thresh4);
    cv::imshow("THRESH_TOZERO_INV", thresh5);

    cv::waitKey(0);
    return 0;
}

Output:

thresholding-trunc-tozero-tozero_inv-results
Results of Simple Thresholding methods

3. Adaptive Thresholding

The algorithm calculates the threshold for small/local regions of the image. This will give us better results for images with varying illumination. Subsequently, we get different thresholds for different regions of the same image.

In contrast to the Simple Thresholding, here, we have one additional parameter. This parameter controls how a threshold value is calculated. We can use ADAPTIVE_THRESH_MEAN_C if we want the threshold value to be the mean of pixel intensities in a neighbourhood area. In addition, ADAPTIVE_THRESH_GAUSSIAN_C can be used if we want that the neighbourhood pixel values are multiplied with weights obtained from a Gaussian kernel.

In addition to that, we must define the block size (neighbourhood area), and the constant that will be subtracted from the mean/weighted mean.

Python

C++

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	// Loading the actual image
    Mat image, gray;
    Mat thresh1, thresh2;

    image=imread("car.jpg", IMREAD_COLOR);
    cv::imshow("Original image", image);
    cv::waitKey();

    // Edge cases
    if(image.empty()){
    	cout << "Error loading image" << endl;
    	return -1;
    }

    // converting the color image into grayscale
    cvtColor(image, gray, COLOR_BGR2GRAY);
    
    // ADAPTIVE_THRESH_MEAN_C - Threshold value is the mean of neighbourhood area.
    // ADAPTIVE_THRESH_GAUSSIAN_C - Threshold value is the weighted sum of neighbourhood 
    // values where weights are a Gaussian window.
    // Block Size - It decides the size of neighbourhood area.
    // C - It is just a constant which is subtracted from the mean or weighted mean calculated.

    adaptiveThreshold(gray, thresh1, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, 2);
    adaptiveThreshold(gray, thresh2, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 11, 2);

    // Displaying the result
    cv::imshow("Adaptive Mean Thresholding", thresh1);
    cv::imshow("Adaptive Gaussian Thresholding", thresh2);

    cv::waitKey(0);
    return 0;
}

Output:

thresholding-mean-and-guassian-adaptive-thresholding
Adaptive thresholding

4. Otsu’s Binarization

This method is developed for a special case. Those are images that have two dominant gray intensity levels. This is called a bimodal image. For such images, we can use Otsu’s method, because it automatically calculates a threshold value from the image histogram. For bimodal images, we can approximately take a value in the middle of those peaks as a threshold value.

It basically assumes that our system is bimodal, and finds the cutoff that minimizes the within-group variance. So, we are only allowed to draw one line. We can do that when it’s bimodal. There is a case when the image is actually tri-modal, but for now, we will only talk about bimodal images.

thresholding-otsu's-method
Otsu’s Binarization

Python

C++

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    // Loading the actual image
    Mat image, gray, blurred;
    Mat thresh1, thresh2;

    image=imread("car.jpg", IMREAD_COLOR);
    cv::imshow("Original image", image);
    cv::waitKey();

    // Edge cases
    if(image.empty()){
    	cout << "Error loading image" << endl;
    	return -1;
    }

    // converting the color image into grayscale
    cvtColor(image, gray, COLOR_BGR2GRAY);

    // Otsu's thresholding
    threshold(gray, thresh1, 0, 255, THRESH_BINARY+THRESH_OTSU);

    // Otsu's thresholding with Gaussian filtering
    GaussianBlur(gray, blurred, Size(5,5),0);
    threshold(blurred, thresh2, 0, 255, THRESH_BINARY+THRESH_OTSU);

    // Displaying the result
    cv::imshow("Otsu's thresholding", thresh1);
    cv::imshow("Otsu's thresholding with Gaussian filtering", thresh2);

    cv::waitKey(0);
    return 0;
}

Output:

Otsu's Binarization mathod
Otsu’s Binarization

Summary

To summarize, we have learned how to use Thresholding to segment and image, which are important for us to better understand the image. In the next post, we will talk more about Canny Edge Detector.

More resources on the topic:

 

Leave a Reply

Your email address will not be published. Required fields are marked *