Edge Detection
Edge Detection¶
Overview¶
An edge is a region in an image where brightness changes rapidly, representing object boundaries or structures. This lesson covers the concept of image gradients and various edge detection techniques including Sobel, Scharr, Laplacian, and Canny.
Table of Contents¶
- Image Gradient Concept
- Sobel Operator
- Scharr Operator
- Laplacian Operator
- Canny Edge Detection
- Gradient Magnitude and Direction
- Exercises
1. Image Gradient Concept¶
What is Gradient?¶
Gradient: Rate of change in image brightness
Mathematical Definition:
βf = (βf/βx, βf/βy)
- βf/βx: Rate of change in x direction (horizontal)
- βf/βy: Rate of change in y direction (vertical)
Gradient Magnitude:
|βf| = β((βf/βx)Β² + (βf/βy)Β²)
Gradient Direction:
ΞΈ = arctan(βf/βy / βf/βx)
Types of Edges¶
1. Step Edge
Brightness βββ
β
βββ Brightness
β Ideal edge, abrupt change
2. Ramp Edge
Brightness βββ²
β²
β²ββ Brightness
β Gradual change, blurred boundary
3. Roof Edge
Brightness βββ±β²
β± β²
β± β²ββ Brightness
β Line structure
4. Ridge Edge
β±β²
β± β²
βββ± β²ββ
β Thin line structure
Edge Detection Pipeline¶
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Input β β Noise β β Gradient β β Edge β
β Image β βββΆ β Removal β βββΆ β Calculation β βββΆ β Extraction β
β β β (Gaussian) β β (Sobel etc) β β (Threshold) β
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
2. Sobel Operator¶
Concept¶
Sobel Operator: First derivative-based edge detection
β Calculate gradients in x and y directions separately
3x3 Sobel Kernels:
Gx (Horizontal edge detection): Gy (Vertical edge detection):
ββββββ¬βββββ¬βββββ ββββββ¬βββββ¬βββββ
β -1 β 0 β +1 β β -1 β -2 β -1 β
ββββββΌβββββΌβββββ€ ββββββΌβββββΌβββββ€
β -2 β 0 β +2 β β 0 β 0 β 0 β
ββββββΌβββββΌβββββ€ ββββββΌβββββΌβββββ€
β -1 β 0 β +1 β β +1 β +2 β +1 β
ββββββ΄βββββ΄βββββ ββββββ΄βββββ΄βββββ
β Gx: Detect vertical edges (left-right brightness difference)
β Gy: Detect horizontal edges (top-bottom brightness difference)
cv2.Sobel() Function¶
cv2.Sobel(src, ddepth, dx, dy, ksize=3, scale=1, delta=0)
| Parameter | Description |
|---|---|
| src | Input image |
| ddepth | Output image depth (cv2.CV_64F recommended) |
| dx | Derivative order in x direction (0 or 1) |
| dy | Derivative order in y direction (0 or 1) |
| ksize | Kernel size (1, 3, 5, 7) |
| scale | Scale factor |
| delta | Value added to result |
Basic Usage¶
import cv2
import numpy as np
# Read image
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# Sobel operation
# Set ddepth to CV_64F to handle negative values
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # x direction
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # y direction
# Convert to absolute value and then to 8-bit
sobel_x = cv2.convertScaleAbs(sobel_x)
sobel_y = cv2.convertScaleAbs(sobel_y)
# Combine x, y gradients
sobel_combined = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
# Display results
cv2.imshow('Original', img)
cv2.imshow('Sobel X', sobel_x)
cv2.imshow('Sobel Y', sobel_y)
cv2.imshow('Sobel Combined', sobel_combined)
cv2.waitKey(0)
Calculate Gradient Magnitude¶
import cv2
import numpy as np
def sobel_magnitude(image):
"""Calculate Sobel gradient magnitude"""
# Convert to grayscale
if len(image.shape) == 3:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image
# Remove noise
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
# Sobel operation (calculate in float64)
sobel_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
# Gradient magnitude: sqrt(GxΒ² + GyΒ²)
magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
# Normalize to 0-255 range
magnitude = np.clip(magnitude, 0, 255).astype(np.uint8)
return magnitude
# Usage example
img = cv2.imread('image.jpg')
edges = sobel_magnitude(img)
cv2.imshow('Sobel Magnitude', edges)
cv2.waitKey(0)
Differences by Kernel Size¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
def compare_sobel_ksize(image_path):
"""Compare Sobel kernel sizes"""
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
ksizes = [1, 3, 5, 7]
for ax, ksize in zip(axes.flatten(), ksizes):
# When ksize=1, use 3x1 or 1x3 filter
if ksize == 1:
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=1)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=1)
else:
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=ksize)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=ksize)
magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
magnitude = np.clip(magnitude, 0, 255).astype(np.uint8)
ax.imshow(magnitude, cmap='gray')
ax.set_title(f'Sobel ksize={ksize}')
ax.axis('off')
plt.tight_layout()
plt.show()
# ksize comparison:
# - ksize=1: Most sensitive, vulnerable to noise
# - ksize=3: Standard, balanced results
# - ksize=5, 7: Smoother edges, more robust to noise
3. Scharr Operator¶
Concept¶
Scharr Operator: More accurate 3x3 kernel than Sobel
β Better rotational symmetry
Scharr Kernels:
Gx: Gy:
ββββββ¬βββββ¬βββββ ββββββ¬βββββ¬βββββ
β -3 β 0 β +3 β β -3 β-10 β -3 β
ββββββΌβββββΌβββββ€ ββββββΌβββββΌβββββ€
β-10 β 0 β+10 β β 0 β 0 β 0 β
ββββββΌβββββΌβββββ€ ββββββΌβββββΌβββββ€
β -3 β 0 β +3 β β +3 β+10 β +3 β
ββββββ΄βββββ΄βββββ ββββββ΄βββββ΄βββββ
Sobel vs Scharr:
- Sobel: [-1, 0, 1] Γ [-1, -2, -1]α΅
- Scharr: [-3, 0, 3] Γ [-3, -10, -3]α΅
β Scharr is more accurate in diagonal directions
cv2.Scharr() Function¶
cv2.Scharr(src, ddepth, dx, dy, scale=1, delta=0)
import cv2
import numpy as np
def compare_sobel_scharr(image):
"""Compare Sobel and Scharr"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Sobel (ksize=3)
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sobel_mag = np.sqrt(sobel_x**2 + sobel_y**2)
# Scharr (fixed 3x3)
scharr_x = cv2.Scharr(gray, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(gray, cv2.CV_64F, 0, 1)
scharr_mag = np.sqrt(scharr_x**2 + scharr_y**2)
# Normalize
sobel_mag = np.clip(sobel_mag, 0, 255).astype(np.uint8)
scharr_mag = np.clip(scharr_mag, 0, 255).astype(np.uint8)
return sobel_mag, scharr_mag
# Scharr usage example
img = cv2.imread('image.jpg')
sobel, scharr = compare_sobel_scharr(img)
cv2.imshow('Sobel', sobel)
cv2.imshow('Scharr', scharr)
cv2.waitKey(0)
Using Scharr with Sobel¶
# Use ksize=-1 or ksize=cv2.FILTER_SCHARR in cv2.Sobel()
scharr_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=-1) # Use Scharr kernel
scharr_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=-1)
# Above code is equivalent to
scharr_x = cv2.Scharr(gray, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(gray, cv2.CV_64F, 0, 1)
4. Laplacian Operator¶
Concept¶
Laplacian Operator: Second derivative-based edge detection
β Zero-crossing at points where brightness changes rapidly
Mathematical Definition:
βΒ²f = βΒ²f/βxΒ² + βΒ²f/βyΒ²
Laplacian Kernels:
4-connectivity: 8-connectivity:
ββββββ¬βββββ¬βββββ ββββββ¬βββββ¬βββββ
β 0 β 1 β 0 β β 1 β 1 β 1 β
ββββββΌβββββΌβββββ€ ββββββΌβββββΌβββββ€
β 1 β -4 β 1 β β 1 β -8 β 1 β
ββββββΌβββββΌβββββ€ ββββββΌβββββΌβββββ€
β 0 β 1 β 0 β β 1 β 1 β 1 β
ββββββ΄βββββ΄βββββ ββββββ΄βββββ΄βββββ
Characteristics:
- Detects edges regardless of direction
- Very sensitive to noise (second derivative)
- Zero-crossing points are edges
First Derivative vs Second Derivative¶
Original Signal (Step Edge):
βββββββββββββ
β
βββββββββββββ
First Derivative (Sobel):
β±β²
β± β²
ββββββββββ± β²βββββββββ
β Peak point is edge
Second Derivative (Laplacian):
β±β²
β± β²
ββββ± β²βββ
β± β²
β± β²
β Zero-crossing point is edge
cv2.Laplacian() Function¶
cv2.Laplacian(src, ddepth, ksize=1, scale=1, delta=0)
| Parameter | Description |
|---|---|
| src | Input image |
| ddepth | Output image depth |
| ksize | Kernel size (1, 3, 5, 7) |
| scale | Scale factor |
| delta | Value added to result |
Basic Usage¶
import cv2
import numpy as np
def laplacian_edge(image):
"""Laplacian edge detection"""
# Convert to grayscale
if len(image.shape) == 3:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image
# Remove noise (Laplacian is sensitive to noise)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
# Laplacian operation
laplacian = cv2.Laplacian(blurred, cv2.CV_64F, ksize=3)
# Convert to absolute value
laplacian = cv2.convertScaleAbs(laplacian)
return laplacian
# Usage example
img = cv2.imread('image.jpg')
edges = laplacian_edge(img)
cv2.imshow('Laplacian', edges)
cv2.waitKey(0)
LoG (Laplacian of Gaussian)¶
import cv2
import numpy as np
def log_edge_detection(image, sigma=1.0):
"""
LoG (Laplacian of Gaussian) edge detection
1. Remove noise with Gaussian blur
2. Detect edges with Laplacian
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Gaussian blur (kernel size based on sigma)
ksize = int(6 * sigma + 1)
if ksize % 2 == 0:
ksize += 1
blurred = cv2.GaussianBlur(gray, (ksize, ksize), sigma)
# Laplacian
log = cv2.Laplacian(blurred, cv2.CV_64F, ksize=3)
# Absolute value
log = cv2.convertScaleAbs(log)
return log
# Use LoG
img = cv2.imread('image.jpg')
edges = log_edge_detection(img, sigma=1.5)
cv2.imshow('LoG', edges)
cv2.waitKey(0)
5. Canny Edge Detection¶
Concept¶
Canny Edge Detection: Multi-stage edge detection algorithm
β Most widely used edge detection method
Canny's 3 Goals:
1. Low error rate: Detect only real edges
2. Accurate localization: Edges at precise locations
3. Single response: One line for one edge
4-Stage Processing:
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Gaussian β β Sobel β β Non- β β Hysteresis β
β Blur β βββΆ β Gradient β βββΆ β Maximum β βββΆ β Thresholdingβ
β β β β β Suppression β β β
βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
Canny Algorithm Details¶
Step 1: Noise Removal (Gaussian Blur)
- Apply 5x5 Gaussian filter
- Remove high-frequency noise
Step 2: Gradient Calculation
- Calculate Gx, Gy with Sobel operation
- Magnitude: G = β(GxΒ² + GyΒ²)
- Direction: ΞΈ = arctan(Gy/Gx)
Step 3: Non-Maximum Suppression (NMS)
βββββββββββββββββββββββββββββββββββββββ
β Keep only maximum values along β
β gradient direction β
β β Make edges 1 pixel thin β
βββββββββββββββββββββββββββββββββββββββ
Direction Quantization (4 directions):
90Β°
β
135Β° βββΌββ 45Β°
β
0Β° (180Β°)
Example:
When direction ΞΈ = 45Β°, compare along diagonal
βββββ¬ββββ¬ββββ
β β q β β
βββββΌββββΌββββ€
β β p β β Keep p if p > q and p > r
βββββΌββββΌββββ€
β β r β β
βββββ΄ββββ΄ββββ
Step 4: Hysteresis Thresholding
βββββββββββββββββββββββββββββββββββββββ
β high_threshold: Strong edges β
β low_threshold: Weak edges β
β β
β Strong edges: Always include β
β Weak edges: Include if connected β
β to strong edge β
β Others: Remove β
βββββββββββββββββββββββββββββββββββββββ
Example:
high = 100, low = 50
Pixel value 120 β Strong edge (include)
Pixel value 70 β Weak edge (check connection)
Pixel value 30 β Remove
cv2.Canny() Function¶
cv2.Canny(image, threshold1, threshold2, apertureSize=3, L2gradient=False)
| Parameter | Description |
|---|---|
| image | Input image (grayscale) |
| threshold1 | Low threshold |
| threshold2 | High threshold |
| apertureSize | Sobel kernel size (3, 5, 7) |
| L2gradient | True: L2 norm, False: L1 norm |
Basic Usage¶
import cv2
def canny_edge(image, low=50, high=150):
"""Canny edge detection"""
# Convert to grayscale
if len(image.shape) == 3:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image
# Remove noise (optional - also performed inside Canny)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
# Canny edge detection
edges = cv2.Canny(blurred, low, high)
return edges
# Usage example
img = cv2.imread('image.jpg')
edges = canny_edge(img, 50, 150)
cv2.imshow('Original', img)
cv2.imshow('Canny Edges', edges)
cv2.waitKey(0)
Threshold Tuning¶
import cv2
import numpy as np
def canny_with_trackbar(image_path):
"""Adjust Canny thresholds with trackbar"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
cv2.namedWindow('Canny')
def nothing(x):
pass
cv2.createTrackbar('Low', 'Canny', 50, 255, nothing)
cv2.createTrackbar('High', 'Canny', 150, 255, nothing)
while True:
low = cv2.getTrackbarPos('Low', 'Canny')
high = cv2.getTrackbarPos('High', 'Canny')
# Ensure low is not greater than high
if low >= high:
low = high - 1
edges = cv2.Canny(blurred, low, high)
cv2.imshow('Canny', edges)
if cv2.waitKey(1) & 0xFF == 27: # ESC
break
cv2.destroyAllWindows()
# Execute
canny_with_trackbar('image.jpg')
Automatic Threshold Setting¶
import cv2
import numpy as np
def auto_canny(image, sigma=0.33):
"""
Automatic threshold Canny
Calculate low and high based on median value
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
# Calculate median
median = np.median(blurred)
# Calculate thresholds
low = int(max(0, (1.0 - sigma) * median))
high = int(min(255, (1.0 + sigma) * median))
print(f"Auto threshold: low={low}, high={high}")
edges = cv2.Canny(blurred, low, high)
return edges
# Usage example
img = cv2.imread('image.jpg')
edges = auto_canny(img)
cv2.imshow('Auto Canny', edges)
cv2.waitKey(0)
Canny on Color Images¶
import cv2
import numpy as np
def canny_color(image, low=50, high=150):
"""
Canny edge detection on color images
Detect edges on each channel and combine
"""
# Method 1: Convert to grayscale then process
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges_gray = cv2.Canny(gray, low, high)
# Method 2: Process each channel then combine
b, g, r = cv2.split(image)
edges_b = cv2.Canny(b, low, high)
edges_g = cv2.Canny(g, low, high)
edges_r = cv2.Canny(r, low, high)
# Combine with OR operation
edges_color = cv2.bitwise_or(edges_b, edges_g)
edges_color = cv2.bitwise_or(edges_color, edges_r)
return edges_gray, edges_color
# Usage example
img = cv2.imread('image.jpg')
edges_gray, edges_color = canny_color(img)
cv2.imshow('Edges (Gray)', edges_gray)
cv2.imshow('Edges (Color)', edges_color)
cv2.waitKey(0)
6. Gradient Magnitude and Direction¶
Calculate Gradient Magnitude¶
import cv2
import numpy as np
def gradient_magnitude_direction(image):
"""Calculate gradient magnitude and direction"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (3, 3), 0)
# Sobel gradient
gx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
gy = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
# Magnitude
magnitude = np.sqrt(gx**2 + gy**2)
# Direction - radians
direction = np.arctan2(gy, gx)
# Convert direction to degrees (0-180)
direction_deg = np.degrees(direction) % 180
return magnitude, direction_deg
# Usage example
img = cv2.imread('image.jpg')
mag, dir = gradient_magnitude_direction(img)
# Normalize and display
mag_display = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
dir_display = (dir / 180 * 255).astype(np.uint8)
cv2.imshow('Magnitude', mag_display)
cv2.imshow('Direction', dir_display)
cv2.waitKey(0)
Visualize Gradient Direction¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
def visualize_gradient_direction(image, step=20):
"""
Visualize gradient direction with arrows
step: Sampling interval
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
gx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
gy = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
magnitude = np.sqrt(gx**2 + gy**2)
# Draw arrows
result = image.copy()
h, w = gray.shape
for y in range(step, h - step, step):
for x in range(step, w - step, step):
if magnitude[y, x] > 50: # Display only above certain magnitude
# Normalize direction vector
dx = gx[y, x]
dy = gy[y, x]
length = np.sqrt(dx**2 + dy**2)
if length > 0:
dx = int(dx / length * 10)
dy = int(dy / length * 10)
cv2.arrowedLine(
result,
(x, y),
(x + dx, y + dy),
(0, 255, 0),
1,
tipLength=0.3
)
return result
# Usage example
img = cv2.imread('image.jpg')
vis = visualize_gradient_direction(img, step=15)
cv2.imshow('Gradient Direction', vis)
cv2.waitKey(0)
Compare Edge Detection Algorithms¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
def compare_edge_detectors(image_path):
"""Compare various edge detection algorithms"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
# 1. Sobel
sobel_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
sobel = np.sqrt(sobel_x**2 + sobel_y**2)
sobel = np.clip(sobel, 0, 255).astype(np.uint8)
# 2. Scharr
scharr_x = cv2.Scharr(blurred, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(blurred, cv2.CV_64F, 0, 1)
scharr = np.sqrt(scharr_x**2 + scharr_y**2)
scharr = np.clip(scharr, 0, 255).astype(np.uint8)
# 3. Laplacian
laplacian = cv2.Laplacian(blurred, cv2.CV_64F, ksize=3)
laplacian = cv2.convertScaleAbs(laplacian)
# 4. Canny
canny = cv2.Canny(blurred, 50, 150)
# Visualization
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes[0, 0].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
axes[0, 0].set_title('Original')
axes[0, 1].imshow(sobel, cmap='gray')
axes[0, 1].set_title('Sobel')
axes[0, 2].imshow(scharr, cmap='gray')
axes[0, 2].set_title('Scharr')
axes[1, 0].imshow(laplacian, cmap='gray')
axes[1, 0].set_title('Laplacian')
axes[1, 1].imshow(canny, cmap='gray')
axes[1, 1].set_title('Canny')
axes[1, 2].axis('off')
for ax in axes.flatten():
ax.axis('off')
plt.tight_layout()
plt.show()
# Run comparison
compare_edge_detectors('image.jpg')
7. Exercises¶
Problem 1: Implement Adaptive Canny¶
Implement a Canny function that automatically adjusts thresholds based on the brightness distribution of the image.
Hint
Calculate low and high thresholds based on the median value of the image.Solution Code
import cv2
import numpy as np
def adaptive_canny(image, sigma=0.33):
"""
Adaptive Canny edge detection
Automatically set thresholds based on median brightness
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Calculate median
median = np.median(blurred)
# Calculate thresholds (adjust range with sigma)
low = int(max(0, (1.0 - sigma) * median))
high = int(min(255, (1.0 + sigma) * median))
edges = cv2.Canny(blurred, low, high)
return edges, low, high
# Test
img = cv2.imread('image.jpg')
edges, low, high = adaptive_canny(img)
print(f"Adaptive thresholds: low={low}, high={high}")
cv2.imshow('Adaptive Canny', edges)
cv2.waitKey(0)
Problem 2: Separate Edges by Direction¶
Implement a function that separates and displays horizontal and vertical edges.
Hint
Calculate gradient direction and classify as horizontal (near 0 degrees) or vertical (near 90 degrees) based on angle.Solution Code
import cv2
import numpy as np
def separate_edges_by_direction(image, angle_threshold=30):
"""
Separate horizontal/vertical edges
angle_threshold: Allowed angle range
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Sobel gradient
gx = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
gy = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
# Magnitude and direction
magnitude = np.sqrt(gx**2 + gy**2)
direction = np.degrees(np.arctan2(gy, gx)) % 180
# Apply threshold
_, edges = cv2.threshold(magnitude.astype(np.uint8), 50, 255, cv2.THRESH_BINARY)
# Horizontal edges (direction near 0 or 180 degrees)
# Strong Sobel gy means horizontal edge
horizontal_mask = ((direction < angle_threshold) |
(direction > 180 - angle_threshold))
horizontal_edges = np.zeros_like(edges)
horizontal_edges[horizontal_mask & (edges > 0)] = 255
# Vertical edges (direction near 90 degrees)
vertical_mask = ((direction > 90 - angle_threshold) &
(direction < 90 + angle_threshold))
vertical_edges = np.zeros_like(edges)
vertical_edges[vertical_mask & (edges > 0)] = 255
return horizontal_edges, vertical_edges
# Test
img = cv2.imread('image.jpg')
h_edges, v_edges = separate_edges_by_direction(img)
cv2.imshow('Horizontal Edges', h_edges)
cv2.imshow('Vertical Edges', v_edges)
cv2.waitKey(0)
Problem 3: Multi-Scale Edge Detection¶
Implement a function that detects edges at multiple scales and combines them.
Hint
Apply Gaussian blur with various sigma values, then apply Canny and combine the results.Solution Code
import cv2
import numpy as np
def multi_scale_canny(image, scales=[1.0, 2.0, 4.0], low=50, high=150):
"""
Multi-scale Canny edge detection
scales: Gaussian blur sigma values
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
combined_edges = np.zeros(gray.shape, dtype=np.uint8)
for sigma in scales:
# Kernel size based on scale
ksize = int(6 * sigma + 1)
if ksize % 2 == 0:
ksize += 1
# Apply Gaussian blur
blurred = cv2.GaussianBlur(gray, (ksize, ksize), sigma)
# Canny edge detection
edges = cv2.Canny(blurred, low, high)
# Combine (OR operation)
combined_edges = cv2.bitwise_or(combined_edges, edges)
return combined_edges
# Test
img = cv2.imread('image.jpg')
edges = multi_scale_canny(img, scales=[1.0, 2.0, 3.0])
cv2.imshow('Multi-scale Canny', edges)
cv2.waitKey(0)
Recommended Problems¶
| Difficulty | Topic | Description |
|---|---|---|
| β | Basic Canny | Apply Canny to various images |
| ββ | Threshold Experiment | Find optimal thresholds with trackbar |
| ββ | Preprocessing Comparison | Compare edge quality by blur type |
| βββ | Document Scanning | Detect document contours |
| βββ | Coin Detection | Find coin boundaries using edges |
Next Steps¶
- 09_Contours.md - findContours, drawContours, hierarchy structure