Master essential image processing techniques including filtering operations, edge detection algorithms, morphological transformations, and color space conversions.
Image processing forms the foundation of computer vision, providing essential techniques for preparing, enhancing, and transforming images before feeding them to machine learning models. These operations help remove noise, highlight important features, and normalize visual data.
In this lesson, we'll explore four fundamental areas of image processing: filtering operations that modify pixel values based on their neighbors, edge detection algorithms that identify boundaries in images, morphological operations that process images based on shapes, and color space conversions that transform how color information is represented.
Mastering these techniques is crucial for building robust computer vision pipelines, as raw images often require significant preprocessing to achieve optimal results.
Image filtering uses convolution operations to modify pixel values based on their neighborhood. Filters (or kernels) slide across the image, computing weighted sums of pixel values to produce output images.
Convolution Fundamentals:
1import numpy as np
2import cv2
3
4def apply_convolution(image, kernel):
5 """Apply convolution manually to understand the operation."""
6 h, w = image.shape[:2]
7 kh, kw = kernel.shape
8 pad_h, pad_w = kh // 2, kw // 2
9
10 # Pad the image
11 if len(image.shape) == 3:
12 padded = np.pad(image, ((pad_h, pad_h), (pad_w, pad_w), (0, 0)), mode='reflect')
13 else:
14 padded = np.pad(image, ((pad_h, pad_h), (pad_w, pad_w)), mode='reflect')
15
16 output = np.zeros_like(image, dtype=np.float64)
17
18 for i in range(h):
19 for j in range(w):
20 region = padded[i:i+kh, j:j+kw]
21 if len(image.shape) == 3:
22 for c in range(image.shape[2]):
23 output[i, j, c] = np.sum(region[:, :, c] * kernel)
24 else:
25 output[i, j] = np.sum(region * kernel)
26
27 return np.clip(output, 0, 255).astype(np.uint8)
28
29# Common kernels
30identity_kernel = np.array([[0, 0, 0],
31 [0, 1, 0],
32 [0, 0, 0]])
33
34box_blur_kernel = np.ones((3, 3)) / 9
35
36sharpen_kernel = np.array([[0, -1, 0],
37 [-1, 5, -1],
38 [0, -1, 0]])Blur and Smoothing Filters:
1def apply_blur_filters(image):
2 """Demonstrate different blur operations."""
3
4 # Box blur (average filter)
5 box_blur = cv2.blur(image, (5, 5))
6
7 # Gaussian blur - weighted average, smoother results
8 gaussian_blur = cv2.GaussianBlur(image, (5, 5), sigmaX=1.5)
9
10 # Median blur - excellent for salt-and-pepper noise
11 median_blur = cv2.medianBlur(image, 5)
12
13 # Bilateral filter - edge-preserving smoothing
14 bilateral = cv2.bilateralFilter(image, d=9, sigmaColor=75, sigmaSpace=75)
15
16 return {
17 'box': box_blur,
18 'gaussian': gaussian_blur,
19 'median': median_blur,
20 'bilateral': bilateral
21 }
22
23# Add noise and test denoising
24def add_noise(image, noise_type='gaussian'):
25 """Add different types of noise to image."""
26 if noise_type == 'gaussian':
27 noise = np.random.normal(0, 25, image.shape).astype(np.float32)
28 noisy = np.clip(image.astype(np.float32) + noise, 0, 255).astype(np.uint8)
29 elif noise_type == 'salt_pepper':
30 noisy = image.copy()
31 # Salt
32 salt_mask = np.random.random(image.shape[:2]) < 0.02
33 noisy[salt_mask] = 255
34 # Pepper
35 pepper_mask = np.random.random(image.shape[:2]) < 0.02
36 noisy[pepper_mask] = 0
37 return noisySharpening Filters:
1def sharpen_image(image, method='kernel'):
2 """Apply different sharpening methods."""
3
4 if method == 'kernel':
5 # Standard sharpening kernel
6 kernel = np.array([[0, -1, 0],
7 [-1, 5, -1],
8 [0, -1, 0]])
9 sharpened = cv2.filter2D(image, -1, kernel)
10
11 elif method == 'unsharp_mask':
12 # Unsharp masking: original + (original - blurred)
13 blurred = cv2.GaussianBlur(image, (5, 5), 2)
14 sharpened = cv2.addWeighted(image, 1.5, blurred, -0.5, 0)
15
16 elif method == 'laplacian':
17 # Laplacian sharpening
18 laplacian = cv2.Laplacian(image, cv2.CV_64F)
19 sharpened = np.clip(image - 0.5 * laplacian, 0, 255).astype(np.uint8)
20
21 return sharpenedEdge detection identifies boundaries between regions of different intensities, crucial for object detection and image segmentation. Edges often correspond to important structural features in images.
Gradient-Based Edge Detection:
1import cv2
2import numpy as np
3
4def gradient_edge_detection(image):
5 """Apply gradient-based edge detection methods."""
6
7 # Convert to grayscale if needed
8 if len(image.shape) == 3:
9 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
10 else:
11 gray = image
12
13 # Sobel operators - detect horizontal and vertical edges
14 sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) # Vertical edges
15 sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) # Horizontal edges
16
17 # Magnitude of gradient
18 sobel_mag = np.sqrt(sobel_x**2 + sobel_y**2)
19 sobel_mag = np.clip(sobel_mag, 0, 255).astype(np.uint8)
20
21 # Direction of gradient
22 sobel_dir = np.arctan2(sobel_y, sobel_x)
23
24 # Scharr operator - more accurate than Sobel
25 scharr_x = cv2.Scharr(gray, cv2.CV_64F, 1, 0)
26 scharr_y = cv2.Scharr(gray, cv2.CV_64F, 0, 1)
27 scharr_mag = np.sqrt(scharr_x**2 + scharr_y**2)
28
29 # Laplacian - second derivative
30 laplacian = cv2.Laplacian(gray, cv2.CV_64F)
31 laplacian = np.clip(np.abs(laplacian), 0, 255).astype(np.uint8)
32
33 return {
34 'sobel_x': np.abs(sobel_x).astype(np.uint8),
35 'sobel_y': np.abs(sobel_y).astype(np.uint8),
36 'sobel_magnitude': sobel_mag,
37 'sobel_direction': sobel_dir,
38 'laplacian': laplacian
39 }Canny Edge Detection:
1def canny_edge_detection(image, low_threshold=50, high_threshold=150,
2 auto_threshold=False):
3 """Apply Canny edge detection with automatic threshold selection."""
4
5 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
6
7 # Apply Gaussian blur to reduce noise
8 blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
9
10 if auto_threshold:
11 # Automatic threshold using median
12 median_val = np.median(blurred)
13 sigma = 0.33
14 low_threshold = int(max(0, (1.0 - sigma) * median_val))
15 high_threshold = int(min(255, (1.0 + sigma) * median_val))
16
17 # Apply Canny
18 edges = cv2.Canny(blurred, low_threshold, high_threshold)
19
20 return edges, low_threshold, high_threshold
21
22# Canny step by step
23def canny_steps(image):
24 """Show individual steps of Canny edge detection."""
25 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
26
27 # Step 1: Gaussian blur
28 blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
29
30 # Step 2: Gradient calculation
31 sobel_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0)
32 sobel_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1)
33 magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
34 direction = np.arctan2(sobel_y, sobel_x)
35
36 # Step 3: Non-maximum suppression (simplified)
37 h, w = magnitude.shape
38 suppressed = np.zeros((h, w))
39
40 angle = direction * 180 / np.pi
41 angle[angle < 0] += 180
42
43 for i in range(1, h-1):
44 for j in range(1, w-1):
45 q, r = 255, 255
46
47 # Angle 0
48 if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180):
49 q = magnitude[i, j+1]
50 r = magnitude[i, j-1]
51 # Angle 45
52 elif 22.5 <= angle[i,j] < 67.5:
53 q = magnitude[i+1, j-1]
54 r = magnitude[i-1, j+1]
55 # Angle 90
56 elif 67.5 <= angle[i,j] < 112.5:
57 q = magnitude[i+1, j]
58 r = magnitude[i-1, j]
59 # Angle 135
60 elif 112.5 <= angle[i,j] < 157.5:
61 q = magnitude[i-1, j-1]
62 r = magnitude[i+1, j+1]
63
64 if magnitude[i,j] >= q and magnitude[i,j] >= r:
65 suppressed[i,j] = magnitude[i,j]
66
67 # Step 4: Hysteresis thresholding
68 high = suppressed > 100
69 low = suppressed > 50
70
71 return {
72 'blurred': blurred,
73 'magnitude': magnitude.astype(np.uint8),
74 'suppressed': suppressed.astype(np.uint8)
75 }Morphological operations process images based on shapes (structuring elements). They're essential for noise removal, shape analysis, and feature extraction.
Basic Morphological Operations:
1import cv2
2import numpy as np
3
4def basic_morphology(image):
5 """Apply basic morphological operations."""
6
7 # Create binary image
8 if len(image.shape) == 3:
9 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
10 else:
11 gray = image
12
13 _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
14
15 # Create structuring elements
16 kernel_rect = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
17 kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
18 kernel_cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
19
20 # Erosion - shrinks bright regions
21 eroded = cv2.erode(binary, kernel_rect, iterations=1)
22
23 # Dilation - expands bright regions
24 dilated = cv2.dilate(binary, kernel_rect, iterations=1)
25
26 # Opening - erosion followed by dilation (removes small bright spots)
27 opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel_rect)
28
29 # Closing - dilation followed by erosion (removes small dark spots)
30 closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel_rect)
31
32 return {
33 'original': binary,
34 'eroded': eroded,
35 'dilated': dilated,
36 'opened': opened,
37 'closed': closed
38 }
39
40def advanced_morphology(image):
41 """Apply advanced morphological operations."""
42
43 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
44 _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
45
46 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
47
48 # Gradient - difference between dilation and erosion
49 gradient = cv2.morphologyEx(binary, cv2.MORPH_GRADIENT, kernel)
50
51 # Top hat - difference between image and opening (bright spots)
52 tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, kernel)
53
54 # Black hat - difference between closing and image (dark spots)
55 blackhat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, kernel)
56
57 # Hit-or-miss - finds specific patterns
58 kernel1 = np.array([[0, 1, 0],
59 [1, 1, 1],
60 [0, 1, 0]], dtype=np.uint8)
61 kernel2 = np.array([[1, 0, 1],
62 [0, 0, 0],
63 [1, 0, 1]], dtype=np.uint8)
64
65 return {
66 'gradient': gradient,
67 'tophat': tophat,
68 'blackhat': blackhat
69 }Practical Applications:
1def denoise_with_morphology(image):
2 """Remove noise using morphological operations."""
3
4 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
5 _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
6
7 kernel_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
8 kernel_large = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
9
10 # Remove small noise
11 cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel_small)
12 cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel_small)
13
14 # Fill holes
15 filled = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel_large)
16
17 return filled
18
19def find_contours_after_morphology(image):
20 """Extract contours after morphological preprocessing."""
21
22 cleaned = denoise_with_morphology(image)
23
24 # Find contours
25 contours, hierarchy = cv2.findContours(
26 cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
27 )
28
29 # Filter by area
30 min_area = 100
31 filtered_contours = [c for c in contours if cv2.contourArea(c) > min_area]
32
33 return filtered_contours, cleanedDifferent color spaces represent color information in various ways, each suited for specific tasks. Understanding color spaces is essential for effective image processing.
Common Color Space Conversions:
1import cv2
2import numpy as np
3
4def convert_color_spaces(image):
5 """Convert image to different color spaces."""
6
7 # Original is assumed to be BGR (OpenCV default)
8
9 # RGB - standard color representation
10 rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
11
12 # Grayscale - intensity only
13 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
14
15 # HSV - Hue, Saturation, Value (good for color-based detection)
16 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
17
18 # LAB - Lightness, A (green-red), B (blue-yellow)
19 lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
20
21 # YCrCb - Luminance, Chrominance (used in JPEG compression)
22 ycrcb = cv2.cvtColor(image, cv2.COLOR_BGR2YCrCb)
23
24 # HLS - Hue, Lightness, Saturation
25 hls = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
26
27 return {
28 'rgb': rgb,
29 'gray': gray,
30 'hsv': hsv,
31 'lab': lab,
32 'ycrcb': ycrcb,
33 'hls': hls
34 }
35
36def analyze_color_channels(image):
37 """Analyze individual color channels."""
38
39 results = {}
40
41 # BGR channels
42 b, g, r = cv2.split(image)
43 results['blue'] = b
44 results['green'] = g
45 results['red'] = r
46
47 # HSV channels
48 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
49 h, s, v = cv2.split(hsv)
50 results['hue'] = h
51 results['saturation'] = s
52 results['value'] = v
53
54 # LAB channels
55 lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
56 l, a, b_lab = cv2.split(lab)
57 results['lightness'] = l
58 results['a_channel'] = a
59 results['b_channel'] = b_lab
60
61 return resultsColor-Based Segmentation:
1def color_segmentation_hsv(image, color_name='red'):
2 """Segment specific colors using HSV color space."""
3
4 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
5
6 # Color ranges in HSV
7 color_ranges = {
8 'red': [
9 (np.array([0, 100, 100]), np.array([10, 255, 255])),
10 (np.array([160, 100, 100]), np.array([180, 255, 255]))
11 ],
12 'green': [(np.array([40, 50, 50]), np.array([80, 255, 255]))],
13 'blue': [(np.array([100, 100, 100]), np.array([130, 255, 255]))],
14 'yellow': [(np.array([20, 100, 100]), np.array([40, 255, 255]))],
15 'orange': [(np.array([10, 100, 100]), np.array([25, 255, 255]))],
16 'white': [(np.array([0, 0, 200]), np.array([180, 30, 255]))],
17 'black': [(np.array([0, 0, 0]), np.array([180, 255, 30]))]
18 }
19
20 if color_name not in color_ranges:
21 raise ValueError(f"Unknown color: {color_name}")
22
23 # Create mask
24 mask = np.zeros(hsv.shape[:2], dtype=np.uint8)
25 for lower, upper in color_ranges[color_name]:
26 mask |= cv2.inRange(hsv, lower, upper)
27
28 # Apply mask
29 result = cv2.bitwise_and(image, image, mask=mask)
30
31 return mask, result
32
33def auto_adjust_brightness_contrast(image, clip_percent=1):
34 """Auto-adjust brightness and contrast using LAB."""
35
36 # Convert to LAB
37 lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
38 l, a, b = cv2.split(lab)
39
40 # Apply CLAHE to L channel
41 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
42 l_enhanced = clahe.apply(l)
43
44 # Merge and convert back
45 lab_enhanced = cv2.merge([l_enhanced, a, b])
46 result = cv2.cvtColor(lab_enhanced, cv2.COLOR_LAB2BGR)
47
48 return result
49
50def white_balance_correction(image, method='gray_world'):
51 """Apply white balance correction."""
52
53 if method == 'gray_world':
54 # Gray World assumption
55 result = image.copy().astype(np.float32)
56 avg_b = np.mean(result[:, :, 0])
57 avg_g = np.mean(result[:, :, 1])
58 avg_r = np.mean(result[:, :, 2])
59 avg_gray = (avg_b + avg_g + avg_r) / 3
60
61 result[:, :, 0] *= avg_gray / avg_b
62 result[:, :, 1] *= avg_gray / avg_g
63 result[:, :, 2] *= avg_gray / avg_r
64
65 result = np.clip(result, 0, 255).astype(np.uint8)
66
67 elif method == 'max_white':
68 # Max White assumption
69 result = image.copy().astype(np.float32)
70 max_b = np.max(result[:, :, 0])
71 max_g = np.max(result[:, :, 1])
72 max_r = np.max(result[:, :, 2])
73
74 result[:, :, 0] *= 255 / max_b
75 result[:, :, 1] *= 255 / max_g
76 result[:, :, 2] *= 255 / max_r
77
78 result = np.clip(result, 0, 255).astype(np.uint8)
79
80 return resultBuild a comprehensive image enhancement pipeline that combines denoising, contrast enhancement, sharpening, and color correction.
1"""
2Complete Image Enhancement Pipeline
3Combines multiple image processing techniques for optimal results.
4"""
5
6import numpy as np
7import cv2
8
9class ImageEnhancementPipeline:
10 """A complete pipeline for image enhancement."""
11
12 def __init__(self):
13 self.steps = []
14
15 def denoise(self, image, method='bilateral', strength=10):
16 """Remove noise while preserving edges."""
17 if method == 'bilateral':
18 return cv2.bilateralFilter(image, 9, strength*5, strength*5)
19 elif method == 'nlmeans':
20 return cv2.fastNlMeansDenoisingColored(image, None, strength, strength, 7, 21)
21 elif method == 'gaussian':
22 return cv2.GaussianBlur(image, (5, 5), strength/10)
23 return image
24
25 def enhance_contrast(self, image, method='clahe', clip_limit=2.0):
26 """Enhance image contrast."""
27 if method == 'clahe':
28 # Apply CLAHE to LAB L-channel
29 lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
30 l, a, b = cv2.split(lab)
31 clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(8, 8))
32 l_enhanced = clahe.apply(l)
33 lab_enhanced = cv2.merge([l_enhanced, a, b])
34 return cv2.cvtColor(lab_enhanced, cv2.COLOR_LAB2BGR)
35
36 elif method == 'histogram':
37 # Histogram equalization per channel
38 channels = cv2.split(image)
39 eq_channels = [cv2.equalizeHist(ch) for ch in channels]
40 return cv2.merge(eq_channels)
41
42 elif method == 'stretch':
43 # Linear contrast stretch
44 result = image.copy().astype(np.float32)
45 for i in range(3):
46 ch = result[:, :, i]
47 min_val, max_val = ch.min(), ch.max()
48 result[:, :, i] = (ch - min_val) * 255 / (max_val - min_val + 1e-6)
49 return np.clip(result, 0, 255).astype(np.uint8)
50
51 return image
52
53 def sharpen(self, image, amount=1.0):
54 """Sharpen image using unsharp masking."""
55 blurred = cv2.GaussianBlur(image, (0, 0), 3)
56 sharpened = cv2.addWeighted(image, 1.0 + amount, blurred, -amount, 0)
57 return sharpened
58
59 def correct_color(self, image, method='gray_world'):
60 """Apply white balance correction."""
61 result = image.copy().astype(np.float32)
62
63 if method == 'gray_world':
64 avg_b = np.mean(result[:, :, 0])
65 avg_g = np.mean(result[:, :, 1])
66 avg_r = np.mean(result[:, :, 2])
67 avg_gray = (avg_b + avg_g + avg_r) / 3
68
69 result[:, :, 0] *= avg_gray / (avg_b + 1e-6)
70 result[:, :, 1] *= avg_gray / (avg_g + 1e-6)
71 result[:, :, 2] *= avg_gray / (avg_r + 1e-6)
72
73 return np.clip(result, 0, 255).astype(np.uint8)
74
75 def adjust_saturation(self, image, factor=1.2):
76 """Adjust color saturation."""
77 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV).astype(np.float32)
78 hsv[:, :, 1] = np.clip(hsv[:, :, 1] * factor, 0, 255)
79 return cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
80
81 def process(self, image, config=None):
82 """Run the complete enhancement pipeline."""
83 if config is None:
84 config = {
85 'denoise': {'enabled': True, 'method': 'bilateral', 'strength': 10},
86 'contrast': {'enabled': True, 'method': 'clahe', 'clip_limit': 2.0},
87 'sharpen': {'enabled': True, 'amount': 0.5},
88 'color': {'enabled': True, 'method': 'gray_world'},
89 'saturation': {'enabled': True, 'factor': 1.1}
90 }
91
92 result = image.copy()
93 self.steps = [('original', image.copy())]
94
95 if config['denoise']['enabled']:
96 result = self.denoise(result, config['denoise']['method'],
97 config['denoise']['strength'])
98 self.steps.append(('denoised', result.copy()))
99
100 if config['contrast']['enabled']:
101 result = self.enhance_contrast(result, config['contrast']['method'],
102 config['contrast']['clip_limit'])
103 self.steps.append(('contrast_enhanced', result.copy()))
104
105 if config['color']['enabled']:
106 result = self.correct_color(result, config['color']['method'])
107 self.steps.append(('color_corrected', result.copy()))
108
109 if config['saturation']['enabled']:
110 result = self.adjust_saturation(result, config['saturation']['factor'])
111 self.steps.append(('saturation_adjusted', result.copy()))
112
113 if config['sharpen']['enabled']:
114 result = self.sharpen(result, config['sharpen']['amount'])
115 self.steps.append(('sharpened', result.copy()))
116
117 return result
118
119 def get_comparison(self, original, enhanced):
120 """Create side-by-side comparison."""
121 h, w = original.shape[:2]
122 comparison = np.zeros((h, w*2 + 10, 3), dtype=np.uint8)
123 comparison[:, :w] = original
124 comparison[:, w+10:] = enhanced
125 comparison[:, w:w+10] = 128 # Gray separator
126 return comparison
127
128
129# Example usage
130pipeline = ImageEnhancementPipeline()
131
132# Create test image with various issues
133test_image = np.random.randint(50, 200, (300, 400, 3), dtype=np.uint8)
134
135# Add some color cast
136test_image[:, :, 2] = np.clip(test_image[:, :, 2] + 30, 0, 255)
137
138# Reduce contrast
139test_image = (test_image * 0.6 + 50).astype(np.uint8)
140
141# Process image
142enhanced = pipeline.process(test_image)
143
144print("Pipeline steps completed:")
145for step_name, _ in pipeline.steps:
146 print(f" - {step_name}")
147
148print(f"\nOriginal stats: mean={test_image.mean():.1f}, std={test_image.std():.1f}")
149print(f"Enhanced stats: mean={enhanced.mean():.1f}, std={enhanced.std():.1f}")This enhancement pipeline demonstrates how to combine multiple image processing techniques into a cohesive workflow. The pipeline includes denoising with edge preservation, contrast enhancement using CLAHE, white balance correction, saturation adjustment, and sharpening. Each step builds on the previous one to produce an optimized final result.
In the next lesson, we'll explore feature detection and extraction techniques including SIFT, SURF, ORB, and HOG descriptors for matching and recognizing patterns across images.
Your progress is saved in this browser, on this device only.