Linear Filtering and Convolution

A photograph fresh off a camera sensor is never quite clean. It carries speckled noise from photon counting, faint blur from lens imperfections, and edges that a later algorithm needs to find precisely. All of these problems share a structural feature: fixing them requires looking at a pixel and its neighbors, not the pixel alone. Point Operations can remap brightness or contrast. But they cannot smooth noise or detect an edge. This is because they process each pixel in isolation. Linear filtering removes that limitation. It defines a new value at each pixel as a weighted combination of the values in a small surrounding neighborhood. This idea is the workhorse underneath nearly every classical vision pipeline. It also underlies much of modern deep learning. Camera image signal processors denoise and sharpen using linear filters before you ever see a photo. Edge detectors, corner detectors, and feature descriptors begin with a handful of small convolutions. Convolutional neural networks generalize the exact same sliding-window, weighted-sum operation. They learn the kernel weights from data instead of designing them by hand. If you understand convolution here, you already understand the core arithmetic of a CNN layer. The big picture is deceptively simple: pick a small grid of numbers (the kernel), slide it over the image. At each position, compute a weighted sum of the pixels underneath it. The entire richness of the topic lies in two questions. What weights should you choose? And how do you handle the mathematics precisely (correlation versus true convolution, and what happens at the image border)? Get these right and you unlock blurring, sharpening, edge detection. You also unlock the mathematical language (the frequency domain) used to reason about all of them. This article builds linear filtering from first principles. It covers the definitions of correlation and convolution, the algorithm and its complexity, and a worked numerical trace. It also covers the classic implementation pitfalls, and the historical path from analog signal processing to today's learned convolutional layers.

Key takeaways
  • A linear filter replaces each pixel with a weighted sum of its neighborhood, using weights given by a small kernel.
  • Correlation slides the kernel as-is; convolution flips the kernel first — they coincide for symmetric kernels.
  • Filtering is linear and shift-invariant: scale inputs, scale outputs; shift the image, shift the output identically.
  • Naive filtering costs O(N · k²) for an N-pixel image and a k×k kernel; separable kernels cut this to O(N · k).
  • Border handling (zero-pad, replicate, reflect, wrap) is a real design decision, not a footnote — it changes pixel values near edges.
  • Convolution is the exact arithmetic operation inside a convolutional neural network layer, just with learned kernels.
The core insight

Linear filtering computes each output pixel as a fixed weighted sum of an input neighborhood, and that single sliding-window operation — convolution — is the shared mathematical core of classical image processing and modern CNNs.

The idea

A linear filter (also called a neighborhood operator or linear shift-invariant system) produces an output image whose value at each pixel is a linear combination of input pixel values in a surrounding window. The pattern of weights is called the kernel or filter mask, written as a small array h[i,j]h[i,j], typically odd-sized (3×33\times3, 5×55\times5, …) so it has a well-defined center pixel. There are two closely related, frequently confused operations. Correlation places the kernel directly over the neighborhood and computes

g[x,y]=∑i=−kk∑j=−kkh[i,j] f[x+i,y+j].g[x,y] = \sum_{i=-k}^{k}\sum_{j=-k}^{k} h[i,j]\, f[x+i, y+j].

Convolution first flips the kernel by 180° (negating both indices) and then does the same sliding sum:

g[x,y]=(f∗h)[x,y]=∑i=−kk∑j=−kkh[i,j] f[x−i,y−j].g[x,y] = (f * h)[x,y] = \sum_{i=-k}^{k}\sum_{j=-k}^{k} h[i,j]\, f[x-i, y-j].

