Unit 03.02: Thresholding on a photo with uneven light
One threshold cannot suit both ends of an unevenly lit image.
Global versus adaptive
A global threshold applies one number everywhere. An adaptive one compares each pixel with its own neighbourhood.
The code runs both on an image with a strong lighting gradient.
import cv2
import numpy as np
# A bright square on a background that is much darker on the left.
image = np.tile(np.linspace(20, 200, 64, dtype="uint8"), (64, 1))
image[20:44, 8:56] = np.clip(image[20:44, 8:56].astype(int) + 40, 0,
255).astype("uint8")
_, global_thresh = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY)
adaptive = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 21, -5)
left = slice(20, 44), slice(8, 20)
right = slice(20, 44), slice(44, 56)
for label, result in [("global 128", global_thresh), ("adaptive", adaptive)]:
print(f"{label:12} square found on the left: {result[left].mean() > 128:5} "
f" on the right: {result[right].mean() > 128}")
# One global threshold cannot suit both ends of an uneven gradient. Adaptive
# thresholding compares each pixel with its own neighbourhood, which is what
# uneven lighting requires.
The global threshold finds the object at one end of the gradient and misses it at the other - and there is no value of the threshold that finds it at both, because the background at one end is brighter than the object at the other.
Adaptive thresholding sidesteps this entirely by never comparing distant parts of the image with each other.
The mistake this prevents
The mistake is tuning the global threshold until it works on your test image. It will work on that image and on images lit like it, and the failure on differently-lit images is silent - a missing object rather than an error.
Takeaway
Uneven lighting requires an adaptive threshold. No single global value exists when the background at one end is brighter than the object at the other.
