Edge Detection

What are Edges?

Edge: Significant local change in pixel intensity (boundary between regions)

Types of edges:

  • Step edge: Sharp intensity change
  • Ramp edge: Gradual intensity change
  • Roof edge: Peak or valley

Why detect edges?

  • Object boundaries
  • Feature extraction
  • Image segmentation
  • Shape analysis

Gradient-Based Edge Detection

Image gradient: Rate of change in intensity

Gradient components:

  • Gx: Horizontal gradient (∂I/∂x)
  • Gy: Vertical gradient (∂I/∂y)

Gradient magnitude: G = √(Gx² + Gy²)
Gradient direction: θ = arctan(Gy/Gx)

Large gradient = edge!

Sobel Edge Detection

Sobel kernels: Approximate gradient with smoothing

Features:

  • Combines gradient + smoothing
  • 3×3 kernels
  • Good noise resistance

Sobel in OpenCV

  1. import cv2
  2. import numpy as np
  3. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  4. # compute sobel x (vertical edges)
  5. sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
  6. #ans: dx=1, dy=0 means x-derivative (vertical edges)
  7. # compute sobel y (horizontal edges)
  8. sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
  9. #ans: dx=0, dy=1 means y-derivative (horizontal edges)
  10. # combine both gradients
  11. #ans: magnitude = np.sqrt(sobelx**2 + sobely**2)
  12. # convert to uint8 for display
  13. #ans: magnitude = np.uint8(np.clip(magnitude, 0, 255))

Sobel Parameters

  1. # cv2.Sobel(src, ddepth, dx, dy, ksize)
  2. sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=5)
  3. # ddepth: output image depth
  4. #ans: CV_64F to avoid negative values being clipped
  5. # dx, dy: derivative order
  6. #ans: (1,0)=x-direction, (0,1)=y-direction
  7. # ksize: kernel size (1, 3, 5, 7)
  8. #ans: larger kernel = more smoothing, less noise

Scharr Edge Detection

Scharr: More accurate gradient than Sobel for small 3×3 kernels

When to use: Need more accuracy with 3×3 kernel

Scharr in OpenCV

  1. # scharr gradient (more accurate than sobel)
  2. scharrx = cv2.Scharr(gray, cv2.CV_64F, 1, 0)
  3. #ans: x-direction gradient
  4. scharry = cv2.Scharr(gray, cv2.CV_64F, 0, 1)
  5. #ans: y-direction gradient
  6. # only works with 3x3 kernel
  7. #ans: more accurate than 3x3 Sobel
  8. # combine gradients
  9. magnitude = np.sqrt(scharrx**2 + scharry**2)
  10. #ans: same as Sobel combination

Laplacian Edge Detection

Laplacian: Second derivative operator

Formula: ∂²I/∂x² + ∂²I/∂y²

Property: Sensitive to noise (use after smoothing!)

Laplacian in OpenCV

  1. # laplacian (2nd derivative)
  2. laplacian = cv2.Laplacian(gray, cv2.CV_64F, ksize=3)
  3. #ans: detects edges in all directions
  4. # why second derivative?
  5. #ans: zero-crossing = edge location
  6. # laplacian is very noise-sensitive
  7. blurred = cv2.GaussianBlur(gray, (3, 3), 0)
  8. laplacian = cv2.Laplacian(blurred, cv2.CV_64F)
  9. #ans: blur first to reduce noise

Canny Edge Detection

Canny: Multi-stage algorithm (best edge detector!)

Steps:

  1. Gaussian smoothing (noise reduction)
  2. Gradient calculation (Sobel)
  3. Non-maximum suppression (thin edges)
  4. Double thresholding (strong/weak edges)
  5. Edge tracking by hysteresis

Output: Binary edge map (0 or 255)

Canny Parameters

  1. # canny edge detection
  2. edges = cv2.Canny(gray, threshold1=50, threshold2=150)
  3. #ans: threshold1=low, threshold2=high
  4. # threshold1 (low threshold):
  5. #ans: weak edges below this are discarded
  6. # threshold2 (high threshold):
  7. #ans: strong edges above this are kept
  8. # ratio recommendation:
  9. #ans: threshold2 = 2 × threshold1 or 3 × threshold1
  10. # edges between thresholds:
  11. #ans: kept if connected to strong edge (hysteresis)

Canny in OpenCV

  1. # basic canny
  2. edges = cv2.Canny(gray, 50, 150)
  3. #ans: detects edges, outputs binary image
  4. # canny with aperture size
  5. edges = cv2.Canny(gray, 50, 150, apertureSize=3)
  6. #ans: apertureSize for Sobel operator (3, 5, 7)
  7. # canny with L2 gradient
  8. edges = cv2.Canny(gray, 50, 150, L2gradient=True)
  9. #ans: L2gradient=True uses √(Gx²+Gy²), more accurate
  10. #ans: L2gradient=False uses |Gx|+|Gy|, faster

