OpenCV: Basic Thresholding Operations

| OpenCV 3.4.2 Open Source Computer Vision

Basic Thresholding Operations

## Goal

In this tutorial you will learn how to:

- Perform basic thresholding operations using OpenCV function cv::threshold

## Cool Theory

Note
  The explanation below belongs to the book Learning OpenCV by Bradski and Kaehler. What is

## Thresholding?

- The simplest segmentation method
- Application example: Separate out regions of an image corresponding to objects which we want to analyze. This separation is based on the variation of intensity between the object pixels and the background pixels.
- To differentiate the pixels we are interested in from the rest (which will eventually be rejected), we perform a comparison of each pixel intensity value with respect to a threshold (determined according to the problem to solve).

- Once we have separated properly the important pixels, we can set them with a determined value to identify them (i.e. we can assign them a value of \(0\) (black), \(255\) (white) or any value that suits your needs).

### Types of Thresholding

- OpenCV offers the function cv::threshold to perform thresholding operations.
- We can effectuate \(5\) types of Thresholding operations with this function. We will explain them in the following subsections.

- To illustrate how these thresholding processes work, let's consider that we have a source image with pixels with intensity values \(src(x,y)\). The plot below depicts this. The horizontal blue line represents the threshold \(thresh\) (fixed).

#### Threshold Binary

- This thresholding operation can be expressed as:

  \[\texttt{dst} (x,y) = \fork{\texttt{maxVal}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\]

- So, if the intensity of the pixel \(src(x,y)\) is higher than \(thresh\), then the new pixel intensity is set to a \(MaxVal\). Otherwise, the pixels are set to \(0\).

#### Threshold Binary, Inverted

- This thresholding operation can be expressed as:

  \[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{maxVal}}{otherwise}\]

- If the intensity of the pixel \(src(x,y)\) is higher than \(thresh\), then the new pixel intensity is set to a \(0\). Otherwise, it is set to \(MaxVal\).

#### Truncate

- This thresholding operation can be expressed as:

  \[\texttt{dst} (x,y) = \fork{\texttt{threshold}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\]

- The maximum intensity value for the pixels is \(thresh\), if \(src(x,y)\) is greater, then its value is truncated. See figure below:

#### Threshold to Zero

- This operation can be expressed as:

  \[\texttt{dst} (x,y) = \fork{\texttt{src}(x,y)}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\]

- If \(src(x,y)\) is lower than \(thresh\), the new pixel value will be set to \(0\).

#### Threshold to Zero, Inverted

- This operation can be expressed as:

  \[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\]

- If \(src(x,y)\) is greater than \(thresh\), the new pixel value will be set to \(0\).

## Code

C++

The tutorial code's is shown lines below. You can also download it from here

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using std::cout;
int threshold_value = 0;
int threshold_type = 3;
int const max_value = 255;
int const max_type = 4;
int const max_binary_value = 255;
Mat src, src_gray, dst;
const char* window_name = "Threshold Demo";
const char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted";
const char* trackbar_value = "Value";
static void Threshold_Demo( int, void* )
{
/* 0: Binary
1: Binary Inverted
2: Threshold Truncated
3: Threshold to Zero
4: Threshold to Zero Inverted
*/
threshold( src_gray, dst, threshold_value, max_binary_value, threshold_type );
imshow( window_name, dst );
}
int main( int argc, char** argv )
{
String imageName("../data/stuff.jpg"); // by default
if (argc > 1)
{
imageName = argv[1];
}
src = imread( imageName, IMREAD_COLOR ); // Load an image
if (src.empty())
{
cout << "Cannot read image: " << imageName << std::endl;
return -1;
}
cvtColor( src, src_gray, COLOR_BGR2GRAY ); // Convert the image to Gray
namedWindow( window_name, WINDOW_AUTOSIZE ); // Create a window to display results
createTrackbar( trackbar_type,
window_name, &threshold_type,
max_type, Threshold_Demo ); // Create Trackbar to choose type of Threshold
createTrackbar( trackbar_value,
window_name, &threshold_value,
max_value, Threshold_Demo ); // Create Trackbar to choose Threshold value
Threshold_Demo( 0, 0 ); // Call the function to initialize
waitKey();
return 0;
}
Java

The tutorial code's is shown lines below. You can also download it from here

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Image;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
public class Threshold {
private static int MAX_VALUE = 255;
private static int MAX_TYPE = 4;
private static int MAX_BINARY_VALUE = 255;
private static final String WINDOW_NAME = "Threshold Demo";
private static final String TRACKBAR_TYPE = "<html><body>Type: <br> 0: Binary <br> "
+ "1: Binary Inverted <br> 2: Truncate <br> "
+ "3: To Zero <br> 4: To Zero Inverted</body></html>";
private static final String TRACKBAR_VALUE = "Value";
private int thresholdValue = 0;
private int thresholdType = 3;
private Mat src;
private Mat srcGray = new Mat();
private Mat dst = new Mat();
private JFrame frame;
private JLabel imgLabel;
public Threshold(String[] args) {
String imagePath = "../data/stuff.jpg";
if (args.length > 0) {
imagePath = args[0];
}
// Load an image
src = Imgcodecs.imread(imagePath);
if (src.empty()) {
System.out.println("Empty image: " + imagePath);
System.exit(0);
}
// Convert the image to Gray
Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY);
// Create and set up the window.
frame = new JFrame(WINDOW_NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set up the content pane.
Image img = HighGui.toBufferedImage(srcGray);
addComponentsToPane(frame.getContentPane(), img);
// Use the content pane's default BorderLayout. No need for
// setLayout(new BorderLayout());
// Display the window.
frame.pack();
frame.setVisible(true);
}
private void addComponentsToPane(Container pane, Image img) {
if (!(pane.getLayout() instanceof BorderLayout)) {
pane.add(new JLabel("Container doesn't use BorderLayout!"));
return;
}
JPanel sliderPanel = new JPanel();
sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.PAGE_AXIS));
sliderPanel.add(new JLabel(TRACKBAR_TYPE));
// Create Trackbar to choose type of Threshold
JSlider sliderThreshType = new JSlider(0, MAX_TYPE, thresholdType);
sliderThreshType.setMajorTickSpacing(1);
sliderThreshType.setMinorTickSpacing(1);
sliderThreshType.setPaintTicks(true);
sliderThreshType.setPaintLabels(true);
sliderPanel.add(sliderThreshType);
sliderPanel.add(new JLabel(TRACKBAR_VALUE));
// Create Trackbar to choose Threshold value
JSlider sliderThreshValue = new JSlider(0, MAX_VALUE, 0);
sliderThreshValue.setMajorTickSpacing(50);
sliderThreshValue.setMinorTickSpacing(10);
sliderThreshValue.setPaintTicks(true);
sliderThreshValue.setPaintLabels(true);
sliderPanel.add(sliderThreshValue);
sliderThreshType.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
thresholdType = source.getValue();
update();
}
});
sliderThreshValue.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
thresholdValue = source.getValue();
update();
}
});
pane.add(sliderPanel, BorderLayout.PAGE_START);
imgLabel = new JLabel(new ImageIcon(img));
pane.add(imgLabel, BorderLayout.CENTER);
}
private void update() {
Imgproc.threshold(srcGray, dst, thresholdValue, MAX_BINARY_VALUE, thresholdType);
Image img = HighGui.toBufferedImage(dst);
imgLabel.setIcon(new ImageIcon(img));
frame.repaint();
}
public static void main(String[] args) {
// Load the native OpenCV library
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// Schedule a job for the event dispatch thread:
// creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Threshold(args);
}
});
}
}
Python

