Skillversitas

Histograms and Equalization

A histogram is the population of an image: a count of how many pixels sit at each of the 256 gray levels. It is the diagnostic that tells you, in one glance, whether an image is dark, blown out, or flat - and histogram equalization is the standard fix, remapping the gray levels so the output histogram is flat and the full range is used. The adaptive variant, CLAHE, does the same per tile, which is what makes it the right tool for locally dark or bright images.

Computing and Displaying

import cv2
import numpy as np

img = cv2.imread("photo.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# one channel: the full distribution
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
print(hist[100])        # how many pixels are at level 100

# per-channel for color
hist_b = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_g = cv2.calcHist([img], [1], None, [256], [0, 256])
hist_r = cv2.calcHist([img], [2], None, [256], [0, 256])

# a masked histogram: levels inside one region only
mask = np.zeros(gray.shape, dtype="uint8")
mask[50:150, 50:250] = 255
hist_region = cv2.calcHist([gray], [0], mask, [256], [0, 256])

# bin the hue axis differently for HSV work
h_hist = cv2.calcHist([cv2.cvtColor(img, cv2.COLOR_BGR2HSV)], [0],
                      None, [180], [0, 180])

calcHist(images, channels, mask, histSize, ranges) is the single API: pick which channels, optionally restrict to a mask, choose the bin count and range. The output is a float array of counts. Plotting with matplotlib (or the drawHistogram helper from the tutorial examples) makes the diagnosis visual: a histogram hugging the left is underexposed, hugging the right is overexposed, and a narrow bump in the middle is low contrast.

Equalization: Using the Full Range

import cv2

gray = cv2.cvtColor(cv2.imread("dark.jpg"), cv2.COLOR_BGR2GRAY)

# global equalization: the CDF becomes a ramp, histogram becomes flat
eq = cv2.equalizeHist(gray)

# compare the histograms - the equalized one spreads across 0..255
h_before = cv2.calcHist([gray], [0], None, [256], [0, 256])
h_after = cv2.calcHist([eq], [0], None, [256], [0, 256])

cv2.imwrite("equalized.jpg", eq)

equalizeHist sorts the gray levels and maps them so the cumulative distribution is uniform - the darkest pixel becomes 0, the brightest becomes 255, and everything spreads between. It is one call, grayscale-only, and extremely effective on underexposed images. Its weakness is that it is global: a mostly-bright image with one dark face will stretch the face into mediocrity, and noise in empty histogram bins gets amplified into banding.

CLAHE: Contrast-Limited Adaptive

import cv2

gray = cv2.cvtColor(cv2.imread("scene.jpg"), cv2.COLOR_BGR2GRAY)

# tile-based equalization with a contrast limit
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
out = clahe.apply(gray)

cv2.imwrite("clahe.jpg", out)

CLAHE divides the image into tiles, equalizes each tile's histogram independently, and interpolates between tiles to avoid block boundaries - so a face in shadow gets its contrast boosted without blowing out a bright sky elsewhere. The clipLimit caps how much any tile can amplify: without it, noise in near-flat regions is stretched into visible texture. It is the default choice for "make this image look better" in photography and document processing, and it works on color images by applying it to the L channel of LAB and converting back.

When to Reach for What

SituationTool
Diagnose exposure/contrastcalcHist + a plot
Uniformly dark image, one callequalizeHist
Locally uneven contrast (scans, faces in shadow)CLAHE
Color photoCLAHE on the L channel of LAB
Measuring a color's presenceHue histogram with calcHist on HSV

Summary

Compute histograms with calcHist (channels, optional mask, bins, ranges) as your first diagnostic on any image. Fix global underexposure with the one-call equalizeHist, and use CLAHE when contrast problems are local - it tile-equalizes with a clip limit that keeps noise from amplifying, and it is the standard "improve this image" operator. For color work, run it in the L channel of LAB. The mental model: the histogram is the image's population, equalization is redrawing that population to use the full range, and CLAHE does it neighborhood by neighborhood so the fix follows the lighting.