Practical 4: Gamma Correction & Power Law Transformations
Run this practical in Google Colab
Objective
To understand and implement power law (gamma) transformations for image contrast enhancement.
2. Description / Theory
Theory: The power law transformation \(s = c \cdot r^\gamma\) is one of the most important intensity transformations.
- \(\gamma < 1\): Enhances dark regions (brightening)
- \(\gamma > 1\): Compresses bright regions (darkening)
- \(\gamma = 1, c = 1\): Identity (no change)
$$s = c \cdot r^\gamma$$
# ----------------------------------------------------------------------
# Install dependencies (uncomment for Google Colab)
# !pip install opencv-python-headless matplotlib numpy
import cv2
import numpy as np
import matplotlib.pyplot as plt
import os
# === AUTO-DOWNLOAD DATASET (works in Google Colab and locally) ===
import os, urllib.request, zipfile
# Choose which chapter to download (CH01-CH12)
CHAPTER = "CH02" # Chapter 2: Digital Image Fundamentals
DATASET_PATH = f"datasets/{CHAPTER}/"
DOWNLOAD_BASE = "https://www.imageprocessingplace.com/downloads_V3/dip3e_downloads/dip3e_book_images"
if not os.path.exists(DATASET_PATH) or not any(f.endswith('.tif') for f in os.listdir(DATASET_PATH)):
zip_name = f"DIP3E_{CHAPTER}_Original_Images.zip"
url = f"{DOWNLOAD_BASE}/{zip_name}"
print(f"Downloading {CHAPTER} dataset from imageprocessingplace.com...")
urllib.request.urlretrieve(url, "chapter.zip")
os.makedirs(DATASET_PATH, exist_ok=True)
with zipfile.ZipFile("chapter.zip", "r") as z:
for f in z.namelist():
if f.lower().endswith(".tif"):
fname = os.path.basename(f)
if fname:
with z.open(f) as src, open(os.path.join(DATASET_PATH, fname), "wb") as dst:
dst.write(src.read())
os.remove("chapter.zip")
print(f"Downloaded {len([f for f in os.listdir(DATASET_PATH) if f.endswith('.tif')])} images")
else:
print(f"Dataset ready: {len([f for f in os.listdir(DATASET_PATH) if f.endswith('.tif')])} images")
# List all available images
images = sorted([f for f in os.listdir(DATASET_PATH) if f.endswith('.tif')])
print(f"\nAvailable images ({len(images)}):")
for i, name in enumerate(images, 1):
print(f" {i}. {name}")
# === SELECT YOUR IMAGE HERE (dark image for best gamma demo) ===
selected_image = "Fig0241(a)(einstein low contrast).tif" # Change to any image
img = cv2.imread(os.path.join(DATASET_PATH, selected_image), cv2.IMREAD_GRAYSCALE)
# Normalize to [0, 1] range
img_normalized = img / 255.0
print(f"Image: {selected_image}")
print(f"Shape: {img.shape}")
print(f"Original range: [{img.min()}, {img.max()}]")
print(f"Normalized range: [{img_normalized.min():.4f}, {img_normalized.max():.4f}]")
plt.figure(figsize=(6, 6))
plt.imshow(img, cmap='gray')
plt.title(f"Original: {selected_image}")
plt.axis('off')
plt.show()
# Apply gamma correction with different gamma values
gamma_values = [0.3, 0.5, 1.0, 1.5, 2.5, 5.0]
c = 1 # constant
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
for ax, gamma in zip(axes.flatten(), gamma_values):
# Gamma correction: s = c * r^gamma
corrected = c * np.power(img_normalized, gamma)
# Convert back to uint8
corrected_uint8 = np.uint8(np.clip(corrected * 255, 0, 255))
ax.imshow(corrected_uint8, cmap='gray')
ax.set_title(f"γ = {gamma}", fontsize=13, fontweight='bold')
ax.axis('off')
plt.suptitle(f"Gamma Correction: s = c · r^γ (c={c})", fontsize=15, fontweight='bold')
plt.tight_layout()
plt.show()
# Plot transformation curves
r = np.linspace(0, 1, 256)
gamma_values_plot = [0.04, 0.1, 0.2, 0.4, 0.67, 1.0, 1.5, 2.5, 5.0, 10.0, 25.0]
plt.figure(figsize=(8, 8))
for gamma in gamma_values_plot:
s = np.power(r, gamma)
plt.plot(r, s, label=f"γ = {gamma}")
plt.plot([0, 1], [0, 1], 'k--', alpha=0.3, label="Identity (γ=1)")
plt.xlabel("Input Intensity (r)", fontsize=12)
plt.ylabel("Output Intensity (s)", fontsize=12)
plt.title("Power Law Transformation Curves: s = r^γ", fontsize=14, fontweight='bold')
plt.legend(loc='best', fontsize=9)
plt.grid(True, alpha=0.3)
plt.axis('equal')
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.tight_layout()
plt.show()
# Log transformation: s = c * log(1 + r)
c_log = 1.0
log_transformed = c_log * np.log1p(img_normalized) # log(1 + r)
# Normalize to [0, 1]
log_transformed = log_transformed / log_transformed.max()
log_uint8 = np.uint8(log_transformed * 255)
# Compare with gamma = 0.4
gamma_04 = np.power(img_normalized, 0.4)
gamma_04_uint8 = np.uint8(np.clip(gamma_04 * 255, 0, 255))
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(img, cmap='gray')
axes[0].set_title("Original")
axes[0].axis('off')
axes[1].imshow(log_uint8, cmap='gray')
axes[1].set_title("Log Transform: s = c·log(1+r)")
axes[1].axis('off')
axes[2].imshow(gamma_04_uint8, cmap='gray')
axes[2].set_title("Gamma: s = r^0.4")
axes[2].axis('off')
plt.suptitle("Log Transformation vs Gamma Correction", fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
# === DARK IMAGE (change to any image) ===
dark_image_file = "Fig0241(a)(einstein low contrast).tif"
dark_img = cv2.imread(os.path.join(DATASET_PATH, dark_image_file), cv2.IMREAD_GRAYSCALE)
dark_norm = dark_img / 255.0
# Gamma values for dark image enhancement
dark_gammas = [0.3, 0.4, 0.6]
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
axes[0].imshow(dark_img, cmap='gray')
axes[0].set_title("Original (Dark)")
axes[0].axis('off')
for ax, gamma in zip(axes[1:], dark_gammas):
enhanced = np.uint8(np.clip(np.power(dark_norm, gamma) * 255, 0, 255))
ax.imshow(enhanced, cmap='gray')
ax.set_title(f"γ = {gamma}")
ax.axis('off')
plt.suptitle(f"Dark Image Enhancement: {dark_image_file}", fontsize=13, fontweight='bold')
plt.tight_layout()
plt.show()
# === BRIGHT IMAGE (change to any image) ===
bright_image_file = "Fig0241(c)(einstein high contrast).tif"
bright_img = cv2.imread(os.path.join(DATASET_PATH, bright_image_file), cv2.IMREAD_GRAYSCALE)
bright_norm = bright_img / 255.0
# Gamma values for bright image compression
bright_gammas = [3.0, 4.0, 5.0]
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
axes[0].imshow(bright_img, cmap='gray')
axes[0].set_title("Original (Bright)")
axes[0].axis('off')
for ax, gamma in zip(axes[1:], bright_gammas):
compressed = np.uint8(np.clip(np.power(bright_norm, gamma) * 255, 0, 255))
ax.imshow(compressed, cmap='gray')
ax.set_title(f"γ = {gamma}")
ax.axis('off')
plt.suptitle(f"Bright Image Compression: {bright_image_file}", fontsize=13, fontweight='bold')
plt.tight_layout()
plt.show()
# Compare all three Einstein contrast levels with gamma correction
einstein_files = [
"Fig0241(a)(einstein low contrast).tif",
"Fig0241(b)(einstein med contrast).tif",
"Fig0241(c)(einstein high contrast).tif"
]
labels = ["Low Contrast", "Medium Contrast", "High Contrast"]
fig, axes = plt.subplots(3, 4, figsize=(16, 12))
for row, (efile, label) in enumerate(zip(einstein_files, labels)):
eimg = cv2.imread(os.path.join(DATASET_PATH, efile), cv2.IMREAD_GRAYSCALE)
enorm = eimg / 255.0
axes[row, 0].imshow(eimg, cmap='gray')
axes[row, 0].set_title(f"Original ({label})")
axes[row, 0].axis('off')
for col, gamma in enumerate([0.4, 1.0, 2.5]):
result = np.uint8(np.clip(np.power(enorm, gamma) * 255, 0, 255))
axes[row, col+1].imshow(result, cmap='gray')
axes[row, col+1].set_title(f"γ = {gamma}")
axes[row, col+1].axis('off')
plt.suptitle("Gamma Correction Across Contrast Levels", fontsize=15, fontweight='bold')
plt.tight_layout()
plt.show()
# Show histogram changes with gamma correction
gamma_demo = 0.4
corrected_demo = np.uint8(np.clip(np.power(img_normalized, gamma_demo) * 255, 0, 255))
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes[0, 0].imshow(img, cmap='gray')
axes[0, 0].set_title("Original")
axes[0, 0].axis('off')
axes[0, 1].imshow(corrected_demo, cmap='gray')
axes[0, 1].set_title(f"Gamma Corrected (γ={gamma_demo})")
axes[0, 1].axis('off')
axes[1, 0].hist(img.ravel(), bins=256, range=(0, 256), color='black', alpha=0.7)
axes[1, 0].set_title("Original Histogram")
axes[1, 0].set_xlabel("Intensity")
axes[1, 0].set_ylabel("Frequency")
axes[1, 1].hist(corrected_demo.ravel(), bins=256, range=(0, 256), color='black', alpha=0.7)
axes[1, 1].set_title(f"Gamma Corrected Histogram (γ={gamma_demo})")
axes[1, 1].set_xlabel("Intensity")
axes[1, 1].set_ylabel("Frequency")
plt.suptitle("Histogram Comparison", fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
Part 1: Apply Gamma Correction
Adjust the gamma slider and see the transformation result with histogram comparison.
Part 2: Gamma Series Comparison
Compare the effect of multiple gamma values (0.3, 0.5, 1.0, 1.5, 2.5, 5.0) on the selected image.
Transformation Curves
Visualize how different γ values map input intensities to output intensities.
Part 3: Log Transformation
Apply \(s = c \cdot \log(1 + r)\) and compare with gamma correction (γ = 0.4).
Part 4: Contrast Enhancement
Enhance dark images with γ < 1 or compress bright images with γ > 1.
Analysis Questions
- For a dark image, which gamma value produces the best visual enhancement? Why does γ < 1 brighten dark regions?
- Compare log transformation with γ = 0.4 on the same image. Which produces better results and why?
- How does gamma correction relate to display devices? If a monitor has γ = 2.5, what pre-correction gamma should be applied?