Choosing Thresholds

Low threshold: Too low → noise edges
High threshold: Too high → missed edges

Strategy:

  • Try ratio 2:1 or 3:1 (e.g., 50, 150)
  • Adjust based on results
  • High contrast images → higher thresholds
  • Low contrast images → lower thresholds

Exercises - Part 1 (Concepts)

  1. # what defines an edge?
  2. #ans: significant change in pixel intensity
  3. # what is gradient magnitude?
  4. #ans: √(Gx² + Gy²), strength of intensity change
  5. # sobel x detects which edges?
  6. #ans: vertical edges (left-right transitions)
  7. # why use CV_64F for sobel?
  8. #ans: preserves negative values, prevents clipping
  9. # what does canny output?
  10. #ans: binary edge map (0 or 255)

Exercises - Part 2 (Concepts)

  1. # difference between sobel and scharr?
  2. #ans: scharr more accurate for 3x3 kernels
  3. # why is laplacian noise-sensitive?
  4. #ans: second derivative amplifies noise
  5. # what is non-maximum suppression?
  6. #ans: thins edges to single-pixel width
  7. # what is hysteresis in canny?
  8. #ans: connects weak edges to strong edges
  9. # recommended canny threshold ratio?
  10. #ans: high = 2×low or 3×low

Exercises - Part 3 (Coding)

  1. # convert to grayscale
  2. #ans: gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  3. # compute sobel x
  4. #ans: sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
  5. # compute sobel y
  6. #ans: sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
  7. # combine sobel x and y
  8. #ans: magnitude = np.sqrt(sobelx**2 + sobely**2)
  9. #ans: magnitude = np.uint8(np.clip(magnitude, 0, 255))

Exercises - Part 4 (Coding)

  1. # apply canny with thresholds 100, 200
  2. #ans: edges = cv2.Canny(gray, 100, 200)
  3. # apply laplacian
  4. #ans: laplacian = cv2.Laplacian(gray, cv2.CV_64F, ksize=3)
  5. # apply scharr x
  6. #ans: scharrx = cv2.Scharr(gray, cv2.CV_64F, 1, 0)
  7. # apply scharr y
  8. #ans: scharry = cv2.Scharr(gray, cv2.CV_64F, 0, 1)

Exercises - Part 5 (Coding)

  1. # canny with different thresholds
  2. edges1 = cv2.Canny(gray, 30, 90)
  3. edges2 = cv2.Canny(gray, 100, 300)
  4. #ans: edges1 detects more (lower thresholds)
  5. #ans: edges2 detects fewer (higher thresholds)
  6. # blur before canny
  7. #ans: blurred = cv2.GaussianBlur(gray, (5, 5), 0)
  8. #ans: edges = cv2.Canny(blurred, 50, 150)
  9. #ans: reduces noise in edge detection

Exercises - Part 6 (Mixed)

  1. # sobel with larger kernel
  2. sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=7)
  3. #ans: more smoothing, less sensitive to noise
  4. # absolute value of sobel
  5. #ans: abs_sobelx = np.absolute(sobelx)
  6. #ans: converts negative gradients to positive
  7. # canny with L2 gradient
  8. edges = cv2.Canny(gray, 50, 150, L2gradient=True)
  9. #ans: more accurate gradient calculation

Exercises - Part 7 (Mixed)

  1. # gradient direction from sobel
  2. sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0)
  3. sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1)
  4. #ans: direction = np.arctan2(sobely, sobelx)
  5. #ans: direction in radians
  6. # combine laplacian and gaussian
  7. #ans: blurred = cv2.GaussianBlur(gray, (5, 5), 0)
  8. #ans: laplacian = cv2.Laplacian(blurred, cv2.CV_64F)
  9. # what is LoG?
  10. #ans: Laplacian of Gaussian (blur + laplacian)
  11. #ans: reduces noise sensitivity

Exercises - Part 8 (Advanced)

  1. # automatic canny threshold (median-based)
  2. median = np.median(gray)
  3. #ans: lower = int(max(0, 0.7 * median))
  4. #ans: upper = int(min(255, 1.3 * median))
  5. #ans: edges = cv2.Canny(gray, lower, upper)
  6. # combine canny with morphology
  7. edges = cv2.Canny(gray, 50, 150)
  8. kernel = np.ones((3, 3), dtype=np.uint8)
  9. #ans: edges = cv2.dilate(edges, kernel, iterations=1)
  10. #ans: thickens edges for better visibility

Google tag (gtag.js)