For a kernel that is symmetric under 180° rotation (true of a Gaussian kernel and of a uniform box kernel), correlation and convolution give identical results. For an asymmetric kernel — a derivative kernel used for Image Gradients, for instance — they differ, and the distinction matters: convolution is the operation with the clean mathematical properties (associativity, commutativity, and a direct correspondence to multiplication in the frequency domain, previewed in The Fourier View of Images), while correlation is what most image-processing libraries actually implement by default because it is more intuitive to specify ("the kernel is the pattern you're matching"). Two properties make this operation worth an entire formal theory rather than being a mere trick. Linearity means filtering a sum of images equals the sum of filtering each image, and scaling an image before filtering scales the output identically: T(αf1+βf2)=αT(f1)+βT(f2)T(\alpha f_1 + \beta f_2) = \alpha T(f_1) + \beta T(f_2). Shift invariance means sliding the input by (dx,dy)(dx,dy) before filtering produces exactly the same output slid by (dx,dy)(dx,dy) — the filter has no notion of "special" image locations. A system with both properties is called an LSI (linear shift-invariant) system, and a classical result of signal processing states that every LSI system is expressible as a convolution with some kernel — the kernel is the system's complete description, called its impulse response.

How it works

The mechanical procedure is the same for correlation and convolution up to the kernel flip, so the pseudocode below implements correlation and notes the one-line change needed for true convolution.

Step 1 — choose the kernel. A (2k+1)×(2k+1)(2k+1)\times(2k+1) array of weights, e.g. a 3×33\times3 box-blur kernel of all 19\tfrac19, or a Sobel derivative kernel.

Step 2 — decide the border policy. The kernel needs pixels outside the image whenever its center sits within kk pixels of an edge. Common choices: zero-padding (treat outside pixels as 0), replicate (clamp to nearest edge pixel), reflect (mirror across the edge), and wrap (treat the image as periodic/toroidal). Each yields a different — sometimes very different — output near the border.

Step 3 — slide and accumulate. For every output pixel, center the kernel there, multiply kernel weights by the (possibly border-extended) underlying pixels, and sum.

Step 4 — (optional) normalize / clip. If the kernel weights do not sum to 1 (e.g. a derivative kernel), the output may need rescaling for display, and values may need clipping into the valid intensity range. ``` function FILTER(image F, kernel H, mode): # mode ∈ {correlate, convolve} (W, H_img) = size(F) k = (size(H) - 1) / 2 # assumes H is (2k+1) x (2k+1) G = new image of size (W, H_img) for y in 0 .. H_img - 1: for x in 0 .. W - 1: sum = 0 for j in -k .. k: for i in -k .. k: if mode == convolve: (hi, hj) = (-i, -j) # flip kernel indices else: (hi, hj) = (i, j) (sx, sy) = BORDER(x - i, y - j, W, H_img) # or x+i,y+j; see note sum += H[hi + k][hj + k] * F[sx][sy] G[x][y] = sum return G function BORDER(sx, sy, W, H_img):

# zero-pad: caller instead returns 0 directly when sx,sy out of range

# replicate: sx = clamp(sx, 0, W - 1) sy = clamp(sy, 0, H_img - 1) return (sx, sy) ``` A production implementation exploits separability: if a 2-D kernel can be written as an outer product of two 1-D kernels, H=v uTH = v\,u^{T} (true of the box filter and the Gaussian filter), then filtering with HH is identical to filtering with uu along rows and then vv along columns: ``` function SEPARABLE_FILTER(image F, vector u, vector v):

# H = v * u^T (rank-1 kernel) Temp = FILTER_1D_HORIZONTAL(F, u) # convolve each row with u G = FILTER_1D_VERTICAL(Temp, v) # convolve each column with v return G ``` This is not an approximation — it is algebraically exact, because the 2-D sum factors:

∑i,jviuj f[x−i,y−j]=∑ivi(∑juj f[x−i,y−j]).\sum_{i,j} v_i u_j\, f[x-i,y-j] = \sum_i v_i \Big(\sum_j u_j\, f[x-i,y-j]\Big).
Figure
Sliding a 3×3 kernel over an image with zero-padding at the border
Input image (6×5), zero-padded border shown as 0 0000 0000 0482 9160 0375 2400 0619 3250 0804 6730 0251 8490 0000 0000 3×3 box kernel (weights 1/9 each) 1/91/91/9 1/91/91/9 1/91/91/9 Output at marked center (blue, value 9): sum = (7+5+2+1+9+3+0+4+6)/9 = 37/9 ≈ 4.11 Kernel center visits every pixel; near borders it reads the zero-padded ring. Red box = 3×3 neighborhood used for the highlighted output pixel (blue).