The tutorial code's is shown lines below. You can also download it from here

from __future__ import print_function
import cv2 as cv
import argparse
max_value = 255
max_type = 4
max_binary_value = 255
trackbar_type = 'Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted'
trackbar_value = 'Value'
window_name = 'Threshold Demo'
def Threshold_Demo(val):
#0: Binary
#1: Binary Inverted
#2: Threshold Truncated
#3: Threshold to Zero
#4: Threshold to Zero Inverted
threshold_type = cv.getTrackbarPos(trackbar_type, window_name)
threshold_value = cv.getTrackbarPos(trackbar_value, window_name)
_, dst = cv.threshold(src_gray, threshold_value, max_binary_value, threshold_type )
cv.imshow(window_name, dst)
parser = argparse.ArgumentParser(description='Code for Basic Thresholding Operations tutorial.')
parser.add_argument('--input', help='Path to input image.', default='../data/stuff.jpg')
args = parser.parse_args()
src = cv.imread(args.input)
if src is None:
print('Could not open or find the image: ', args.input)
exit(0)
# Convert the image to Gray
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
cv.namedWindow(window_name)
cv.createTrackbar(trackbar_type, window_name , 3, max_type, Threshold_Demo)
# Create Trackbar to choose Threshold value
cv.createTrackbar(trackbar_value, window_name , 0, max_value, Threshold_Demo)
# Call the function to initialize
Threshold_Demo(0)
# Wait until user finishes program
cv.waitKey()

## Explanation

Let's check the general structure of the program:

- Load an image. If it is BGR we convert it to Grayscale. For this, remember that we can use the function cv::cvtColor :

