Edge: Significant local change in pixel intensity (boundary between regions)
Types of edges:
Why detect edges?
Image gradient: Rate of change in intensity
Gradient components:
Gradient magnitude: G = √(Gx² + Gy²) Gradient direction: θ = arctan(Gy/Gx)
G = √(Gx² + Gy²)
θ = arctan(Gy/Gx)
Large gradient = edge!
Sobel kernels: Approximate gradient with smoothing
Features:
import cv2import numpy as npgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# compute sobel x (vertical edges)sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)#ans: dx=1, dy=0 means x-derivative (vertical edges)# compute sobel y (horizontal edges)sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)#ans: dx=0, dy=1 means y-derivative (horizontal edges)# combine both gradients#ans: magnitude = np.sqrt(sobelx**2 + sobely**2)# convert to uint8 for display#ans: magnitude = np.uint8(np.clip(magnitude, 0, 255))
# cv2.Sobel(src, ddepth, dx, dy, ksize)sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=5)# ddepth: output image depth#ans: CV_64F to avoid negative values being clipped# dx, dy: derivative order#ans: (1,0)=x-direction, (0,1)=y-direction# ksize: kernel size (1, 3, 5, 7)#ans: larger kernel = more smoothing, less noise
Scharr: More accurate gradient than Sobel for small 3×3 kernels
When to use: Need more accuracy with 3×3 kernel
# scharr gradient (more accurate than sobel)scharrx = cv2.Scharr(gray, cv2.CV_64F, 1, 0)#ans: x-direction gradientscharry = cv2.Scharr(gray, cv2.CV_64F, 0, 1)#ans: y-direction gradient# only works with 3x3 kernel#ans: more accurate than 3x3 Sobel# combine gradientsmagnitude = np.sqrt(scharrx**2 + scharry**2)#ans: same as Sobel combination
Laplacian: Second derivative operator
Formula: ∂²I/∂x² + ∂²I/∂y²
Property: Sensitive to noise (use after smoothing!)
# laplacian (2nd derivative)laplacian = cv2.Laplacian(gray, cv2.CV_64F, ksize=3)#ans: detects edges in all directions# why second derivative?#ans: zero-crossing = edge location# laplacian is very noise-sensitiveblurred = cv2.GaussianBlur(gray, (3, 3), 0)laplacian = cv2.Laplacian(blurred, cv2.CV_64F)#ans: blur first to reduce noise
Canny: Multi-stage algorithm (best edge detector!)
Steps:
Output: Binary edge map (0 or 255)
# canny edge detectionedges = cv2.Canny(gray, threshold1=50, threshold2=150)#ans: threshold1=low, threshold2=high# threshold1 (low threshold):#ans: weak edges below this are discarded# threshold2 (high threshold):#ans: strong edges above this are kept# ratio recommendation:#ans: threshold2 = 2 × threshold1 or 3 × threshold1# edges between thresholds:#ans: kept if connected to strong edge (hysteresis)
# basic cannyedges = cv2.Canny(gray, 50, 150)#ans: detects edges, outputs binary image# canny with aperture sizeedges = cv2.Canny(gray, 50, 150, apertureSize=3)#ans: apertureSize for Sobel operator (3, 5, 7)# canny with L2 gradientedges = cv2.Canny(gray, 50, 150, L2gradient=True)#ans: L2gradient=True uses √(Gx²+Gy²), more accurate#ans: L2gradient=False uses |Gx|+|Gy|, faster
Low threshold: Too low → noise edges High threshold: Too high → missed edges
Strategy:
# what defines an edge?#ans: significant change in pixel intensity# what is gradient magnitude?#ans: √(Gx² + Gy²), strength of intensity change# sobel x detects which edges?#ans: vertical edges (left-right transitions)# why use CV_64F for sobel?#ans: preserves negative values, prevents clipping# what does canny output?#ans: binary edge map (0 or 255)
# difference between sobel and scharr?#ans: scharr more accurate for 3x3 kernels# why is laplacian noise-sensitive?#ans: second derivative amplifies noise# what is non-maximum suppression?#ans: thins edges to single-pixel width# what is hysteresis in canny?#ans: connects weak edges to strong edges# recommended canny threshold ratio?#ans: high = 2×low or 3×low
# convert to grayscale#ans: gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# compute sobel x#ans: sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)# compute sobel y#ans: sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)# combine sobel x and y#ans: magnitude = np.sqrt(sobelx**2 + sobely**2)#ans: magnitude = np.uint8(np.clip(magnitude, 0, 255))
# apply canny with thresholds 100, 200#ans: edges = cv2.Canny(gray, 100, 200)# apply laplacian#ans: laplacian = cv2.Laplacian(gray, cv2.CV_64F, ksize=3)# apply scharr x#ans: scharrx = cv2.Scharr(gray, cv2.CV_64F, 1, 0)# apply scharr y#ans: scharry = cv2.Scharr(gray, cv2.CV_64F, 0, 1)
# canny with different thresholdsedges1 = cv2.Canny(gray, 30, 90)edges2 = cv2.Canny(gray, 100, 300)#ans: edges1 detects more (lower thresholds)#ans: edges2 detects fewer (higher thresholds)# blur before canny#ans: blurred = cv2.GaussianBlur(gray, (5, 5), 0)#ans: edges = cv2.Canny(blurred, 50, 150)#ans: reduces noise in edge detection
# sobel with larger kernelsobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=7)#ans: more smoothing, less sensitive to noise# absolute value of sobel#ans: abs_sobelx = np.absolute(sobelx)#ans: converts negative gradients to positive# canny with L2 gradientedges = cv2.Canny(gray, 50, 150, L2gradient=True)#ans: more accurate gradient calculation
# gradient direction from sobelsobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0)sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1)#ans: direction = np.arctan2(sobely, sobelx)#ans: direction in radians# combine laplacian and gaussian#ans: blurred = cv2.GaussianBlur(gray, (5, 5), 0)#ans: laplacian = cv2.Laplacian(blurred, cv2.CV_64F)# what is LoG?#ans: Laplacian of Gaussian (blur + laplacian)#ans: reduces noise sensitivity
# automatic canny threshold (median-based)median = np.median(gray)#ans: lower = int(max(0, 0.7 * median))#ans: upper = int(min(255, 1.3 * median))#ans: edges = cv2.Canny(gray, lower, upper)# combine canny with morphologyedges = cv2.Canny(gray, 50, 150)kernel = np.ones((3, 3), dtype=np.uint8)#ans: edges = cv2.dilate(edges, kernel, iterations=1)#ans: thickens edges for better visibility
Google tag (gtag.js)