By Andrey Golovin • • 12 min read
#image-processing#algorithms#python

Image Processing: From Theory to Practice

Introduction

Digital image processing is one of the most exciting fields in computer science and graphics. Whether you’re building photo editing software, creating artistic effects, or developing computer vision systems, understanding image processing is essential.

In this post, we’ll explore:

  • Fundamental concepts like pixels and color spaces
  • Common filters and their applications
  • Building your own image processor
  • Real-world performance considerations

Part 1: Understanding Pixels and Color Spaces

RGB and Beyond

Every digital image is composed of pixels arranged in a 2D grid. Each pixel contains color information, most commonly in RGB (Red, Green, Blue) format.

from PIL import Image
import numpy as np

# Load an image
img = Image.open('example.jpg')
pixels = np.array(img)

# pixels.shape gives us (height, width, 3) for RGB
print(f"Image dimensions: {pixels.shape}")

Working with Channels

Each color channel can be manipulated independently:

# Extract individual channels
red_channel = pixels[:, :, 0]
green_channel = pixels[:, :, 1]
blue_channel = pixels[:, :, 2]

# Create a grayscale image
grayscale = np.mean(pixels, axis=2)

Part 2: Filters and Convolutions

Kernel-Based Filtering

Convolution is the mathematical operation that powers most image filters. A kernel (small matrix) is slid across the image, computing weighted sums.

Common kernels:

# Blur kernel (3x3)
blur_kernel = np.array([
    [1, 1, 1],
    [1, 1, 1],
    [1, 1, 1]
]) / 9

# Sharpen kernel
sharpen_kernel = np.array([
    [0, -1, 0],
    [-1, 5, -1],
    [0, -1, 0]
])

# Sobel edge detection (X direction)
sobel_x = np.array([
    [-1, 0, 1],
    [-2, 0, 2],
    [-1, 0, 1]
])

Applying Convolution

from scipy import signal

def apply_filter(image, kernel):
    """Apply a convolution filter to an image."""
    filtered = signal.convolve2d(
        image,
        kernel,
        mode='same',
        boundary='symm'
    )
    return np.clip(filtered, 0, 255).astype(np.uint8)

# Apply blur
blurred = apply_filter(grayscale, blur_kernel)

# Apply edge detection
edges = apply_filter(grayscale, sobel_x)

Part 3: Advanced Techniques

Histogram Equalization

Improve contrast by redistributing pixel intensities:

def histogram_equalization(image):
    """Improve image contrast using histogram equalization."""
    hist, bins = np.histogram(image.flatten(), 256, [0, 256])
    cdf = hist.cumsum()
    cdf_normalized = cdf * hist.max() / cdf.max()
    
    # Map old values to new values
    cdf_m = np.ma.masked_equal(cdf, 0)
    cdf_m = (cdf_m - cdf_m.min()) * 255 / (cdf_m.max() - cdf_m.min())
    cdf = np.ma.filled(cdf_m, 0).astype('uint8')
    
    return cdf[image]

Bilateral Filtering

Preserve edges while smoothing:

from scipy.ndimage import gaussian_filter

def bilateral_filter(image, sigma_spatial=1.0, sigma_intensity=0.1):
    """Edge-preserving blur filter."""
    h, w = image.shape
    result = np.zeros_like(image, dtype=float)
    
    for i in range(h):
        for j in range(w):
            # This is simplified; production code would optimize this
            pixel_intensity = image[i, j]
            
            # Gaussian spatial weight
            x_dist = np.arange(max(0, i-5), min(h, i+6))
            y_dist = np.arange(max(0, j-5), min(w, j+6))
            
            # Apply weights...
            
    return np.clip(result, 0, 255).astype(np.uint8)

Part 4: Performance Optimization

Using NumPy Vectorization

# SLOW: Python loops
slow_result = np.zeros_like(image)
for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        slow_result[i, j] = image[i, j] * 2

# FAST: NumPy vectorization
fast_result = image * 2

GPU Acceleration with CuPy

For large images or real-time processing:

import cupy as cp

def gpu_blur(image):
    """Fast blur using GPU."""
    gpu_image = cp.asarray(image)
    kernel = cp.ones((3, 3)) / 9
    result = cp.convolve(gpu_image, kernel)
    return cp.asnumpy(result)

Practical Example: Creating an Artistic Filter

def artistic_filter(image, blur_amount=5, edge_threshold=50):
    """Create a stylized artistic effect."""
    # Step 1: Bilateral filter for smoothing
    smoothed = bilateral_filter(image, blur_amount)
    
    # Step 2: Detect edges
    edges = apply_filter(smoothed, sobel_x)
    
    # Step 3: Create mask where edges are strong
    edge_mask = edges > edge_threshold
    
    # Step 4: Posterize (reduce colors)
    posterized = (smoothed // 50) * 50
    
    # Step 5: Combine
    result = posterized.copy()
    result[edge_mask] = 0  # Black edges
    
    return result

Conclusion

Image processing is a vast field with applications from medical imaging to artistic creation. The techniques covered here form the foundation for more advanced work. Experiment with different kernels, combine filters, and discover your own effects!

Further Reading

  • OpenCV (cv2) - Industry standard library
  • scikit-image - Academic/scientific focus
  • GIMP Plugin Development - Apply these concepts to a real application

What image processing projects are you working on? Share in the comments!