Unit 04.02: Adaptive thresholding when lighting varies
Three thresholding strategies, and only one of them handles a lighting gradient.
Global, Otsu, adaptive
Otsu chooses the global threshold from the histogram rather than by hand - which is still one number for the whole frame.
The code runs all three on a gradient image.
import cv2
import numpy as np
image = np.tile(np.linspace(30, 220, 64, dtype="uint8"), (64, 1))
image[24:40, 8:56] = np.clip(image[24:40, 8:56].astype(int) + 35, 0,
255).astype("uint8")
results = {
"global mean": cv2.threshold(image, int(image.mean()), 255,
cv2.THRESH_BINARY)[1],
"Otsu": cv2.threshold(image, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1],
"adaptive mean": cv2.adaptiveThreshold(image, 255,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 21, -8),
}
for name, result in results.items():
left = result[24:40, 10:20].mean() > 128
right = result[24:40, 44:54].mean() > 128
print(f"{name:14} object detected left: {left!s:5} right: {right}")
# Otsu picks one threshold from the histogram, which is still one number for
# the whole frame. Only the adaptive method compares each pixel with its own
# neighbourhood, and only it finds the object at both ends of the gradient.
Otsu is often presented as the automatic answer, and here it fails the same way a hand-picked global threshold does. Choosing the number optimally does not help when no single number is correct.
Only the adaptive method, which compares each pixel with its own neighbourhood, finds the object at both ends.
The mistake this prevents
The mistake is reaching for Otsu when a global threshold fails. Otsu solves a different problem - picking the threshold without tuning - and if the failure is caused by uneven lighting, it does not address it at all.
Takeaway
Otsu automates the choice of a global threshold; it does not make a global threshold appropriate. Uneven lighting needs a local comparison.
