CV Histograms and Contrast
A histogram counts how many pixels share each brightness level. A grayscale image has 256 levels, from 0 for pure black to 255 for pure white. Imagine sorting marbles into 256 jars lined up from darkest to lightest. The height of each jar shows how many marbles landed inside. That row of jars is the histogram.
Reading a Histogram
The shape of the jars reveals the story of the image without looking at it.
Dark photo Bright photo Flat, low contrast Healthy contrast # # ### # # ## ## ##### ## ## #### #### ##### #### #### ###### ###### ##### ########## 0 ----------- 255 0 ----------- 255 0 ----------- 255 0 ----------- 255
- Bars bunched on the left mean a dark image
- Bars bunched on the right mean a bright image
- Bars squeezed in the middle mean a dull, foggy look
- Bars spread across the full range mean strong contrast
Computing a Histogram
import cv2
import matplotlib.pyplot as plt
gray = cv2.imread("photo.jpg", cv2.IMREAD_GRAYSCALE)
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
plt.plot(hist)
plt.xlim([0, 256])
plt.show()Contrast Stretching
Contrast stretching widens a squeezed histogram. The method finds the darkest and brightest pixel and spreads every value across the full 0 to 255 range.
new = (old - min) x 255 / (max - min) Example: min = 90, max = 160, old = 125 new = (125 - 90) x 255 / 70 = 127
A pixel in the middle of the narrow band lands in the middle of the full range. Dark pixels sink toward 0 and bright pixels climb toward 255.
Histogram Equalization
Equalization goes further. The algorithm studies how pixel counts add up from dark to bright and reassigns brightness so each level holds a similar share of pixels. Crowded levels spread apart, and the image gains punch.
BEFORE AFTER
levels crowded near 100-140 levels spread over 0-255
#### ## ## ## ## ##
###### ## ## ## ## ##
######## ## ## ## ## ##
equalized = cv2.equalizeHist(gray)Equalization works well on X-ray style pictures, foggy outdoor shots, and scans with weak lighting.
CLAHE for Natural Results
Plain equalization treats the whole image as one block. Bright windows and dark corners fight for the same adjustment, and noise often explodes. CLAHE, short for Contrast Limited Adaptive Histogram Equalization, splits the image into small tiles and equalizes each tile on its own. A clip limit stops any tile from boosting contrast too aggressively.
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
result = clahe.apply(gray)Parameter Guide
clipLimit: values from 2 to 4 suit most photos; higher values add graintileGridSize: an 8 by 8 grid balances local detail and smoothness
Color Images
Equalizing the blue, green, and red channels separately shifts colors and creates odd tints. A safer plan converts the picture to a color space that separates brightness from color, adjusts only the brightness channel, and converts back.
BGR image -> LAB space -> CLAHE on L channel only -> BGR image
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
l = clahe.apply(l)
fixed = cv2.cvtColor(cv2.merge((l, a, b)), cv2.COLOR_LAB2BGR)Gamma Correction
Gamma correction brightens or darkens midtones while leaving black and white nearly untouched. A gamma value below 1 lifts dark scenes, and a value above 1 tames washed-out ones. A lookup table applies the change to every pixel quickly.
When Histogram Tools Help
- Preparing dim images before edge detection
- Making faint text readable before text recognition
- Normalizing lighting across a training set
- Comparing two images by their brightness patterns
Heavy contrast changes can hide real detail or amplify noise. Review results by eye and by histogram, and pick the gentlest method that solves the problem.
Practice Exercise
Take a photo in dim light, such as a room at dusk. Plot its histogram and note where the bars cluster. Apply plain equalization and CLAHE to the same image, and plot both histograms side by side. Compare the pictures by eye. Plain equalization usually lifts shadows strongly and adds grain, while CLAHE keeps a calmer, more natural look.
Questions to Ask Yourself
- Did the bright areas lose detail after the adjustment?
- Did noise become visible in flat regions such as walls or sky?
- Does the new histogram cover the full range from black to white?
Answering these questions builds the habit of judging results with both the eye and the numbers.
