Adaptive Threshold

Learn when and how to use the adaptive threshold operation.

When discussing the thresholding operation, we assumed that the gray levels of the background and the objects to inspect were reasonably uniform across the image. In real-life scenarios, this is rarely the case. In particular, the lighting conditions can vary significantly from one side of the image to the other, especially when the camera’s field of view is more than a few tens of centimeters.

The intensity gradient problem

Let’s consider the following checkerboard image, affected by a strong intensity gradient due to non-uniform lighting:

Press + to interact
A checkerboard image with a strong intensity gradient
A checkerboard image with a strong intensity gradient

Imagine we want to compute an active mask (i.e., white) in the areas of the black squares and inactive (i.e., black) in the areas of the white squares. The surface outside the checkerboard doesn’t matter.

Thresholding the checkerboard

Q

How can we threshold the above image to highlight the black squares?

A)
retval, mask = cv2.threshold(
  src=image,
  thresh=128,
  maxval=255,
  type=cv2.THRESH_BINARY_INV
)
B)
retval, mask = cv2.threshold(
  src=image,
  thresh=None,
  maxval=255,
  type=cv2.THRESH_OTSU
)

mask = 255 - mask
C)

With a uniform threshold, there is no way to highlight all the black squares without highlighting some white squares.

Let’s see what we can do with a uniform threshold:

Press + to interact
import cv2
threshold = 128
image = cv2.imread('./images/checkerboards/lighting_checkerboard.png',
flags=cv2.IMREAD_GRAYSCALE) # Read directly as a grayscale image
# Apply a uniform threshold
retval, mask = cv2.threshold(
src=image, # The source image
thresh=threshold, # The threshold
maxval=255, # The 'active' value
type=cv2.THRESH_BINARY_INV # Highlight gray levels below the threshold
)
# Write to './output'
cv2.imwrite('./output/0_original.png', image)
cv2.imwrite('./output/1_mask.png', mask)

In lines 9–14, we call the cv2.threshold() function on the grayscale image.

We can change the threshold value in line 3 to convince ourselves that no single threshold value would be adequate to highlight only the black squares without highlighting at least some parts of the white squares. Making use of the Otsu algorithm won’t help since it computes a single threshold that is applied uniformly to the image.

Notice that in lines 5 and 6, the image is read with the flags=cv2.IMREAD_GRAYSCALE argument. This shortcut allows us to read a grayscale image, whether the file is a color or a grayscale image. We can spare a call to cv2.cvtColor() because we won’t need the color information for the task.

Adaptive thresholding

Binarizing an image in the presence of a ...