Edge detection with the Sobel operator
Estimate intensity gradients with two kernels and combine them into edge strength and direction.
On this page
Convolution and basic trigonometry.
- Estimate intensity gradients with two kernels and combine them into edge strength and direction.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
An edge is a place where image intensity changes sharply, such as the boundary of an object. Intensity change is measured by the gradient, the vector of how fast the brightness changes in the horizontal and vertical directions. The Sobel operator estimates both with two 3 × 3 kernels. SOBEL_X responds to horizontal change (and so to vertical edges), SOBEL_Y to vertical change (and so to horizontal edges). Each kernel combines a derivative across the edge with smoothing along it, which makes it less sensitive to noise than a plain difference.
With the two responses gx and gy at a pixel, the edge strength is the gradient magnitude √(gx² + gy²) and the edge direction is atan2(gy, gx). In image coordinates the y axis points down, so an edge where intensity increases downward has a gradient angle of 90 degrees. The example image has a vertical edge between columns 1 and 2. The magnitude is 400 where the edge lies within the window and 0 in the flat region, and the gradient direction is 0 degrees, pointing toward increasing brightness.
A raw gradient map is a first step, not a finished edge detector. The Canny detector, still widely used, smooths the image, computes gradients, thins ridges with non-maximum suppression, and keeps connected edges using two thresholds (hysteresis). Edge maps feed many later steps such as line detection, contour extraction, and shape analysis.
Code example
import math
def convolve(image, kernel):
kh, kw = len(kernel), len(kernel[0])
return [
[
sum(image[r + i][c + j] * kernel[i][j] for i in range(kh) for j in range(kw))
for c in range(len(image[0]) - kw + 1)
]
for r in range(len(image) - kh + 1)
]
SOBEL_X = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]
SOBEL_Y = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]]
vertical_edge = [[0, 0, 100, 100, 100] for _ in range(5)]
gx = convolve(vertical_edge, SOBEL_X)
gy = convolve(vertical_edge, SOBEL_Y)
magnitude = [[round(math.hypot(a, b)) for a, b in zip(row_x, row_y)] for row_x, row_y in zip(gx, gy)]
print(magnitude[1])
print(round(math.degrees(math.atan2(gy[1][0], gx[1][0]))))
horizontal_edge = [[0] * 5, [0] * 5, [100] * 5, [100] * 5, [100] * 5]
print(convolve(horizontal_edge, SOBEL_Y)[0])[400, 400, 0] 0 [400, 400, 400]
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
import math def convolve(image, kernel): kh, kw = len(kernel), len(kernel[0]) return [ [ sum(image[r + i][c + j] * kernel[i][j] for i in range(kh) for j in range(kw)) for c in range(len(image[0]) - kw + 1) ] for r in range(len(image) - kh + 1) ] SOBEL_X = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]SOBEL_Y = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]] vertical_edge = [[0, 0, 100, 100, 100] for _ in range(5)]gx = convolve(vertical_edge, SOBEL_X)gy = convolve(vertical_edge, SOBEL_Y)magnitude = [[round(math.hypot(a, b)) for a, b in zip(row_x, row_y)] for row_x, row_y in zip(gx, gy)]print(magnitude[1])print(round(math.degrees(math.atan2(gy[1][0], gx[1][0])))) horizontal_edge = [[0] * 5, [0] * 5, [100] * 5, [100] * 5, [100] * 5]print(convolve(horizontal_edge, SOBEL_Y)[0])Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Right pixels minus left pixels, with the middle row weighted double: a smoothed horizontal derivative. | — | — | |
| A vertical edge has no change from top to bottom, so every gy value is 0. | — | — | |
| The magnitude is 400 for the two windows that straddle the 0 → 100 step, and 0 in the flat region to the right. | — | [400, 400, 0] | |
| gy = 0 and gx > 0, so the gradient points along +x: 0 degrees, toward the brighter side. | — | [400, 400, 0] 0 | |
| For a horizontal edge the roles swap: SOBEL_Y responds strongly at every position. | — | [400, 400, 0] 0 [400, 400, 400] |
Behind the scenes
How Python processes this code
Python runs this source through its interpreter. It creates objects for values and resolves names as each statement runs.
Cost and common mistakes
Two 3 × 3 convolutions: O(H·W) time with a constant of 18 multiply-adds per pixel.
- Noise creates false edges, because differentiation amplifies it. Smooth the image first, as Canny does.
- Sobel gradients are not thin lines. Thresholding without non-maximum suppression gives thick, uneven edges.
- Check axis conventions. Positive gy means intensity increases downward in image coordinates.
Try it yourself
What magnitude would a perfectly flat image of constant value 50 give?
Reveal answer and reasoning
0 everywhere, because both Sobel kernels sum to zero and there is no intensity change.