Complexity & correctness

Time complexity. For an image with N=W×HN = W \times H pixels and a (2k+1)×(2k+1)(2k{+}1)\times(2k{+}1) kernel, the direct (naive) algorithm visits every output pixel once and does O(k2)O(k^2) multiply-adds per pixel, giving

T(N,k)=O(N⋅k2).T(N,k) = O(N \cdot k^2).

This is tight for the direct method: every one of the k2k^2 kernel taps genuinely must be read and multiplied for a general (non-separable) kernel, so no direct algorithm can do fewer operations without extra structure. If the kernel is separable (H=vuTH = v u^{T}, rank 1), filtering decomposes into two 1-D passes, each doing O(k)O(k) work per pixel, for a total of

Tsep(N,k)=O(N⋅k)(two passes, each O(Nk)).T_{\text{sep}}(N,k) = O(N \cdot k) \quad\text{(two passes, each } O(Nk)).

This is a genuine asymptotic win — for a 15×1515\times15 kernel, k2=225k^2 = 225 versus 2k=302k = 30, roughly a 7.5× speedup — and it is exact, not approximate, because of the algebraic factoring shown above. A third route uses the convolution theorem: multiplying in the frequency domain via the FFT costs O(Nlog⁡N)O(N \log N) regardless of kk, which wins asymptotically once kk is large enough relative to log⁡N\log N; the crossover in practice is typically around kk in the dozens, since the FFT carries constant-factor and memory overhead that direct or separable filtering avoids for small kernels.

Space complexity. The direct and separable algorithms need O(N)O(N) space for the output image (plus O(N)O(N) for the intermediate pass in the separable case, or in-place with care), and O(k2)O(k^2) or O(k)O(k) for the kernel itself — negligible compared to the image. FFT-based filtering needs O(N)O(N) working space for the transformed arrays, with a larger constant due to complex-valued intermediate storage and (for linear, not circular, convolution) zero-padding to at least W+k−1W + k - 1 by H+k−1H + k - 1.

Correctness argument. The claim to justify is not an algorithmic subtlety but a mathematical identity: the sliding-window sum, as defined, is correlation or convolution by construction, so correctness reduces to showing the loop faithfully implements the sum. Three invariants guarantee this: 1. Index coverage. For each output location (x,y)(x,y), the inner double loop over i,j∈[−k,k]i, j \in [-k,k] visits every kernel tap exactly once, matching the summation range in the definition. 2. Correct addressing. For correlation the sample read is f[x+i,y+j]f[x+i, y+j]; for convolution it is f[x−i,y−j]f[x-i, y-j] — equivalently, correlation with the 180°-rotated kernel. Implementing the flip as `hi, hj = -i, -j` while still reading `F[x - i][y - j]` reproduces exactly the convolution sum; swapping only one of the two (kernel index or image index) but not both is the classic bug that silently turns convolution into correlation or vice versa. 3. Border consistency. Whatever border policy is chosen, it must be applied consistently to every out-of-range access, otherwise the boundary handling itself is not shift-invariant (a pixel near the top edge and an identical pixel pattern near the left edge should be treated by the same rule, just reflected in orientation). Given these invariants hold, the output at every pixel equals the defined sum exactly (up to floating-point rounding), so the algorithm is correct. Separability's correctness follows from the distributive-law factoring shown earlier — it is not a heuristic approximation, so the separable and direct algorithms are provably identical (up to floating-point associativity effects), not merely similar.

Honest tradeoffs. Larger kernels blur more but cost proportionally more time (O(k2)O(k^2)) unless separable. Zero-padding introduces a dark halo near edges (output pixels near the border are biased toward zero); replicate and reflect avoid that bias but can create artifacts of their own (replicate can smear an edge feature; reflect assumes local symmetry that may not exist). FFT-based convolution is asymptotically superior for large kernels but adds implementation complexity, a periodic-wraparound artifact if padding is insufficient, and higher constant overhead unsuitable for the small 3×33\times3–7×77\times7 kernels dominant in classical vision.

Worked example

