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 grain
  • tileGridSize: 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.