Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Decision matrix for Global vs Otsu vs Adaptive thresholding, CLAHE parameter tuning, and histogram-based preprocessing
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 7% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 65% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 107% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 134% | 0% |
Image lighting conditions?
├── Uniform lighting, good contrast
│ ├── You know the ideal threshold value
│ │ └── ✅ Global Threshold (cv2.threshold)
│ └── You don't know the ideal value
│ └── ✅ Otsu's Method (automatic optimal threshold)
│
├── Uneven lighting / shadows present
│ └── ✅ Adaptive Threshold (cv2.adaptiveThreshold)
│ ├── General use → ADAPTIVE_THRESH_MEAN_C
│ └── Text/document → ADAPTIVE_THRESH_GAUSSIAN_C (better)
│
└── Very low contrast (details invisible)
└── First enhance contrast, THEN threshold:
├── Moderate enhancement → Histogram Stretching
├── Strong enhancement → Histogram Equalization
└── Local enhancement → CLAHE (best for most cases)| Method | Function | Effect | Best For | |--------|----------|--------|----------| | Histogram Stretching | cv2.normalize(NORM_MINMAX) | Linear rescale min→0, max→255 | Natural-looking enhancement | | Histogram Equalization | cv2.equalizeHist() | Aggressive CDF-based redistribution | Maximum contrast, looks artificial | | CLAHE | cv2.createCLAHE() | Local adaptive equalization | Medical images, preserves local detail |
> Default choice: CLAHE. It provides strong enhancement without the over-amplification artifacts of global equalization.
| Type | Above Threshold | Below Threshold | Use Case | |------|----------------|-----------------|----------| | THRESH_BINARY | White (255) | Black (0) | Standard foreground extraction | | THRESH_BINARY_INV | Black (0) | White (255) | Dark objects on light background | | THRESH_TRUNC | Clamped to threshold | Unchanged | Brightness capping | | THRESH_TOZERO | Unchanged | Set to 0 | Keep only bright regions | | THRESH_TOZERO_INV | Set to 0 | Unchanged | Keep only dark regions |
Thresholding only works on single-channel (grayscale) images.
pythongray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
Otsu minimizes intra-class variance to find the optimal split point. Pass 0 as threshold and add the flag:
python# Otsu finds optimal threshold automatically _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
python# CRASHES cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 10, 3) # blockSize=10 → Error! # CORRECT cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 3) # blockSize=11 ✓
pythonclahe = cv2.createCLAHE(clipLimit=2.5, tileGridSize=(8, 8)) result = clahe.apply(gray)
| Parameter | Range | Effect | |-----------|-------|--------| | clipLimit | 2.0 - 3.0 (typical) | Higher = more contrast, but more noise amplification | | tileGridSize | (4,4) to (16,16) | Smaller tiles = more local adaptation, risk of artifacts |
> Safe defaults: clipLimit=2.5, tileGridSize=(8,8)
cv2.equalizeHist() accepts only single-channel images. For color images, convert to LAB or YCrCb, equalize the L/Y channel only, then convert back.
| Histogram Shape | Diagnosis | Action | |-----------------|-----------|--------| | Clustered on left (dark) | Underexposed image | Apply CLAHE or equalization | | Clustered on right (bright) | Overexposed image | Apply CLAHE or normalize | | Narrow peak in center | Low contrast | Histogram stretching or CLAHE | | Wide, uniform spread | Good contrast | Ready for thresholding | | Two distinct peaks (bimodal) | Clear foreground/background | Otsu will work perfectly |
pythonhist = cv2.calcHist([gray], [0], None, [256], [0, 256]) # [gray] — input image (list) # [0] — channel index # None — no mask # [256] — number of bins # [0, 256] — pixel value range
python# Method 1: Histogram Stretching (natural) stretched = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX) # Method 2: Global Equalization (aggressive) equalized = cv2.equalizeHist(gray) # Method 3: CLAHE (recommended) clahe = cv2.createCLAHE(clipLimit=2.5, tileGridSize=(8, 8)) enhanced = clahe.apply(gray)
python# MEAN_C: simple average of neighborhood binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 3) # GAUSSIAN_C: weighted average (better for text) binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 3) # blockSize=11: neighborhood size (must be odd) # C=3: constant subtracted from computed mean
Other measured skills in the registry, with their headline benchmark lift.