Take a 5×55\times5 grayscale image (values 0–9, purely illustrative) and correlate it with the non-symmetric 3×33\times3 kernel

H=[10−120−210−1]H = \begin{bmatrix} 1 & 0 & -1 \\ 2 & 0 & -2 \\ 1 & 0 & -1 \end{bmatrix}

(the horizontal Sobel kernel, foreshadowing Image Gradients), using zero-padding, and compute one interior output pixel by hand to make the flip/no-flip distinction concrete. Image FF (rows top to bottom, columns left to right, 0-indexed):

c0c1c2c3c4
r022288
r122288
r222288
r322288
r422288

This is a vertical step edge (dark on the left, bright on the right) — exactly the pattern Sobel-x is designed to detect.

Correlation at pixel (r2,c2)(r2, c2). The 3×33\times3 neighborhood centered there (rows 1–3, cols 1–3):

N=[228228228]N = \begin{bmatrix} 2 & 2 & 8 \\ 2 & 2 & 8 \\ 2 & 2 & 8 \end{bmatrix}

Correlation multiplies elementwise with HH as written and sums:

gcorr=(1)(2)+(0)(2)+(−1)(8)+(2)(2)+(0)(2)+(−2)(8)+(1)(2)+(0)(2)+(−1)(8)g_{\text{corr}} = (1)(2)+(0)(2)+(-1)(8) + (2)(2)+(0)(2)+(-2)(8) + (1)(2)+(0)(2)+(-1)(8)
=(2−8)+(4−16)+(2−8)=−6−12−6=−24.= (2 - 8) + (4 - 16) + (2 - 8) = -6 -12 -6 = -24.

Convolution at the same pixel. First flip HH by 180° (reverse both rows and columns):

Hflip=[−101−202−101]H_{\text{flip}} = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix}

Now correlate NN with HflipH_{\text{flip}}:

gconv=(−1)(2)+(0)(2)+(1)(8)+(−2)(2)+(0)(2)+(2)(8)+(−1)(2)+(0)(2)+(1)(8)g_{\text{conv}} = (-1)(2)+(0)(2)+(1)(8) + (-2)(2)+(0)(2)+(2)(8) + (-1)(2)+(0)(2)+(1)(8)
=(−2+8)+(−4+16)+(−2+8)=6+12+6=24.= (-2+8)+(-4+16)+(-2+8) = 6 + 12 + 6 = 24.

So at this pixel correlation gives −24-24 and convolution gives +24+24 — exact sign flips, as expected for a kernel that is antisymmetric under 180° rotation. Both correctly signal "there is a strong vertical edge here"; the sign merely encodes which side is brighter, and a consistent choice (always convolution, or always correlation) must be used throughout a pipeline so signs stay comparable across the whole image.

Border pixel check. Now compute correlation at (r0,c0)(r0, c0), which needs zero-padded neighbors above and to the left:

Npadded=[000022022]N_{\text{padded}} = \begin{bmatrix} 0 & 0 & 0 \\ 0 & 2 & 2 \\ 0 & 2 & 2 \end{bmatrix}
gcorr=(1)(0)+(0)(0)+(−1)(0)+(2)(0)+(0)(2)+(−2)(2)+(1)(0)+(0)(2)+(−1)(2)g_{\text{corr}} = (1)(0)+(0)(0)+(-1)(0) + (2)(0)+(0)(2)+(-2)(2) + (1)(0)+(0)(2)+(-1)(2)
=0+(0−4)+(0−2)=−6.= 0 + (0 - 4) + (0 - 2) = -6.

Compare this to the true (unpadded) local pattern, which is uniformly 22 in the visible region — an interior pixel deep inside the left constant region gives exactly 00 (no edge), while this corner pixel gives −6-6 purely because zero-padding manufactured a fake dark region outside the image. This is precisely the border artifact discussed in Complexity & Correctness: it is not a bug in the loop, but a consequence of the chosen border policy, and a replicate border would have returned 00 here instead, matching the interior behavior.

Common pitfalls & traps

