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

image

🧳 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 intensity i

  • Normalized histogram p(i) = h(i) / N
    Probability of intensity i over all N pixels

  • Cumulative Distribution Function (CDF)
    image

Intensity remapping

image

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()

image