Skip to content
AI360XpertBeta

Image Thresholding

Thresholding turns a grayscale image into a binary mask by comparing each pixel to a cutoff, the simplest segmentation step before counting and contours.

One cutoff at 127 splits the gray histogram into background and foreground, painting the row values 50 and 127 black and 128 and 200 white
One cutoff at 127 splits the gray histogram into background and foreground, painting the row values 50 and 127 black and 128 and 200 white

Why Does This Exist?

Counting cells, reading gauges and finding parts on a belt share one need: decide, per pixel, object or background. Thresholding is the cheapest possible decider: compare each intensity to a cutoff TT and paint the pixel white or black. It runs in a single pass, needs no training data, and feeds directly into contour finding and connected components. The broader classical toolbox (edges, regions, watershed) is surveyed on the segmentation page; this page is the thresholding map.

Three cutoff strategies each own a page: global for one cutoff under even light, adaptive for per-pixel cutoffs under uneven light, and Otsu for the automatic histogram-optimal cutoff. This page covers the shared rule, the five OpenCV types, and preprocessing.

Think of It Like This

A height bar at an amusement ride

A ride sign reads "you must be this tall": every child is measured against one bar and sorted into riders and waiters. Nobody judges faces or clothes, only height against the bar. Thresholding measures pixels against a brightness bar the same way.

The analogy stops at fairness. The bar cannot see that a tall child stands in a ditch (shadow) or a short one on tiptoes (glare). One global bar misjudges anyone standing on uneven ground, which is why lighting decides everything.

How It Actually Works

The binary rule and its variants

retval, mask = cv2.threshold(gray, T, 255, cv2.THRESH_BINARY) implements: pixel becomes 255 (foreground) if its value is greater than TT, else 0. Note the strict inequality: a pixel exactly at TT goes to background. The four siblings are THRESH_BINARY_INV (swap black and white), THRESH_TRUNC (cap aboves at TT instead of painting white), THRESH_TOZERO (zero below-TT pixels, keep the rest as-is) and THRESH_TOZERO_INV.

Grayscale first, always

Thresholding reads one intensity per pixel. On color input OpenCV would need a single channel anyway, so convert deliberately with cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) rather than letting a pipeline guess. When color is the actual signal (a dark stain on pale metal), threshold a color distance or a single channel like HSV saturation instead of gray.

Denoise before deciding

A speck of noise above TT becomes a false foreground pixel, and thresholding has no neighborhood to overrule it. Blur lightly (Gaussian 3×33 \times 3 or 5×55 \times 5) before cutting: it suppresses single-pixel spikes while moving real boundaries by less than a pixel. Morphological cleanup after the cut then removes the survivors.

Worked example

Fix T=127T = 127 and read the row [50,127,128,200][50, 127, 128, 200] left to right under strict greater-than. 50>12750 > 127 is false, so 5050 becomes 00. 127>127127 > 127 is false, so 127127 becomes 00: exactly at the cutoff counts as background. 128>127128 > 127 is true, so 128128 becomes 255255. 200>127200 > 127 is true, so 200200 becomes 255255. The mask is [0,0,255,255][0, 0, 255, 255]: two background, two foreground, with the boundary falling between 127127 and 128128.

Code

The point of this snippet is the worked example's rule in plain Python with no dependencies: strict greater-than at T=127T = 127 on the same four values.

# -> Strict greater-than at T = 127 on one row.row = [50, 127, 128, 200]mask = [255 if v > 127 else 0 for v in row]print(mask)# -> [0, 0, 255, 255]

The value 127127 stays black while 128128 flips white, which is exactly the off-by-one the diagram shows at the cutoff line.

Watch Out For

Thresholding color images directly

Feeding BGR into a threshold call either errors or thresholds an arbitrary channel, and brightness-based cuts ignore hue entirely. The symptom is masks that follow illumination instead of objects. Convert to grayscale or the meaningful channel first, every time.

The boundary pixel at exactly T

THRESH_BINARY uses strict greater-than, so edge pixels sitting exactly on the cutoff fall to background. The symptom is one-pixel contour wobble when TT sweeps across flat regions. Harmless once known, baffling in A/B comparisons of nearby thresholds.

The Quick Version

  • Thresholding paints pixels above TT white and the rest black in one pass.
  • Five OpenCV types: binary, inverted, truncate, to-zero and inverted to-zero.
  • The comparison is strict: the row [50,127,128,200][50, 127, 128, 200] at T=127T = 127 gives [0,0,255,255][0, 0, 255, 255].
  • Always convert to grayscale (or the signal channel) before cutting.
  • Blur lightly first; clean survivors with morphology after.