PitfallWhat goes wrongFix
Confusing correlation and convolutionAsymmetric kernels (Sobel, derivative masks) produce sign-flipped or mirrored results if the kernel isn't flipped when "convolution" is claimedDecide once which operation the pipeline uses and flip the kernel explicitly if convolution is required
Off-by-one in kernel indexingUsing `size(H)/2` with integer truncation on an even-sized kernel breaks the "center pixel" assumptionAlways require odd kernel dimensions, or handle even kernels with an explicit anchor point
Ignoring border policyComparing a filtered image's edge pixels to interior pixels as if both were equally reliableDocument the border mode; consider cropping the outer kk pixels before downstream analysis
Integer overflow / clippingSharpening or derivative kernels with negative weights produce negative or out-of-range sums, silently wrapping in fixed-point arithmeticAccumulate in floating point or wide integers, then clip/rescale for display
Treating separability as approximateRe-deriving a rank-1 kernel decomposition by trial and error, assuming loss of accuracyRecognize separability is an exact algebraic factoring — verify via SVD that the kernel matrix has rank 1
Forgetting kernel normalizationA blur kernel whose weights sum to more than 1 brightens the image; less than 1 darkens itNormalize kernel weights to sum to 1 for blur/averaging filters (not for derivative filters, whose weights should sum to 0)

Pioneers & history

The mathematics of convolution predates computer vision by roughly two centuries, developed for analyzing linear systems in physics and later electrical engineering; image filtering imported this apparatus wholesale once digital images and Digital Images and Sampling made it computable.

Origins · 1 minPierre-Simon Laplace and how this was discovered
PL
Pierre-Simon Laplace
1749–1827 · Celestial mechanics and probability theory, France

Laplace's work on integral transforms and linear operators, alongside contemporaries studying differential equations, laid mathematical groundwork later recognized as convolution — the same operator that, two centuries later, would slide across pixel grids to blur and sharpen digital photographs.

History
1822
— Joseph Fourier introduces the Fourier series, seeding the frequency-domain view that makes the convolution theorem meaningful.
1948
— Claude Shannon's sampling and information theory formalize the discrete signal framework that digital image filtering operates within.
1965
— Cooley and Tukey publish the Fast Fourier Transform algorithm, making frequency-domain convolution computationally practical.
1968
— Sobel and Feldman describe the Sobel edge-detection kernel at a Stanford AI Lab talk, a canonical asymmetric filter still taught today.
1998
— LeCun, Bottou, Bengio, and Haffner's LeNet-5 demonstrates learned convolutional kernels for digit recognition, connecting hand-designed filtering directly to trainable convolutional layers.

The throughline from Fourier's series to Sobel's edge kernel to LeCun's learned filters is the same sliding-window sum: only the source of the kernel weights changes, from analytic derivation, to manual engineering, to gradient-based learning.

Where this matters

Camera image signal processors (ISPs) apply small linear filters at every stage of the pipeline: demosaicing interpolation, noise reduction, and sharpening (an "unsharp mask," itself a linear filter combination) all run before an image is even displayed. Medical imaging systems filter CT and MRI slices to suppress acquisition noise while preserving diagnostic edges. Classical computer vision feature detectors — Sobel and Prewitt gradients, the Laplacian of Gaussian, the Harris corner detector — are built from a handful of small, fixed convolution kernels applied in sequence. In deep learning, the convolutional layer at the heart of every CNN (from LeNet to ResNet to modern vision transformer hybrids that still use convolutional stems) performs exactly the sliding weighted-sum operation described here, differing only in that the kernel weights are learned by gradient descent rather than hand-designed, and that many kernels (channels) are learned and combined at once. GPUs and specialized accelerators are built around fast convolution primitives (im2col + matrix multiply, Winograd convolution, FFT-based convolution) precisely because this operation is the dominant cost in both classical and learned vision pipelines. Video codecs and graphics pipelines use separable filters for fast resizing and blurring at real-time frame rates, directly exploiting the O(Nk)O(Nk) separable complexity argued above.

Ask the tutor: How does the convolution theorem let us compute large-kernel filtering faster using the FFT, and why does zero-padding size matter there?

Why do most software libraries call their filtering function "convolve" when it actually computes correlation?