C++
String imageName("../data/stuff.jpg"); // by default
if (argc > 1)
{
imageName = argv[1];
}
src = imread( imageName, IMREAD_COLOR ); // Load an image
if (src.empty())
{
cout << "Cannot read image: " << imageName << std::endl;
return -1;
}
cvtColor( src, src_gray, COLOR_BGR2GRAY ); // Convert the image to Gray
Java
String imagePath = "../data/stuff.jpg";
if (args.length > 0) {
imagePath = args[0];
}
// Load an image
src = Imgcodecs.imread(imagePath);
if (src.empty()) {
System.out.println("Empty image: " + imagePath);
System.exit(0);
}
// Convert the image to Gray
Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY);
Python
# Load an image
src = cv.imread(args.input)
if src is None:
print('Could not open or find the image: ', args.input)
exit(0)
# Convert the image to Gray
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)

- Create a window to display the result

C++
namedWindow( window_name, WINDOW_AUTOSIZE ); // Create a window to display results
Java
// Create and set up the window.
frame = new JFrame(WINDOW_NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set up the content pane.
Image img = HighGui.toBufferedImage(srcGray);
addComponentsToPane(frame.getContentPane(), img);
// Use the content pane's default BorderLayout. No need for
// setLayout(new BorderLayout());
// Display the window.
frame.pack();
frame.setVisible(true);
Python
# Create a window to display results
cv.namedWindow(window_name)

- Create \(2\) trackbars for the user to enter user input:
  - Type of thresholding: Binary, To Zero, etc...
  - Threshold value

C++
createTrackbar( trackbar_type,
window_name, &threshold_type,
max_type, Threshold_Demo ); // Create Trackbar to choose type of Threshold
createTrackbar( trackbar_value,
window_name, &threshold_value,
max_value, Threshold_Demo ); // Create Trackbar to choose Threshold value
Java
sliderPanel.add(new JLabel(TRACKBAR_TYPE));
// Create Trackbar to choose type of Threshold
JSlider sliderThreshType = new JSlider(0, MAX_TYPE, thresholdType);
sliderThreshType.setMajorTickSpacing(1);
sliderThreshType.setMinorTickSpacing(1);
sliderThreshType.setPaintTicks(true);
sliderThreshType.setPaintLabels(true);
sliderPanel.add(sliderThreshType);
sliderPanel.add(new JLabel(TRACKBAR_VALUE));
// Create Trackbar to choose Threshold value
JSlider sliderThreshValue = new JSlider(0, MAX_VALUE, 0);
sliderThreshValue.setMajorTickSpacing(50);
sliderThreshValue.setMinorTickSpacing(10);
sliderThreshValue.setPaintTicks(true);
sliderThreshValue.setPaintLabels(true);
sliderPanel.add(sliderThreshValue);
Python
# Create Trackbar to choose type of Threshold
cv.createTrackbar(trackbar_type, window_name , 3, max_type, Threshold_Demo)
# Create Trackbar to choose Threshold value
cv.createTrackbar(trackbar_value, window_name , 0, max_value, Threshold_Demo)

- Wait until the user enters the threshold value, the type of thresholding (or until the program exits)
- Whenever the user changes the value of any of the Trackbars, the function Threshold_Demo (update in Java) is called:

C++
static void Threshold_Demo( int, void* )
{
/* 0: Binary
1: Binary Inverted
2: Threshold Truncated
3: Threshold to Zero
4: Threshold to Zero Inverted
*/
threshold( src_gray, dst, threshold_value, max_binary_value, threshold_type );
imshow( window_name, dst );
}
Java
private void update() {
Imgproc.threshold(srcGray, dst, thresholdValue, MAX_BINARY_VALUE, thresholdType);
Image img = HighGui.toBufferedImage(dst);
imgLabel.setIcon(new ImageIcon(img));
frame.repaint();
}
Python
def Threshold_Demo(val):
#0: Binary
#1: Binary Inverted
#2: Threshold Truncated
#3: Threshold to Zero
#4: Threshold to Zero Inverted
threshold_type = cv.getTrackbarPos(trackbar_type, window_name)
threshold_value = cv.getTrackbarPos(trackbar_value, window_name)
_, dst = cv.threshold(src_gray, threshold_value, max_binary_value, threshold_type )
cv.imshow(window_name, dst)

As you can see, the function cv::threshold is invoked. We give \(5\) parameters in C++ code:

- src_gray: Our input image
- dst: Destination (output) image
- threshold_value: The \(thresh\) value with respect to which the thresholding operation is made
- max_BINARY_value: The value used with the Binary thresholding operations (to set the chosen pixels)
- threshold_type: One of the \(5\) thresholding operations. They are listed in the comment section of the function above.

## Results

1. After compiling this program, run it giving a path to an image as argument. For instance, for an input image as:

2. First, we try to threshold our image with a binary threshold inverted. We expect that the pixels brighter than the \(thresh\) will turn dark, which is what actually happens, as we can see in the snapshot below (notice from the original image, that the doggie's tongue and eyes are particularly bright in comparison with the image, this is reflected in the output image).

3. Now we try with the threshold to zero. With this, we expect that the darkest pixels (below the threshold) will become completely black, whereas the pixels with value greater than the threshold will keep its original value. This is verified by the following snapshot of the output image: