Skip to content
AI360XpertBeta

Spatial Filtering and Edge Detection

Sliding a small kernel across an image rewrites each pixel from its neighbors, so one pass can blur noise, sharpen detail, or expose edges as high gradient responses.

A 3 by 3 Sobel kernel slides over a dark-to-bright step and scores 320 at the boundary but zero on flat ground, turning intensity change into edge strength.
A 3 by 3 Sobel kernel slides over a dark-to-bright step and scores 320 at the boundary but zero on flat ground, turning intensity change into edge strength.

Why Does This Exist?

Raw pixels carry no notion of boundaries: a detector staring at one pixel cannot tell whether it sits inside a wall or on the outline of a door. Looking at each pixel together with its neighbors fixes that, because edges are exactly the places where a neighborhood changes fast. Spatial filtering makes the neighborhood the unit of work: center a small matrix called a kernel over each pixel, multiply overlapping values, and sum them into the output pixel.

That one operation covers blurring, sharpening, and edge finding, and it is the direct ancestor of the convolutional layer. The Sobel operator page isolates the gradient kernels, Gaussian smoothing covers the blur-first step edges depend on, and Canny chains these pieces into a full detector. This page covers the shared sliding mechanism, the Sobel gradient math with a hand-scored example, and the two traps that ruin first attempts.

Think of It Like This

Rubbing a coin through paper with a pencil

Lay a coin under paper and rub a pencil over it: flat paper stays faint, while the coin's raised rim catches dark lead exactly where the height changes. The pencil is the kernel, the rubbing motion is the slide, and the dark rim lines are the edge map. Flat regions score near zero just as flat paper stays blank.

The analogy stops at the paper. A rubbing keeps only one fixed pattern, while a filter bank swaps kernels per task: averaging kernels blur, Sobel kernels differentiate, and learned kernels in a network do both at once.

How It Actually Works

The sliding weighted sum

Filtering is cross-correlation: for a 3×33 \times 3 kernel KK centered on pixel (x,y)(x, y) of image II, the output is O(x,y)=∑i=−11∑j=−11K(i,j)⋅I(x+i,y+j)O(x, y) = \sum_{i=-1}^{1}\sum_{j=-1}^{1} K(i, j) \cdot I(x+i, y+j). Here K(i,j)K(i, j) is the kernel weight at offset (i,j)(i, j) and I(x+i,y+j)I(x+i, y+j) is the covered pixel. Every output pixel is a weighted vote of its nine neighbors. At image borders part of the kernel hangs off the edge, so implementations pad the frame, usually with zeros, to keep the output the same size. A stride of 1 slides pixel by pixel; larger strides downsample.

Sobel gradients

Edges are rapid intensity change, which is a derivative, so edge kernels approximate derivatives. The Sobel operator pairs two kernels, GxG_x for horizontal change and GyG_y for vertical change:

Gx=[−101−202−101],Gy=[−1−2−1000121]G_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix}, \quad G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix}

The center column of zeros with doubled middle weights smooths along the edge direction while differencing across it, which is why Sobel tolerates noise slightly better than a bare difference. Strength and direction combine both responses: magnitude Gx2+Gy2\sqrt{G_x^2 + G_y^2} and direction arctan⁡(Gy/Gx)\arctan(G_y / G_x), where GxG_x and GyG_y are the two kernel responses at that pixel.

Worked example: scoring one step edge

Take a 3×33 \times 3 patch with a dark-to-bright vertical step, columns of 10, 10, and 90:

[101090101090101090]\begin{bmatrix} 10 & 10 & 90 \\ 10 & 10 & 90 \\ 10 & 10 & 90 \end{bmatrix}

Apply GxG_x row by row. Top row: −1⋅10+0⋅10+1⋅90=80-1 \cdot 10 + 0 \cdot 10 + 1 \cdot 90 = 80. Middle row: −2⋅10+0⋅10+2⋅90=160-2 \cdot 10 + 0 \cdot 10 + 2 \cdot 90 = 160. Bottom row: same as the top, 8080. Total Gx=80+160+80=320G_x = 80 + 160 + 80 = 320. Apply GyG_y: the top row contributes −(10+20+10)=−40-(10 + 20 + 10) = -40, the middle row contributes 00, and the bottom row contributes +(10+20+10)=40+(10 + 20 + 10) = 40, so Gy=0G_y = 0. Magnitude is 3202+0=320\sqrt{320^2 + 0} = 320 at direction arctan⁡(0/320)=0\arctan(0/320) = 0 degrees, a strong vertical edge. On a flat patch of all 10s both kernels sum to exactly 00, which is the point: uniform regions vanish and only change survives.

Code

patch = [[10, 10, 90], [10, 10, 90], [10, 10, 90]]sobel_x = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]sobel_y = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]]
def respond(kernel):    return sum(k * p for krow, prow in zip(kernel, patch) for k, p in zip(krow, prow))
gx, gy = respond(sobel_x), respond(sobel_y)print(gx, gy, (gx * gx + gy * gy) ** 0.5)# -> 320 0 320.0

Watch Out For

Running a derivative kernel on raw noisy pixels

Derivative kernels amplify whatever changes fastest, and sensor noise changes faster than any real edge. The symptom is an edge map of speckle with the true boundaries buried. Blur with a small Gaussian first, then differentiate; that blur-then-differentiate order is exactly what the Gaussian plus Sobel chain in Canny does.

Displaying signed responses as plain images

Sobel responses run negative on bright-to-dark steps, and a standard 8-bit display clips negatives to zero, silently deleting half the edges. The symptom is edges visible in only one direction around every object. Take the magnitude or absolute value before display, never the raw signed array.

The Quick Version

  • Each output pixel is a weighted sum of its neighborhood under a small kernel.
  • Sobel pairs GxG_x and GyG_y kernels that difference across an edge while smoothing along it.
  • A 10-to-90 step scores Gx=320G_x = 320 and Gy=0G_y = 0, while flat regions score exactly zero.
  • Zero padding keeps the output size; stride above 1 downsamples.
  • Gaussian blur must come before differentiation, and magnitudes must precede display.