Skillversitas

Edge and Gradient Detection

Edge detection measures how quickly intensity changes across the image - the gradient. The raw operators (Sobel, Scharr, Laplacian) compute that gradient in a specific direction, and Canny builds on top of them the full edge-detection pipeline: smooth, gradient, non-maximum suppression, and hysteresis thresholding. The distinction that matters in practice: a Sobel/Laplacian image is a map of change you interpret, while Canny is a decision that returns clean 1-pixel edges ready to feed into contours and shape analysis.

Sobel and Scharr: Directional Gradients

import cv2
import numpy as np

img = cv2.imread("photo.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Sobel: derivative in x and y, ksize 3 (fast) or 5 (smoother)
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)

# the gradient magnitude - direction-independent "amount of change"
magnitude = cv2.magnitude(sobelx, sobely)

# Scharr is a refined 3x3 version, slightly more accurate for small kernels
scharrx = cv2.Scharr(gray, cv2.CV_64F, 1, 0)

# the output is float with signed values - convert for display
mag_u8 = np.uint8(np.clip(magnitude, 0, 255))
cv2.imwrite("sobel_mag.jpg", mag_u8)

Sobel(src, ddepth, dx, dy, ksize) computes the dx-then-dy derivative - (1,0) is the horizontal gradient (vertical edges light up), (0,1) the vertical. The ddepth=cv2.CV_64F argument is important: the derivative produces signed values larger than a byte, so you compute in float and convert for display. cv2.magnitude combines the two components into the direction-independent gradient strength - the "where is there an edge, regardless of orientation" map.

Laplacian: The Isotropic Second Derivative

import cv2
import numpy as np

gray = cv2.cvtColor(cv2.imread("photo.jpg"), cv2.COLOR_BGR2GRAY)

lap = cv2.Laplacian(gray, cv2.CV_64F)
lap_u8 = np.uint8(np.abs(lap))        # second derivative is signed; take abs

cv2.imwrite("laplacian.jpg", lap_u8)

# the blur-vs-sharp heuristic: a blurry image has low Laplacian variance
sharp_var = cv2.Laplacian(cv2.imread("sharp.jpg"), cv2.CV_64F).var()
blur_var = cv2.Laplacian(cv2.imread("blurry.jpg"), cv2.CV_64F).var()
print(sharp_var, blur_var)            # the sharp one is much higher

The Laplacian is the second derivative in all directions at once - it responds to intensity changes of change, so edges show as a bright-dark pair rather than a single line. Its signature use outside edge maps is the focus measure: the variance of the Laplacian is the standard sharpness metric, the one that drives auto-focus selection and "was this frame in focus" filters in video processing.

Canny: The Full Pipeline

import cv2

gray = cv2.cvtColor(cv2.imread("photo.jpg"), cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)

# Canny(image, threshold1, threshold2, apertureSize)
edges = cv2.Canny(blur, 100, 200, apertureSize=3)
cv2.imwrite("canny.jpg", edges)

Canny runs four stages internally: Gaussian smooth, gradient via Sobel, non-maximum suppression (thin each edge to its ridge so it is 1 pixel wide), and hysteresis thresholding. The two thresholds implement hysteresis: gradients above threshold2 are definite edges, below threshold1 are discarded, and anything in between is kept only if connected to a strong edge - that is what produces continuous contours instead of fragments. The practical rule: threshold2 around 2-3x threshold1, and tune threshold1 until the edges you care about survive. Too low = noise edges; too high = broken contours.

Tuning and Interpretation

  • Always smooth first. Canny smooths internally, but a heavier Gaussian before it (on noisy sources) gives cleaner, more connected edges.
  • Edges are not objects. The output is a set of curves - to get shapes, feed Canny (or a threshold) into findContours, the next-but-one chapter.
  • Direction matters for features. The separate Sobel x/y images are how orientation-selective features (the HOG descriptor, texture analysis) are built - the magnitude loses the direction they need.
  • apertureSize is the Sobel kernel (1, 3, 5, 7) - larger = smoother, weaker response.

Summary

Compute gradients with Sobel (directional, in float64) and cv2.magnitude (direction-independent strength), use the Laplacian variance as the standard sharpness/focus metric, and for actual edges use Canny - the four-stage pipeline whose hysteresis (two thresholds, keep the weak only when connected to the strong) is what makes edges thin and continuous. Smooth before you run it, tune the lower threshold for the edges you care about, and remember the output is curves: the path to shapes is Canny or threshold into findContours.