Image Histograms and Enhancement - iffatAGheyas/computer-vision-handbook GitHub Wiki
📊 Image Histograms & Enhancement
Histograms are a powerful tool for analysing and enhancing image contrast by showing the distribution of pixel intensities.
1. What is an Image Histogram?
A histogram is a graphical representation of the distribution of pixel intensity values in an image.
Simply put:
It tells you how many pixels have each brightness (or colour) level.
2. Histogram for a Grayscale Image
- X-axis: pixel intensity (0–255)
- Y-axis: number of pixels at each intensity
Interpretation Examples:
- Dark image → skewed left (most pixels near 0)
- Bright image → skewed right (most pixels near 255)
3. Code Example: Grayscale Histogram
import cv2
import matplotlib.pyplot as plt
# Load image in grayscale
img_gray = cv2.imread("bird.jpg", cv2.IMREAD_GRAYSCALE)
# Plot histogram
plt.figure(figsize=(10, 4))
plt.hist(img_gray.ravel(), bins=256, range=[0, 256], color='gray')
plt.title("Grayscale Image Histogram")
plt.xlabel("Pixel Intensity")
plt.ylabel("Frequency")
plt.grid(True)
plt.show()
Example Histogram Plot
🧳 Histogram Enhancement Techniques
Histogram-based methods adjust the distribution of pixel intensities to improve image contrast. This section covers Contrast Stretching (Normalization)—a simple yet effective global enhancement technique.
🎨 Contrast Stretching (Normalization)
Contrast Stretching remaps pixel values so they span the full intensity range [0–255], boosting contrast in washed-out images.
Why It’s Needed
A low-contrast image has its pixel values crammed into a narrow band (e.g. [90, 150]) instead of the full [0, 255], making the scene look dull—imagine a song played quietly between volume levels 40 and 60.
Implementation
import cv2
import matplotlib.pyplot as plt
# Normalize pixel values to [0, 255]
img_stretched = cv2.normalize(img_gray, None, 0, 255, cv2.NORM_MINMAX)
# Compare original vs stretched
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.imshow(img_gray, cmap='gray'); plt.title("Original")
plt.subplot(1, 2, 2)
plt.imshow(img_stretched, cmap='gray'); plt.title("Contrast Stretched")
plt.show()
Mathematical Formula
I' = (I − Iₘᵢₙ) / (Iₘₐₓ − Iₘᵢₙ) × (newₘₐₓ − newₘᵢₙ) + newₘᵢₙ
# For 8-bit images (newₘᵢₙ = 0, newₘₐₓ = 255):
I' = (I − Iₘᵢₙ) / (Iₘₐₓ − Iₘᵢₙ) × 255
Analogy: Rubber Band Stretch
Imagine dots drawn on a rubber band, all clustered near the middle. Stretching the band spreads the dots across its entire length—just like stretching pixel values to cover [0–255].
Effect on Histogram
- Before: Histogram is narrow and centred → low contrast
- After: Histogram spans full [0–255] → high contrast
When & Why to Use It
- Enhancing underexposed or foggy images
- Preprocessing for tasks like edge detection or thresholding
- Making hidden details more visible
Limitations
- Global operation—does not preserve local contrast
- Sensitive to outliers—one extreme pixel can skew the stretch
Tip: For uneven lighting or to limit over-amplification, consider CLAHE (Contrast Limited Adaptive Histogram Equalization).
B. Histogram Equalization
Histogram Equalization is a nonlinear contrast‐enhancement technique that redistributes pixel intensities to produce a more uniform histogram, revealing detail in dark or washed‐out images.
⚙️ Code Example
# Equalize histogram
img_eq = cv2.equalizeHist(img_gray)
# Compare with original
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.imshow(img_gray, cmap='gray')
plt.title("Original")
plt.subplot(1, 2, 2)
plt.imshow(img_eq, cmap='gray')
plt.title("Histogram Equalized")
plt.show()
🎯 Problem It Solves
Low-contrast images often have pixel values cramped into a narrow band (e.g. 0–80 out of 255), making details hard to discern. Histogram Equalization redistributes these overrepresented intensities across the full [0–255] range.
🔍 How It Works
-
Histogram
h(i)
Count of pixels with intensityi
-
Normalized histogram
p(i) = h(i) / N
Probability of intensityi
over allN
pixels -
Cumulative Distribution Function (CDF)
Intensity remapping
where L = number of levels (256 for 8-bit images).
Darker, overpopulated intensities stretch apart; sparse intensities compress to fill gaps.
📈 Effect on the Histogram
Before: Bumpy and concentrated → low contrast
After: Flatter, more even distribution → high contrast
Note: Perfect flatness is impossible with discrete bins, but the result is much more balanced.
🖼️ Visual Example
Suppose an image has this simplified histogram:
Intensity | Count |
---|---|
0 | 3000 |
1 | 3000 |
2 | 1000 |
3–255 | 0 |
After equalization:
- Original 0 → New 0
- Original 1 → New 128
- Original 2 → New 255
Now details span the entire tonal range.
🛠️ When to Use It
- Low-contrast images with pixels stuck in a narrow range
- Night-vision or foggy scenes
- Medical, satellite or surveillance imagery
⚠️ Limitations
- May over-enhance noise in well-exposed areas
- Global method: ignores local contrast variations
Tip: For local contrast control, use CLAHE (Contrast Limited Adaptive Histogram Equalization).
📊 Visualizing Histograms Side-by-Side
plt.figure(figsize=(12, 4))
plt.hist(img_gray.ravel(), 256, [0, 256], alpha=0.5, label='Original')
plt.hist(img_eq.ravel(), 256, [0, 256], alpha=0.5, label='Equalized')
plt.title("Histogram Comparison")
plt.xlabel("Pixel Value")
plt.ylabel("Frequency")
plt.legend()
plt.grid(True)
plt.show()