Edge detection with the Sobel operator

Estimate intensity gradients with two kernels and combine them into edge strength and direction.

Python
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])
Output
[400, 400, 0]
0
[400, 400, 400]

Explanation

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.

Explore the dry run
Guided walkthroughReady
1import math
2
3
4def convolve(image, kernel):
5 kh, kw = len(kernel), len(kernel[0])
6 return [
7 [
8 sum(image[r + i][c + j] * kernel[i][j] for i in range(kh) for j in range(kw))
9 for c in range(len(image[0]) - kw + 1)
10 ]
11 for r in range(len(image) - kh + 1)
12 ]
13
14
15SOBEL_X = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]
16SOBEL_Y = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]]
17
18vertical_edge = [[0, 0, 100, 100, 100] for _ in range(5)]
19gx = convolve(vertical_edge, SOBEL_X)
20gy = convolve(vertical_edge, SOBEL_Y)
21magnitude = [[round(math.hypot(a, b)) for a, b in zip(row_x, row_y)] for row_x, row_y in zip(gx, gy)]
22print(magnitude[1])
23print(round(math.degrees(math.atan2(gy[1][0], gx[1][0]))))
24
25horizontal_edge = [[0] * 5, [0] * 5, [100] * 5, [100] * 5, [100] * 5]
26print(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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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]

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.

Further reading