Learning/IVP/Session 06
Session 06✨

Image Enhancement & Restoration

Sharpening degraded images and restoring blurry/noisy images with Butterworth and Wiener filters.


Learning Objectives

  • āœ“Sharpen images using high-pass filters
  • āœ“Understand image degradation (blur + noise)
  • āœ“Apply Butterworth low-pass filter in frequency domain
  • āœ“Restore images using Wiener filter

Image Sharpening

Sharpening enhances edges by subtracting a smoothed version from the original (unsharp masking) or using a high-pass kernel. A Laplacian kernel highlights rapid intensity changes.

Ex6_1 — Sharpening filtersPython
import cv2
import numpy as np

img = cv2.imread('input.jpg', cv2.IMREAD_GRAYSCALE)

# Laplacian sharpening kernel
kernel_1 = np.array([[0,-1,0],[-1,5,-1],[0,-1,0]], dtype=np.float64)

# Stronger sharpening
kernel_2 = np.array([[-1,-1,-1],[-1,9,-1],[-1,-1,-1]], dtype=np.float64)

sharp_1 = cv2.filter2D(img, -1, kernel_1)
sharp_2 = cv2.filter2D(img, -1, kernel_2)

Image Degradation Model

A degraded image is modelled as g=hāˆ—f+Ī·g = h * f + \eta, where ff is the original, hh is the blur kernel (PSF), āˆ—* is convolution, and Ī·\eta is additive noise. In the Fourier domain convolution becomes multiplication — restoration tries to recover FF from GG.

g=hāˆ—f+Ī·G(u,v)=H(u,v)ā‹…F(u,v)+N(u,v)\begin{aligned} g &= h * f + \eta \\[4pt] G(u,v) &= H(u,v) \cdot F(u,v) + N(u,v) \end{aligned}
Spatial domain (top) and frequency domain (bottom) — convolution becomes multiplication
Ex6_2 — Simulate blur and noisePython
from scipy.signal import convolve2d
import numpy as np

img_f = img.astype(np.float64) / 255.0

# Create motion blur kernel (LENGTH=21, ANGLE=10)
LENGTH, ANGLE = 21, 10
kernel = np.zeros((LENGTH, LENGTH))
kernel[LENGTH // 2, :] = 1
kernel = kernel / LENGTH

# Stage 1: blur
blurry = convolve2d(img_f, kernel, mode='same')

# Stage 2: add noise
noise = np.random.normal(0, 0.01, img_f.shape)
noisy_blurry = blurry + noise

mse = np.mean((img_f - noisy_blurry) ** 2)
print(f'MSE: {mse:.6f}')

Butterworth Filter

The Butterworth low-pass filter attenuates high frequencies (where noise concentrates) smoothly, without a sharp cutoff — f0f_0 is the cutoff frequency and nn is the filter order.

H(f)=11+(ff0)2nH(f) = \frac{1}{\sqrt{1 + \left(\dfrac{f}{f_0}\right)^{2n}}}
The larger nn, the steeper the response (closer to an ideal filter); a small nn gives a smooth transition
Ex6_2 — Butterworth low-pass filterPython
from scipy.signal import butter, lfilter

def butterworth_1d(cutoff, fs, order=5):
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = butter(order, normal_cutoff, btype='low', analog=False)
    return b, a

b, a = butterworth_1d(cutoff=0.1, fs=1.0, order=5)
# Apply row by row then column by column (separable 2-D)
filtered = lfilter(b, a, lfilter(b, a, noisy_blurry, axis=0), axis=1)

Wiener Filter

The Wiener filter is the optimal linear filter for restoration in the presence of additive noise — it minimizes the mean squared error. Hāˆ—H^{*} is the complex conjugate of HH, and KK is the noise-to-signal power ratio.

F^(u,v)=[Hāˆ—(u,v)∣H(u,v)∣2+K]ā‹…G(u,v)\hat{F}(u,v) = \left[ \frac{H^{*}(u,v)}{|H(u,v)|^{2} + K} \right] \cdot G(u,v)
K→0K \to 0 (no noise) reduces to the inverse filter F^=G/H\hat{F} = G/H
Ex6_2 — Wiener filter restorationPython
from scipy.signal import wiener

# Apply Wiener filter (mysize=kernel window, noise=estimated noise power)
restored = wiener(noisy_blurry, mysize=(21, 21), noise=0.01)
restored = np.clip(restored, 0, 1)

mse_wiener = np.mean((img_f - restored) ** 2)
print(f'Wiener MSE: {mse_wiener:.6f}')