2D Convolution
Implement a 2D convolution operation to filter an image using a given kernel. This process is fundamental in Linear Filtering for image processing, where the goal is to modify or enhance the image by applying a kernel that slides over the entire image, performing an element-wise multiplication at each position.
The concept of 2D convolution is crucial in image processing as it allows for the application of various filters to achieve desired effects such as blurring, sharpening, or edge detection. Mathematically, the 2D convolution operation can be represented as a double summation over the kernel's dimensions.
- Initialize the output image with zeros.
- Slide the kernel over the input image, calculating the element-wise product at each position.
- Sum the products to obtain the output value at each position.
This technique is widely used in image processing applications.
Example:
image = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
kernel = [[1, 0],
[0, -1]][[-4, -4], [-4, -4]]
2D Convolution formula:
(IβK)[i,j]=βmββnβI[i+m,j+n]β K[m,n]
Valid convolution: Output size = (3β2+1)Γ(3β2+1)=2Γ2
-
Position (0,0): Top-left 2Γ2 region
- Image patch: (14β25β)
- Kernel: (10β0β1β) Sum=(1Γ1)+(2Γ0)+(4Γ0)+(5Γβ1)=1β5=β4
-
Position (0,1): Top-right 2Γ2 region Sum=(2Γ1)+(3Γ0)+(5Γ0)+(6Γβ1)=2β6=β4
-
Position (1,0): Bottom-left 2Γ2 region Sum=(4Γ1)+(5Γ0)+(7Γ0)+(8Γβ1)=4β8=β4
-
Position (1,1): Bottom-right 2Γ2 region Sum=(5Γ1)+(6Γ0)+(8Γ0)+(9Γβ1)=5β9=β4
Result: [[β4,β4],[β4,β4]]
This kernel computes the diagonal difference, detecting diagonal edges.
Constraints:
- Kernel is a small 2D array (typically 3Γ3 or 5Γ5)
- Use zero-padding
- Return result with same dimensions as input
- Background Knowledge
In image processing, a 2D convolution takes an input image I(x,y) and a small kernel/filter K(i,j), and produces an output image where each output pixel is a weighted sum of nearby input pixels. The weights come from the kernel, which can be designed to blur, sharpen, detect edges, etc. Mathematically, for discrete images, convolution is defined as (IβK)(x,y)=βiββjβI(xβi,yβj)K(i,j). Notice the indices: I is sampled at (xβi,yβj), so the kernel is effectively flipped in both directions compared to correlation.
Because the kernel extends beyond the image boundaries near the edges, you must decide what to assume βoutsideβ the image. This problem specifies zero-padding, meaning any pixel access outside valid indices is treated as 0. This leads to a full output image of the same size as the input, but with boundary effects (e.g., darker borders for some filters).
- Algorithm/Approach
The general pattern is:
- Treat the image as a 2D array I[h][w] and kernel as K[kh][kw].
- For each output pixel (x,y), compute a double sum over all kernel positions (i,j).
- Map kernel coordinates to image coordinates: (ix,iy)=(xβi,yβj) according to the given formula (or an equivalent but consistent indexing scheme).
- If (ix,iy) is outside the image, use 0 (zero-padding); otherwise, use I[iy][ix].
- Multiply and accumulate: sum += I[iy][ix] * K[i][j].
- Store sum into the output image at (x,y).
This is a straightforward nested-loop algorithm: two loops over output pixels, and two inner loops over kernel entries.
-
Step-by-Step Strategy
-
Get dimensions
- Let image size be H x W.
- Let kernel size be KH x KW.
- Option A (explicit padding array)
- Compute padding sizes, e.g.
- pad_h = KH - 1, pad_w = KW - 1 if following the exact formula with full flip, or
- often pad_h = KH // 2, pad_w = KW // 2 if using the common centered form.
- Create a new array P of size (H + 2pad_h) x (W + 2pad_w) initialized to 0.
- Copy I into the center of P.
- Option B (on-the-fly bounds checks)
- Skip creating a padded array and, whenever you compute an index into I, check if it is within [0, H) and [0, W); if not, treat as 0.
- Main convolution loop (conceptual form) Pseudocode (on-the-fly bounds check version):
out = [[0.0 for _ in range(W)] for _ in range(H)]
for y in range(H):
for x in range(W):
s = 0.0
for i in range(KH):
for j in range(KW):
ix = x - i
iy = y - j
if 0 <= ix < W and 0 <= iy < H:
s += I[iy][ix] * K[i][j]
# else: add 0
out[y][x] = s
This matches the given formula directly. You can also re-index to center the kernel differently as long as youβre consistent.
- Return / output
- Return the out image with the same size H x W.
- Common Pitfalls
- Confusing convolution with correlation: Convolution flips the kernel; correlation does not. Check your indexing carefully against the provided formula.
- Wrong center / offset: Misaligning how kernel indices map to image coordinates can shift the whole output or produce rotated results.
- Boundary handling mistakes:
- Forgetting zero-padding and reading garbage/out-of-bounds memory.
- Accidentally using βvalidβ (smaller output) instead of βsameβ (same size) behavior.
- Integer overflow / precision: If the image is integer and kernel has large values, sums can overflow smaller integer types; often you should accumulate in a larger or floating type.
- Kernel orientation: If you visually designed a kernel (e.g., Sobel), but you implement correlation instead of convolution, you may get edges in the opposite direction.
- Time & Space Complexity
-
Time complexity: For each of the HΓW output pixels, you compute a sum over all KHΓKW kernel elements. T=O(Hβ Wβ KHβ KW).
-
Space complexity:
-
Output image of size HΓW: O(HW).
-
Optional padded image adds O((H+2β padhβ)(W+2β padwβ)), still O(HW) asymptotically. Overall: S=O(HW).