Historically, many image-processing APIs prioritized the intuitive interpretation of a kernel as a literal pattern being matched against the image, which is exactly what correlation computes — no flip needed for the kernel to "look like" the feature you're searching for. True convolution's flip matters mathematically because it makes the operation commutative and associative and gives it the clean multiplication property in the frequency domain via the convolution theorem. For symmetric kernels the distinction is invisible, so many libraries never bothered to enforce the flip. The practical fix is to always check documentation and test with an asymmetric kernel (like Sobel) before trusting sign conventions in a new library.

Is a convolutional neural network layer literally the same operation described here?

Almost exactly, with two extensions. First, a CNN layer typically operates over multiple input channels simultaneously, summing contributions from a small 3-D kernel (height × width × input channels) rather than a single 2-D kernel. Second, most deep learning frameworks implement the correlation form (no kernel flip) despite calling it "convolution," which is mathematically harmless during training because the network simply learns whichever kernel orientation minimizes the loss. The sliding-window, weighted-sum arithmetic — and its complexity profile — is identical to classical linear filtering; only the source and dimensionality of the weights differ.

Why must the kernel usually have odd dimensions?

An odd-sized kernel, like 3×3 or 5×5, has a single unambiguous center pixel, so every output location aligns exactly with one input pixel. An even-sized kernel like 2×2 has no true center; its "center" sits between four pixels, forcing an arbitrary anchor convention (often rounding toward one corner) that can subtly shift the entire output image by half a pixel. Some specialized applications (certain downsampling filters) do use even kernels deliberately, but the anchor point must then be tracked explicitly, and most general-purpose filtering code simply requires odd sizes to avoid the ambiguity entirely.

How does separability relate to the rank of the kernel matrix?

A 2-D kernel HH can be viewed as a matrix; it is separable exactly when that matrix has rank 1, meaning it can be written as an outer product vuTv u^{T} of a column vector and a row vector. This can be checked via singular value decomposition: a rank-1 matrix has exactly one nonzero singular value. The Gaussian and box (mean) filters are separable by construction because their 2-D formula is itself a product of two independent 1-D formulas (one per axis). Non-separable kernels, such as many hand-tuned sharpening masks, require the full O(k2)O(k^2) per-pixel cost, though higher-rank kernels can sometimes be approximated by a sum of a few rank-1 terms for partial speedup.

What's the difference between "filtering" and the earlier point operations covered in prerequisite material?

A point operation computes each output pixel from that same single input pixel, using a fixed function such as a brightness curve or gamma correction — no neighborhood information is used at all. Linear filtering fundamentally requires a neighborhood: the output at a pixel depends on several nearby input pixels weighted by the kernel. This is why filtering can perform blurring, sharpening, and edge detection, none of which are achievable by any per-pixel remapping, since those tasks intrinsically require comparing a pixel to its surroundings.

Does the choice of border-handling policy actually change downstream results in a real pipeline?

Yes, measurably, especially for tasks reliant on edge or gradient information near image boundaries. Zero-padding manufactures a fake dark (or bright, depending on normalization) region outside the image, which can produce spurious gradient responses right at the border, as shown in the worked example. Replicate and reflect padding avoid manufacturing new intensities but can smear or falsely mirror real content. Many pipelines simply crop the outer kk pixels after filtering to sidestep the issue entirely, accepting a slightly smaller valid output region in exchange for guaranteed uncontaminated values everywhere that remains.

Why is the FFT-based approach not always used, given it's asymptotically faster for large kernels?

The FFT approach computes convolution in O(Nlog⁡N)O(N \log N) time independent of kernel size, which wins decisively for large kernels, but it carries real overhead: forward and inverse transforms, complex number arithmetic, and careful zero-padding to avoid circular-wraparound artifacts that would corrupt a linear convolution computed via a nominally periodic transform. For the small 3×3–7×7 kernels dominant in classical vision and early CNN layers, the constant factors of the direct or separable method are smaller in practice, so FFT convolution is reserved for genuinely large kernels or for signal-processing contexts (e.g., large blur radii, deconvolution) where its asymptotic advantage actually pays off.

Before this

Where this leads