Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/image-processing/edge_detection.tex
51 views
unlisted
1
% Edge Detection in Digital Image Processing
2
% Topics: Gradient operators, Canny edge detection, Laplacian methods, performance metrics
3
% Style: Technical report with computational implementations
4
5
\documentclass[a4paper, 11pt]{article}
6
\usepackage[utf8]{inputenc}
7
\usepackage[T1]{fontenc}
8
\usepackage{amsmath, amssymb}
9
\usepackage{graphicx}
10
\usepackage{siunitx}
11
\usepackage{booktabs}
12
\usepackage{subcaption}
13
\usepackage[makestderr]{pythontex}
14
\usepackage{algorithm}
15
\usepackage{algpseudocode}
16
17
% Theorem environments
18
\newtheorem{definition}{Definition}[section]
19
\newtheorem{theorem}{Theorem}[section]
20
\newtheorem{example}{Example}[section]
21
\newtheorem{remark}{Remark}[section]
22
23
\title{Edge Detection: From Gradient Operators to Multi-Scale Analysis}
24
\author{Computer Vision Laboratory}
25
\date{\today}
26
27
\begin{document}
28
\maketitle
29
30
\begin{abstract}
31
This report presents a comprehensive analysis of edge detection algorithms in digital image
32
processing. We examine gradient-based methods (Sobel, Prewitt, Roberts), the Canny edge
33
detector with its multi-stage pipeline, and Laplacian-based approaches including zero-crossing
34
detection. Computational implementations demonstrate edge detection on synthetic test patterns
35
and natural images, with quantitative performance evaluation using precision, recall, and
36
F-measure metrics. We analyze the trade-offs between noise sensitivity, localization accuracy,
37
and computational efficiency across different detector families.
38
\end{abstract}
39
40
\section{Introduction}
41
42
Edge detection is a fundamental operation in computer vision, providing critical information
43
about object boundaries, surface discontinuities, and scene structure. An edge represents a
44
significant local change in image intensity, typically occurring at the boundary between
45
regions of different brightness or texture.
46
47
\begin{definition}[Edge]
48
An edge in a digital image is a set of connected pixels that form a boundary between two
49
regions with relatively distinct intensity properties. Mathematically, edges correspond to
50
local maxima in the intensity gradient magnitude:
51
\begin{equation}
52
|\nabla I| = \sqrt{\left(\frac{\partial I}{\partial x}\right)^2 + \left(\frac{\partial I}{\partial y}\right)^2}
53
\end{equation}
54
where $I(x,y)$ is the image intensity function.
55
\end{definition}
56
57
\section{Theoretical Framework}
58
59
\subsection{Gradient-Based Edge Detection}
60
61
The image gradient captures the rate and direction of intensity change at each pixel.
62
63
\begin{definition}[Image Gradient]
64
For a continuous image $I(x,y)$, the gradient is a vector:
65
\begin{equation}
66
\nabla I = \left[\frac{\partial I}{\partial x}, \frac{\partial I}{\partial y}\right]^T
67
\end{equation}
68
with magnitude $|\nabla I| = \sqrt{I_x^2 + I_y^2}$ and direction $\theta = \arctan(I_y/I_x)$.
69
\end{definition}
70
71
\begin{theorem}[Sobel Operator]
72
The Sobel operator approximates the gradient using 3$\times$3 convolution kernels:
73
\begin{equation}
74
G_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix}, \quad
75
G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix}
76
\end{equation}
77
These kernels combine smoothing (perpendicular to gradient direction) with differentiation
78
(parallel to gradient direction), providing noise robustness.
79
\end{theorem}
80
81
\begin{remark}[Alternative Gradient Operators]
82
Other first-order operators include:
83
\begin{itemize}
84
\item \textbf{Prewitt}: Equal-weight smoothing, kernels $\begin{bmatrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \end{bmatrix}$
85
\item \textbf{Roberts}: 2$\times$2 diagonal kernels $\begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix}$, $\begin{bmatrix} 0 & 1 \\ -1 & 0 \end{bmatrix}$
86
\item \textbf{Scharr}: Optimized 3$\times$3 kernels with coefficients $\pm 3, \pm 10$
87
\end{itemize}
88
\end{remark}
89
90
\subsection{Canny Edge Detector}
91
92
The Canny algorithm (1986) remains the gold standard for edge detection due to its optimal
93
performance according to three criteria: good detection, good localization, and single response.
94
95
\begin{theorem}[Canny Edge Detection Pipeline]
96
The Canny algorithm consists of four stages:
97
\begin{enumerate}
98
\item \textbf{Gaussian smoothing}: Convolve image with $G_\sigma(x,y) = \frac{1}{2\pi\sigma^2}e^{-(x^2+y^2)/2\sigma^2}$
99
\item \textbf{Gradient computation}: Calculate magnitude and orientation using Sobel or similar
100
\item \textbf{Non-maximum suppression}: Thin edges to single-pixel width by suppressing non-maxima perpendicular to edge direction
101
\item \textbf{Hysteresis thresholding}: Use dual thresholds $T_{low}$ and $T_{high}$ to link edge chains
102
\end{enumerate}
103
\end{theorem}
104
105
\subsection{Laplacian-Based Methods}
106
107
Second-order derivatives detect edges as zero-crossings rather than gradient maxima.
108
109
\begin{definition}[Laplacian of Gaussian (LoG)]
110
The LoG operator combines Gaussian smoothing with the Laplacian:
111
\begin{equation}
112
\nabla^2 G_\sigma = -\frac{1}{\pi\sigma^4}\left[1 - \frac{x^2+y^2}{2\sigma^2}\right]e^{-(x^2+y^2)/2\sigma^2}
113
\end{equation}
114
Edges occur where $\nabla^2(G_\sigma * I) = 0$ with sign change.
115
\end{definition}
116
117
\begin{remark}[Difference of Gaussians]
118
The LoG can be approximated by the Difference of Gaussians (DoG):
119
\begin{equation}
120
\text{DoG}(x,y) = G_{\sigma_1}(x,y) - G_{\sigma_2}(x,y), \quad \sigma_2 \approx 1.6\sigma_1
121
\end{equation}
122
This approximation is computationally efficient and forms the basis of SIFT features.
123
\end{remark}
124
125
\subsection{Performance Evaluation}
126
127
\begin{definition}[Edge Detection Metrics]
128
Given ground truth edges $E_{GT}$ and detected edges $E_{D}$:
129
\begin{itemize}
130
\item \textbf{Precision}: $P = \frac{TP}{TP + FP}$ (fraction of detected edges that are correct)
131
\item \textbf{Recall}: $R = \frac{TP}{TP + FN}$ (fraction of true edges detected)
132
\item \textbf{F-measure}: $F_1 = \frac{2PR}{P+R}$ (harmonic mean of precision and recall)
133
\item \textbf{Pratt's FOM}: $\text{FOM} = \frac{1}{\max(N_I, N_D)}\sum_{i=1}^{N_D}\frac{1}{1+\alpha d_i^2}$ where $d_i$ is distance to nearest true edge
134
\end{itemize}
135
\end{definition}
136
137
\section{Computational Analysis}
138
139
\begin{pycode}
140
import numpy as np
141
import matplotlib.pyplot as plt
142
from scipy import ndimage, signal
143
from scipy.ndimage import gaussian_filter, convolve
144
145
np.random.seed(42)
146
147
# Define gradient operators
148
sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
149
sobel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
150
151
prewitt_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]])
152
prewitt_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]])
153
154
roberts_x = np.array([[1, 0], [0, -1]])
155
roberts_y = np.array([[0, 1], [-1, 0]])
156
157
# Create synthetic test image: step edges at known locations
158
img_size = 200
159
test_image = np.zeros((img_size, img_size))
160
# Vertical edge
161
test_image[:, img_size//2:] = 1.0
162
# Horizontal edge in right half
163
test_image[img_size//2:, img_size//2:] = 0.5
164
# Add diagonal edge
165
for i in range(img_size//2):
166
test_image[i, i:i+2] = 0.7
167
168
# Add Gaussian noise
169
noise_level = 0.05
170
noisy_image = test_image + noise_level * np.random.randn(img_size, img_size)
171
noisy_image = np.clip(noisy_image, 0, 1)
172
173
# Function implementations
174
def apply_sobel(image):
175
gradient_x = convolve(image, sobel_x)
176
gradient_y = convolve(image, sobel_y)
177
gradient_magnitude = np.sqrt(gradient_x**2 + gradient_y**2)
178
gradient_direction = np.arctan2(gradient_y, gradient_x)
179
return gradient_magnitude, gradient_direction, gradient_x, gradient_y
180
181
def apply_prewitt(image):
182
gradient_x = convolve(image, prewitt_x)
183
gradient_y = convolve(image, prewitt_y)
184
gradient_magnitude = np.sqrt(gradient_x**2 + gradient_y**2)
185
return gradient_magnitude
186
187
def non_maximum_suppression(gradient_mag, gradient_dir):
188
suppressed = np.zeros_like(gradient_mag)
189
angle = gradient_dir * 180.0 / np.pi
190
angle[angle < 0] += 180
191
192
M, N = gradient_mag.shape
193
for i in range(1, M-1):
194
for j in range(1, N-1):
195
q = 255
196
r = 255
197
198
# Angle 0 (horizontal)
199
if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180):
200
q = gradient_mag[i, j+1]
201
r = gradient_mag[i, j-1]
202
# Angle 45
203
elif (22.5 <= angle[i,j] < 67.5):
204
q = gradient_mag[i+1, j-1]
205
r = gradient_mag[i-1, j+1]
206
# Angle 90 (vertical)
207
elif (67.5 <= angle[i,j] < 112.5):
208
q = gradient_mag[i+1, j]
209
r = gradient_mag[i-1, j]
210
# Angle 135
211
elif (112.5 <= angle[i,j] < 157.5):
212
q = gradient_mag[i-1, j-1]
213
r = gradient_mag[i+1, j+1]
214
215
if (gradient_mag[i,j] >= q) and (gradient_mag[i,j] >= r):
216
suppressed[i,j] = gradient_mag[i,j]
217
218
return suppressed
219
220
def hysteresis_thresholding(image, threshold_low, threshold_high):
221
strong_edges = image > threshold_high
222
weak_edges = (image >= threshold_low) & (image <= threshold_high)
223
224
edges = strong_edges.copy()
225
M, N = image.shape
226
227
# Link weak edges to strong edges
228
for i in range(1, M-1):
229
for j in range(1, N-1):
230
if weak_edges[i, j]:
231
# Check 8-connected neighbors
232
if np.any(strong_edges[i-1:i+2, j-1:j+2]):
233
edges[i, j] = True
234
235
return edges.astype(float)
236
237
def canny_edge_detector(image, sigma=1.0, threshold_low=0.05, threshold_high=0.15):
238
# Step 1: Gaussian smoothing
239
smoothed = gaussian_filter(image, sigma)
240
241
# Step 2: Gradient computation
242
gradient_mag, gradient_dir, _, _ = apply_sobel(smoothed)
243
244
# Step 3: Non-maximum suppression
245
suppressed = non_maximum_suppression(gradient_mag, gradient_dir)
246
247
# Step 4: Hysteresis thresholding
248
edges = hysteresis_thresholding(suppressed, threshold_low, threshold_high)
249
250
return edges, gradient_mag, suppressed
251
252
def laplacian_of_gaussian(image, sigma=2.0):
253
# Smooth with Gaussian
254
smoothed = gaussian_filter(image, sigma)
255
# Apply Laplacian
256
laplacian = ndimage.laplace(smoothed)
257
return laplacian
258
259
def zero_crossing_detector(laplacian, threshold=0.01):
260
zero_crossings = np.zeros_like(laplacian)
261
M, N = laplacian.shape
262
263
for i in range(1, M-1):
264
for j in range(1, N-1):
265
# Check for sign changes in 4-connected neighborhood
266
region = laplacian[i-1:i+2, j-1:j+2]
267
if (np.min(region) < -threshold) and (np.max(region) > threshold):
268
zero_crossings[i, j] = 1
269
270
return zero_crossings
271
272
# Apply different edge detectors
273
sobel_mag, sobel_dir, sobel_gx, sobel_gy = apply_sobel(noisy_image)
274
prewitt_mag = apply_prewitt(noisy_image)
275
canny_edges, canny_grad, canny_nms = canny_edge_detector(noisy_image, sigma=1.4,
276
threshold_low=0.05, threshold_high=0.15)
277
log_result = laplacian_of_gaussian(noisy_image, sigma=2.0)
278
log_edges = zero_crossing_detector(log_result, threshold=0.005)
279
280
# Threshold gradient magnitudes for comparison
281
sobel_edges = sobel_mag > 0.2
282
prewitt_edges = prewitt_mag > 0.2
283
284
# Count detected edges
285
num_sobel_edges = np.sum(sobel_edges)
286
num_prewitt_edges = np.sum(prewitt_edges)
287
num_canny_edges = np.sum(canny_edges)
288
num_log_edges = np.sum(log_edges)
289
290
# Calculate ground truth edges from clean image
291
true_edges_v = np.zeros_like(test_image)
292
true_edges_v[:, img_size//2-1:img_size//2+1] = 1
293
true_edges_h = np.zeros_like(test_image)
294
true_edges_h[img_size//2-1:img_size//2+1, img_size//2:] = 1
295
ground_truth = (true_edges_v + true_edges_h) > 0
296
297
# Compute performance metrics for Canny
298
def compute_metrics(detected, ground_truth, tolerance=2):
299
# Dilate ground truth for tolerance
300
from scipy.ndimage import binary_dilation
301
struct = np.ones((2*tolerance+1, 2*tolerance+1))
302
gt_dilated = binary_dilation(ground_truth, structure=struct)
303
304
true_positive = np.sum(detected & gt_dilated)
305
false_positive = np.sum(detected & ~gt_dilated)
306
false_negative = np.sum(~detected & ground_truth)
307
308
precision = true_positive / (true_positive + false_positive) if (true_positive + false_positive) > 0 else 0
309
recall = true_positive / (true_positive + false_negative) if (true_positive + false_negative) > 0 else 0
310
f_measure = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
311
312
return precision, recall, f_measure
313
314
canny_precision, canny_recall, canny_f1 = compute_metrics(canny_edges, ground_truth)
315
sobel_precision, sobel_recall, sobel_f1 = compute_metrics(sobel_edges, ground_truth)
316
317
# Create comprehensive visualization
318
fig = plt.figure(figsize=(16, 14))
319
320
# Plot 1: Original test image
321
ax1 = fig.add_subplot(4, 4, 1)
322
ax1.imshow(test_image, cmap='gray', interpolation='nearest')
323
ax1.set_title('Clean Test Image', fontsize=10)
324
ax1.axis('off')
325
326
# Plot 2: Noisy image
327
ax2 = fig.add_subplot(4, 4, 2)
328
ax2.imshow(noisy_image, cmap='gray', interpolation='nearest')
329
ax2.set_title(f'Noisy Image (SNR$\\approx${1/noise_level:.1f})', fontsize=10)
330
ax2.axis('off')
331
332
# Plot 3: Sobel gradient magnitude
333
ax3 = fig.add_subplot(4, 4, 3)
334
im3 = ax3.imshow(sobel_mag, cmap='hot', interpolation='nearest')
335
ax3.set_title('Sobel Gradient Magnitude', fontsize=10)
336
ax3.axis('off')
337
plt.colorbar(im3, ax=ax3, fraction=0.046)
338
339
# Plot 4: Gradient direction
340
ax4 = fig.add_subplot(4, 4, 4)
341
im4 = ax4.imshow(sobel_dir, cmap='hsv', interpolation='nearest')
342
ax4.set_title('Gradient Direction', fontsize=10)
343
ax4.axis('off')
344
plt.colorbar(im4, ax=ax4, fraction=0.046)
345
346
# Plot 5: Sobel X gradient
347
ax5 = fig.add_subplot(4, 4, 5)
348
im5 = ax5.imshow(sobel_gx, cmap='RdBu_r', interpolation='nearest')
349
ax5.set_title('Sobel $G_x$ (Vertical Edges)', fontsize=10)
350
ax5.axis('off')
351
plt.colorbar(im5, ax=ax5, fraction=0.046)
352
353
# Plot 6: Sobel Y gradient
354
ax6 = fig.add_subplot(4, 4, 6)
355
im6 = ax6.imshow(sobel_gy, cmap='RdBu_r', interpolation='nearest')
356
ax6.set_title('Sobel $G_y$ (Horizontal Edges)', fontsize=10)
357
ax6.axis('off')
358
plt.colorbar(im6, ax=ax6, fraction=0.046)
359
360
# Plot 7: Sobel thresholded
361
ax7 = fig.add_subplot(4, 4, 7)
362
ax7.imshow(sobel_edges, cmap='gray', interpolation='nearest')
363
ax7.set_title(f'Sobel Edges ({num_sobel_edges} pixels)', fontsize=10)
364
ax7.axis('off')
365
366
# Plot 8: Prewitt comparison
367
ax8 = fig.add_subplot(4, 4, 8)
368
ax8.imshow(prewitt_edges, cmap='gray', interpolation='nearest')
369
ax8.set_title(f'Prewitt Edges ({num_prewitt_edges} pixels)', fontsize=10)
370
ax8.axis('off')
371
372
# Plot 9: Canny - gradient after smoothing
373
ax9 = fig.add_subplot(4, 4, 9)
374
im9 = ax9.imshow(canny_grad, cmap='hot', interpolation='nearest')
375
ax9.set_title('Canny: Gradient (Smoothed)', fontsize=10)
376
ax9.axis('off')
377
plt.colorbar(im9, ax=ax9, fraction=0.046)
378
379
# Plot 10: Canny - after NMS
380
ax10 = fig.add_subplot(4, 4, 10)
381
im10 = ax10.imshow(canny_nms, cmap='hot', interpolation='nearest')
382
ax10.set_title('Canny: Non-Max Suppression', fontsize=10)
383
ax10.axis('off')
384
plt.colorbar(im10, ax=ax10, fraction=0.046)
385
386
# Plot 11: Canny final edges
387
ax11 = fig.add_subplot(4, 4, 11)
388
ax11.imshow(canny_edges, cmap='gray', interpolation='nearest')
389
ax11.set_title(f'Canny Edges ({num_canny_edges} pixels)', fontsize=10)
390
ax11.axis('off')
391
392
# Plot 12: Canny overlay
393
ax12 = fig.add_subplot(4, 4, 12)
394
overlay = np.stack([noisy_image, noisy_image, noisy_image], axis=-1)
395
overlay[canny_edges.astype(bool), :] = [1, 0, 0]
396
ax12.imshow(overlay, interpolation='nearest')
397
ax12.set_title('Canny Overlay on Image', fontsize=10)
398
ax12.axis('off')
399
400
# Plot 13: Laplacian of Gaussian
401
ax13 = fig.add_subplot(4, 4, 13)
402
im13 = ax13.imshow(log_result, cmap='RdBu_r', interpolation='nearest')
403
ax13.set_title('Laplacian of Gaussian', fontsize=10)
404
ax13.axis('off')
405
plt.colorbar(im13, ax=ax13, fraction=0.046)
406
407
# Plot 14: LoG zero crossings
408
ax14 = fig.add_subplot(4, 4, 14)
409
ax14.imshow(log_edges, cmap='gray', interpolation='nearest')
410
ax14.set_title(f'LoG Zero-Crossings ({num_log_edges} pixels)', fontsize=10)
411
ax14.axis('off')
412
413
# Plot 15: Performance comparison
414
ax15 = fig.add_subplot(4, 4, 15)
415
methods = ['Sobel', 'Canny']
416
precision_vals = [sobel_precision, canny_precision]
417
recall_vals = [sobel_recall, canny_recall]
418
f1_vals = [sobel_f1, canny_f1]
419
420
x_pos = np.arange(len(methods))
421
width = 0.25
422
423
ax15.bar(x_pos - width, precision_vals, width, label='Precision', color='steelblue', edgecolor='black')
424
ax15.bar(x_pos, recall_vals, width, label='Recall', color='coral', edgecolor='black')
425
ax15.bar(x_pos + width, f1_vals, width, label='F-measure', color='lightgreen', edgecolor='black')
426
427
ax15.set_ylabel('Score', fontsize=10)
428
ax15.set_title('Detection Performance', fontsize=10)
429
ax15.set_xticks(x_pos)
430
ax15.set_xticklabels(methods, fontsize=9)
431
ax15.legend(fontsize=8)
432
ax15.set_ylim([0, 1])
433
ax15.grid(axis='y', alpha=0.3)
434
435
# Plot 16: Edge count comparison
436
ax16 = fig.add_subplot(4, 4, 16)
437
edge_counts = [num_sobel_edges, num_prewitt_edges, num_canny_edges, num_log_edges]
438
count_labels = ['Sobel', 'Prewitt', 'Canny', 'LoG']
439
colors_bars = ['steelblue', 'coral', 'lightgreen', 'plum']
440
441
ax16.bar(count_labels, edge_counts, color=colors_bars, edgecolor='black')
442
ax16.set_ylabel('Number of Edge Pixels', fontsize=10)
443
ax16.set_title('Edge Detector Comparison', fontsize=10)
444
ax16.tick_params(axis='x', labelsize=9)
445
ax16.grid(axis='y', alpha=0.3)
446
447
plt.tight_layout()
448
plt.savefig('edge_detection_comprehensive.pdf', dpi=150, bbox_inches='tight')
449
plt.close()
450
\end{pycode}
451
452
\begin{figure}[htbp]
453
\centering
454
\includegraphics[width=\textwidth]{edge_detection_comprehensive.pdf}
455
\caption{Comprehensive edge detection analysis: (a) Clean synthetic test image with step edges;
456
(b) Image corrupted with Gaussian noise; (c-d) Sobel gradient magnitude and direction revealing
457
edge strength and orientation; (e-f) Directional gradients $G_x$ and $G_y$ highlighting vertical
458
and horizontal features; (g-h) Thresholded Sobel and Prewitt edges showing similar detection
459
patterns; (i-l) Canny detector pipeline stages from gradient computation through non-maximum
460
suppression to final hysteresis thresholding with overlay; (m-n) Laplacian of Gaussian response
461
and zero-crossing detection; (o-p) Quantitative performance comparison showing Canny's superior
462
precision-recall balance and total edge pixel counts across methods.}
463
\label{fig:edge_detection}
464
\end{figure}
465
466
\section{Algorithm Implementations}
467
468
\subsection{Canny Edge Detector}
469
470
\begin{algorithm}
471
\caption{Canny Edge Detection}
472
\begin{algorithmic}[1]
473
\Require Image $I$, Gaussian $\sigma$, thresholds $T_{low}$, $T_{high}$
474
\Ensure Binary edge map $E$
475
\State $I_{smooth} \gets G_\sigma * I$ \Comment{Gaussian smoothing}
476
\State $G_x \gets$ Sobel$_x * I_{smooth}$
477
\State $G_y \gets$ Sobel$_y * I_{smooth}$
478
\State $M \gets \sqrt{G_x^2 + G_y^2}$ \Comment{Gradient magnitude}
479
\State $\theta \gets \arctan(G_y / G_x)$ \Comment{Gradient direction}
480
\State $M_{nms} \gets$ NonMaxSuppress$(M, \theta)$ \Comment{Thin edges}
481
\State $E \gets$ HysteresisThreshold$(M_{nms}, T_{low}, T_{high})$ \Comment{Link edges}
482
\State \Return $E$
483
\end{algorithmic}
484
\end{algorithm}
485
486
\subsection{Non-Maximum Suppression}
487
488
The key to single-pixel-wide edges is suppressing gradient magnitudes that are not local maxima
489
in the direction perpendicular to the edge.
490
491
\begin{algorithm}
492
\caption{Non-Maximum Suppression}
493
\begin{algorithmic}[1]
494
\Require Gradient magnitude $M$, direction $\theta$
495
\Ensure Suppressed magnitude $M_{nms}$
496
\For{each pixel $(i,j)$}
497
\State Quantize $\theta_{i,j}$ to nearest 45$^\circ$ (0, 45, 90, 135)
498
\State Get neighboring pixels $p, q$ along gradient direction
499
\If{$M_{i,j} \geq M_p$ \textbf{and} $M_{i,j} \geq M_q$}
500
\State $M_{nms}[i,j] \gets M_{i,j}$ \Comment{Local maximum}
501
\Else
502
\State $M_{nms}[i,j] \gets 0$ \Comment{Suppressed}
503
\EndIf
504
\EndFor
505
\State \Return $M_{nms}$
506
\end{algorithmic}
507
\end{algorithm}
508
509
\section{Natural Image Analysis}
510
511
\begin{pycode}
512
# Create a more complex natural-like scene
513
np.random.seed(123)
514
scene_size = 300
515
516
# Create synthetic natural scene with multiple objects
517
scene = np.ones((scene_size, scene_size)) * 0.3
518
519
# Add geometric shapes with different intensities
520
# Circle
521
center_y, center_x = 100, 100
522
radius = 40
523
y, x = np.ogrid[:scene_size, :scene_size]
524
circle_mask = (x - center_x)**2 + (y - center_y)**2 <= radius**2
525
scene[circle_mask] = 0.8
526
527
# Rectangle
528
scene[150:220, 50:150] = 0.6
529
530
# Triangle (approximate)
531
for i in range(70):
532
scene[200-i, 180:180+2*i] = 0.45
533
534
# Add texture using random noise patterns
535
texture = np.random.randn(scene_size, scene_size) * 0.03
536
scene += texture
537
scene = np.clip(scene, 0, 1)
538
539
# Add realistic noise
540
scene_noisy = scene + 0.02 * np.random.randn(scene_size, scene_size)
541
scene_noisy = np.clip(scene_noisy, 0, 1)
542
543
# Apply multiple edge detectors with different parameters
544
canny_sigma1, _, _ = canny_edge_detector(scene_noisy, sigma=0.8, threshold_low=0.04, threshold_high=0.12)
545
canny_sigma2, _, _ = canny_edge_detector(scene_noisy, sigma=1.5, threshold_low=0.04, threshold_high=0.12)
546
canny_sigma3, _, _ = canny_edge_detector(scene_noisy, sigma=2.5, threshold_low=0.04, threshold_high=0.12)
547
548
sobel_scene, _, _, _ = apply_sobel(scene_noisy)
549
log_scene = laplacian_of_gaussian(scene_noisy, sigma=1.5)
550
log_scene_edges = zero_crossing_detector(log_scene, threshold=0.003)
551
552
# Multi-scale analysis
553
scales = [0.5, 1.0, 2.0, 3.0]
554
multiscale_edges = np.zeros_like(scene)
555
556
for sigma in scales:
557
edges, _, _ = canny_edge_detector(scene_noisy, sigma=sigma, threshold_low=0.04, threshold_high=0.12)
558
multiscale_edges += edges
559
560
multiscale_edges = multiscale_edges > 0
561
562
# Create visualization
563
fig2 = plt.figure(figsize=(16, 10))
564
565
# Original scene
566
ax1 = fig2.add_subplot(3, 4, 1)
567
ax1.imshow(scene, cmap='gray', interpolation='nearest')
568
ax1.set_title('Synthetic Scene (Clean)', fontsize=10)
569
ax1.axis('off')
570
571
# Noisy scene
572
ax2 = fig2.add_subplot(3, 4, 2)
573
ax2.imshow(scene_noisy, cmap='gray', interpolation='nearest')
574
ax2.set_title('Noisy Scene', fontsize=10)
575
ax2.axis('off')
576
577
# Sobel magnitude
578
ax3 = fig2.add_subplot(3, 4, 3)
579
im3 = ax3.imshow(sobel_scene, cmap='hot', interpolation='nearest')
580
ax3.set_title('Sobel Gradient Magnitude', fontsize=10)
581
ax3.axis('off')
582
plt.colorbar(im3, ax=ax3, fraction=0.046)
583
584
# Canny sigma=0.8
585
ax4 = fig2.add_subplot(3, 4, 4)
586
ax4.imshow(canny_sigma1, cmap='gray', interpolation='nearest')
587
ax4.set_title(f'Canny ($\\sigma$=0.8, {np.sum(canny_sigma1)} edges)', fontsize=10)
588
ax4.axis('off')
589
590
# Canny sigma=1.5
591
ax5 = fig2.add_subplot(3, 4, 5)
592
ax5.imshow(canny_sigma2, cmap='gray', interpolation='nearest')
593
ax5.set_title(f'Canny ($\\sigma$=1.5, {np.sum(canny_sigma2)} edges)', fontsize=10)
594
ax5.axis('off')
595
596
# Canny sigma=2.5
597
ax6 = fig2.add_subplot(3, 4, 6)
598
ax6.imshow(canny_sigma3, cmap='gray', interpolation='nearest')
599
ax6.set_title(f'Canny ($\\sigma$=2.5, {np.sum(canny_sigma3)} edges)', fontsize=10)
600
ax6.axis('off')
601
602
# LoG response
603
ax7 = fig2.add_subplot(3, 4, 7)
604
im7 = ax7.imshow(log_scene, cmap='RdBu_r', interpolation='nearest')
605
ax7.set_title('LoG Response ($\\sigma$=1.5)', fontsize=10)
606
ax7.axis('off')
607
plt.colorbar(im7, ax=ax7, fraction=0.046)
608
609
# LoG edges
610
ax8 = fig2.add_subplot(3, 4, 8)
611
ax8.imshow(log_scene_edges, cmap='gray', interpolation='nearest')
612
ax8.set_title(f'LoG Edges ({np.sum(log_scene_edges)} pixels)', fontsize=10)
613
ax8.axis('off')
614
615
# Multi-scale edges
616
ax9 = fig2.add_subplot(3, 4, 9)
617
ax9.imshow(multiscale_edges, cmap='gray', interpolation='nearest')
618
ax9.set_title('Multi-Scale Edges (Combined)', fontsize=10)
619
ax9.axis('off')
620
621
# Edge count vs sigma
622
ax10 = fig2.add_subplot(3, 4, 10)
623
sigma_range = np.linspace(0.5, 4.0, 15)
624
edge_counts_sigma = []
625
for sig in sigma_range:
626
edges_temp, _, _ = canny_edge_detector(scene_noisy, sigma=sig, threshold_low=0.04, threshold_high=0.12)
627
edge_counts_sigma.append(np.sum(edges_temp))
628
629
ax10.plot(sigma_range, edge_counts_sigma, 'o-', linewidth=2, markersize=6, color='steelblue')
630
ax10.set_xlabel('Gaussian $\\sigma$', fontsize=10)
631
ax10.set_ylabel('Edge Pixels Detected', fontsize=10)
632
ax10.set_title('Scale Parameter Effect', fontsize=10)
633
ax10.grid(True, alpha=0.3)
634
635
# Threshold sensitivity
636
ax11 = fig2.add_subplot(3, 4, 11)
637
threshold_range = np.linspace(0.02, 0.20, 15)
638
edge_counts_thresh = []
639
for thresh in threshold_range:
640
edges_temp, _, _ = canny_edge_detector(scene_noisy, sigma=1.5,
641
threshold_low=thresh, threshold_high=thresh*3)
642
edge_counts_thresh.append(np.sum(edges_temp))
643
644
ax11.plot(threshold_range, edge_counts_thresh, 's-', linewidth=2, markersize=6, color='coral')
645
ax11.set_xlabel('Threshold $T_{low}$', fontsize=10)
646
ax11.set_ylabel('Edge Pixels Detected', fontsize=10)
647
ax11.set_title('Threshold Sensitivity', fontsize=10)
648
ax11.grid(True, alpha=0.3)
649
650
# Histogram of gradient magnitudes
651
ax12 = fig2.add_subplot(3, 4, 12)
652
gradient_flat = sobel_scene.flatten()
653
ax12.hist(gradient_flat, bins=50, density=True, alpha=0.7, color='steelblue', edgecolor='black')
654
ax12.axvline(x=0.12, color='red', linestyle='--', linewidth=2, label='Typical Threshold')
655
ax12.set_xlabel('Gradient Magnitude', fontsize=10)
656
ax12.set_ylabel('Probability Density', fontsize=10)
657
ax12.set_title('Gradient Distribution', fontsize=10)
658
ax12.legend(fontsize=9)
659
ax12.set_xlim([0, 0.5])
660
661
plt.tight_layout()
662
plt.savefig('edge_detection_natural_scene.pdf', dpi=150, bbox_inches='tight')
663
plt.close()
664
665
# Store key statistics
666
num_canny_sigma1 = int(np.sum(canny_sigma1))
667
num_canny_sigma2 = int(np.sum(canny_sigma2))
668
num_canny_sigma3 = int(np.sum(canny_sigma3))
669
num_log_scene = int(np.sum(log_scene_edges))
670
num_multiscale = int(np.sum(multiscale_edges))
671
\end{pycode}
672
673
\begin{figure}[htbp]
674
\centering
675
\includegraphics[width=\textwidth]{edge_detection_natural_scene.pdf}
676
\caption{Edge detection on synthetic natural scene: (a-b) Clean and noisy synthetic scenes
677
containing geometric shapes with varying intensities; (c) Sobel gradient magnitude showing
678
continuous edge strength; (d-f) Canny edge detection at three scales ($\sigma$ = 0.8, 1.5, 2.5)
679
demonstrating the noise-detail trade-off, detecting \py{num_canny_sigma1}, \py{num_canny_sigma2},
680
and \py{num_canny_sigma3} edge pixels respectively; (g-h) Laplacian of Gaussian response and
681
zero-crossing detection finding \py{num_log_scene} edge pixels; (i) Multi-scale edge combination
682
yielding \py{num_multiscale} consolidated edge pixels; (j-k) Parameter sensitivity analysis showing
683
edge count dependence on Gaussian smoothing scale and threshold values; (l) Gradient magnitude
684
histogram revealing the distribution of edge strengths and typical threshold selection criterion.}
685
\label{fig:natural_scene}
686
\end{figure}
687
688
\section{Results and Performance Analysis}
689
690
\subsection{Computational Complexity}
691
692
\begin{pycode}
693
print(r"\begin{table}[htbp]")
694
print(r"\centering")
695
print(r"\caption{Edge Detection Algorithm Complexity}")
696
print(r"\begin{tabular}{lccc}")
697
print(r"\toprule")
698
print(r"Algorithm & Convolutions & Non-linear Ops & Complexity \\")
699
print(r"\midrule")
700
print(r"Sobel & 2 ($3\times3$) & Sqrt, Atan & $O(n^2)$ \\")
701
print(r"Prewitt & 2 ($3\times3$) & Sqrt, Atan & $O(n^2)$ \\")
702
print(r"Roberts & 2 ($2\times2$) & Sqrt, Atan & $O(n^2)$ \\")
703
print(r"Canny & 3 (Gaussian + 2 Sobel) & NMS + Hysteresis & $O(n^2)$ \\")
704
print(r"LoG & 1 (LoG kernel) & Zero-crossing & $O(n^2)$ \\")
705
print(r"\bottomrule")
706
print(r"\end{tabular}")
707
print(r"\label{tab:complexity}")
708
print(r"\end{table}")
709
\end{pycode}
710
711
\subsection{Detection Performance Summary}
712
713
\begin{pycode}
714
print(r"\begin{table}[htbp]")
715
print(r"\centering")
716
print(r"\caption{Edge Detector Performance on Test Image}")
717
print(r"\begin{tabular}{lcccc}")
718
print(r"\toprule")
719
print(r"Method & Edges Detected & Precision & Recall & F-measure \\")
720
print(r"\midrule")
721
print(f"Sobel (threshold=0.2) & {num_sobel_edges} & {sobel_precision:.3f} & {sobel_recall:.3f} & {sobel_f1:.3f} \\\\")
722
print(f"Prewitt (threshold=0.2) & {num_prewitt_edges} & --- & --- & --- \\\\")
723
print(f"Canny ($\\sigma$=1.4) & {num_canny_edges} & {canny_precision:.3f} & {canny_recall:.3f} & {canny_f1:.3f} \\\\")
724
print(f"LoG ($\\sigma$=2.0) & {num_log_edges} & --- & --- & --- \\\\")
725
print(r"\bottomrule")
726
print(r"\end{tabular}")
727
print(r"\label{tab:performance}")
728
print(r"\end{table}")
729
\end{pycode}
730
731
\subsection{Scale Parameter Analysis}
732
733
The choice of Gaussian smoothing scale $\sigma$ represents a fundamental trade-off in edge detection:
734
735
\begin{itemize}
736
\item \textbf{Small $\sigma$ (0.5-1.0)}: Preserves fine details but sensitive to noise, detects $\sim$\py{num_canny_sigma1} edges
737
\item \textbf{Medium $\sigma$ (1.5-2.5)}: Balanced noise suppression and detail retention, detects $\sim$\py{num_canny_sigma2} edges
738
\item \textbf{Large $\sigma$ (3.0-4.0)}: Strong noise rejection but loses fine structure, detects $\sim$\py{num_canny_sigma3} edges
739
\end{itemize}
740
741
\section{Discussion}
742
743
\begin{example}[Gradient Operator Selection]
744
For real-time applications on noisy images, the Sobel operator provides an excellent balance:
745
\begin{itemize}
746
\item \textbf{Efficiency}: Only two $3\times3$ convolutions required
747
\item \textbf{Robustness}: Built-in smoothing suppresses high-frequency noise
748
\item \textbf{Accuracy}: Weighted gradient approximation superior to simple differences
749
\end{itemize}
750
For highest quality, Canny's multi-stage pipeline achieves F-measure of \py{f"{canny_f1:.3f}"}
751
compared to Sobel's \py{f"{sobel_f1:.3f}"} on our test image.
752
\end{example}
753
754
\begin{remark}[Second vs First Derivatives]
755
Laplacian-based methods offer theoretical advantages:
756
\begin{itemize}
757
\item Isotropic response (rotation invariant)
758
\item Precise localization at zero-crossings
759
\item No explicit thresholding of gradient magnitude
760
\end{itemize}
761
However, second derivatives amplify noise more severely, requiring larger smoothing kernels
762
and potentially missing low-contrast edges.
763
\end{remark}
764
765
\begin{example}[Hysteresis Thresholding]
766
Canny's dual-threshold approach solves the edge linking problem:
767
\begin{itemize}
768
\item High threshold ($T_h$): Seeds strong edge segments (high confidence)
769
\item Low threshold ($T_l$): Extends edges along weak gradients (connected to strong edges)
770
\item Typical ratio: $T_h/T_l \approx 2-3$
771
\end{itemize}
772
This prevents edge fragmentation while rejecting isolated noise pixels.
773
\end{example}
774
775
\section{Conclusions}
776
777
This analysis demonstrates the implementation and evaluation of fundamental edge detection algorithms:
778
779
\begin{enumerate}
780
\item Gradient-based methods (Sobel, Prewitt) detect \py{num_sobel_edges} and \py{num_prewitt_edges}
781
edge pixels respectively on the noisy test image
782
\item The Canny detector's multi-stage pipeline achieves superior performance with precision
783
\py{f"{canny_precision:.3f}"}, recall \py{f"{canny_recall:.3f}"}, and F-measure \py{f"{canny_f1:.3f}"}
784
\item Laplacian of Gaussian methods provide rotation-invariant detection with \py{num_log_edges}
785
zero-crossings identified
786
\item Scale selection critically impacts detection: smaller $\sigma$ values preserve detail
787
(\py{num_canny_sigma1} edges at $\sigma$=0.8) while larger values enhance noise robustness
788
(\py{num_canny_sigma3} edges at $\sigma$=2.5)
789
\item Multi-scale approaches combining detections across $\sigma \in [0.5, 3.0]$ yield
790
\py{num_multiscale} consolidated edge pixels
791
\end{enumerate}
792
793
The choice of edge detector depends on application requirements: Sobel for computational efficiency,
794
Canny for accuracy and completeness, and LoG for precise localization in low-noise scenarios.
795
796
\section*{References}
797
798
\begin{enumerate}
799
\item Canny, J. (1986). A computational approach to edge detection. \textit{IEEE Trans. Pattern Analysis and Machine Intelligence}, 8(6), 679-698.
800
\item Sobel, I., \& Feldman, G. (1968). A 3x3 isotropic gradient operator for image processing. \textit{Stanford Artificial Intelligence Project}.
801
\item Marr, D., \& Hildreth, E. (1980). Theory of edge detection. \textit{Proc. Royal Society of London B}, 207(1167), 187-217.
802
\item Prewitt, J. M. S. (1970). Object enhancement and extraction. \textit{Picture Processing and Psychopictorics}, Academic Press.
803
\item Roberts, L. G. (1965). Machine perception of three-dimensional solids. \textit{Optical and Electro-Optical Information Processing}, MIT Press.
804
\item Torre, V., \& Poggio, T. A. (1986). On edge detection. \textit{IEEE Trans. Pattern Analysis and Machine Intelligence}, 8(2), 147-163.
805
\item Haralick, R. M. (1984). Digital step edges from zero crossing of second directional derivatives. \textit{IEEE Trans. Pattern Analysis and Machine Intelligence}, 6(1), 58-68.
806
\item Scharr, H. (2000). Optimal operators in digital image processing. \textit{PhD Thesis}, University of Heidelberg.
807
\item Elder, J. H., \& Zucker, S. W. (1998). Local scale control for edge detection and blur estimation. \textit{IEEE Trans. Pattern Analysis and Machine Intelligence}, 20(7), 699-716.
808
\item Lindeberg, T. (1998). Edge detection and ridge detection with automatic scale selection. \textit{Int. Journal of Computer Vision}, 30(2), 117-156.
809
\item Ziou, D., \& Tabbone, S. (1998). Edge detection techniques: An overview. \textit{International Journal of Pattern Recognition and Image Analysis}, 8(4), 537-559.
810
\item Heath, M., et al. (1997). A robust visual method for assessing the relative performance of edge-detection algorithms. \textit{IEEE Trans. Pattern Analysis and Machine Intelligence}, 19(12), 1338-1359.
811
\item Kittler, J. (1983). On the accuracy of the Sobel edge detector. \textit{Image and Vision Computing}, 1(1), 37-42.
812
\item Deriche, R. (1987). Using Canny's criteria to derive a recursively implemented optimal edge detector. \textit{Int. Journal of Computer Vision}, 1(2), 167-187.
813
\item Bovik, A. C., Huang, T. S., \& Munson, D. C. (1983). A generalization of median filtering using linear combinations of order statistics. \textit{IEEE Trans. Acoustics, Speech, and Signal Processing}, 31(6), 1342-1350.
814
\item Pratt, W. K. (2007). \textit{Digital Image Processing}, 4th ed. Wiley-Interscience.
815
\item Gonzalez, R. C., \& Woods, R. E. (2018). \textit{Digital Image Processing}, 4th ed. Pearson.
816
\item Jain, A. K. (1989). \textit{Fundamentals of Digital Image Processing}. Prentice Hall.
817
\end{enumerate}
818
819
\end{document}
820
821