LambdaLabTM
Artificial Intelligence · Class 10 · Practical File · Program 11 of 15
Computer VisionFrom the CBSE AI handbook⏱️ 6 min read

Program 11: The Convolution Operator

Write a program to perform convolution of an image array with a kernel — the worked example from the CBSE AI handbook — and apply an edge-detecting kernel to a photo.

1Aim

Write a program to perform convolution of an image array with a kernel — the worked example from the CBSE AI handbook — and apply an edge-detecting kernel to a photo.
Python 3NumPyOpenCVMatplotlib⬇ photo.jpg

Keep photo.jpg in the same folder as the program.

2Program

convolution.py
# Program 11: The convolution operator
# Part 1 works the example from the CBSE AI handbook: a 5 x 5 image array
# and a 3 x 3 kernel. The edges of the image are extended with zeros, so
# the result is the same size as the image.
# Part 2 applies an edge-finding kernel to a real photo.

import numpy as np
import cv2
import matplotlib.pyplot as plt

image = np.array([[17, 24,  1,  8, 15],
                  [23,  5,  7, 14, 16],
                  [ 4,  6, 13, 20, 22],
                  [10, 12, 19, 21,  3],
                  [11, 18, 25,  2,  9]])

# The kernel, already rotated as in the handbook.
kernel = np.array([[2, 9, 4],
                   [7, 5, 3],
                   [6, 1, 8]])

# Extend every edge by one pixel of value 0.
padded = np.pad(image, 1)

result = np.zeros((5, 5), dtype=int)
for r in range(5):
    for c in range(5):
        # The 3 x 3 patch of the image under the kernel, centred on (r, c).
        patch = padded[r:r + 3, c:c + 3]
        # Multiply matching numbers and add all nine products.
        result[r, c] = (patch * kernel).sum()

print("Image array I:")
print(image)
print()
print("Kernel K:")
print(kernel)
print()
print("First value, worked out by hand:")
print("  centre 5 x 17 + right 3 x 24 + below 1 x 23 + below-right 8 x 5")
print("  =", 5 * 17, "+", 3 * 24, "+", 1 * 23, "+", 8 * 5, "=", 5 * 17 + 3 * 24 + 1 * 23 + 8 * 5)
print()
print("Result I * K:")
print(result)
print()

# Check: OpenCV's filter2D slides a kernel the same way.
check = cv2.filter2D(image.astype(np.float32), -1, kernel.astype(np.float32),
                     borderType=cv2.BORDER_CONSTANT)
print("Same answer from OpenCV filter2D :", np.array_equal(result, check.astype(int)))

# Part 2: an edge kernel on the photo.
grey = cv2.imread("photo.jpg", cv2.IMREAD_GRAYSCALE)
edge_kernel = np.array([[-1, -1, -1],
                        [-1,  8, -1],
                        [-1, -1, -1]], dtype=np.float32)
edges = cv2.filter2D(grey.astype(np.float32), -1, edge_kernel)

plt.figure(figsize=(8, 4.5))
plt.subplot(1, 2, 1)
plt.imshow(grey, cmap="gray")
plt.title("Grayscale photo")
plt.axis("off")
plt.subplot(1, 2, 2)
plt.imshow(np.abs(edges), cmap="gray", vmin=0, vmax=120)
plt.title("After the edge kernel")
plt.axis("off")
plt.show()

3Output

The output of a real run, exactly as it appeared.

Output
Image array I:
[[17 24  1  8 15]
 [23  5  7 14 16]
 [ 4  6 13 20 22]
 [10 12 19 21  3]
 [11 18 25  2  9]]

Kernel K:
[[2 9 4]
 [7 5 3]
 [6 1 8]]

First value, worked out by hand:
  centre 5 x 17 + right 3 x 24 + below 1 x 23 + below-right 8 x 5
  = 85 + 72 + 23 + 40 = 220

Result I * K:
[[220 441 346 276 231]
 [431 595 410 575 471]
 [371 440 555 620 551]
 [301 585 600 765 421]
 [247 446 536 451 128]]

Same answer from OpenCV filter2D : True
Output figure 1 of program 11
the window opened by plt.show()

4How it works

np.pad(image, 1)

Extends every edge by one pixel of value 0, so the kernel can be centred on the edge pixels and the result stays 5 × 5 — as the handbook describes.

patch = padded[r:r + 3, c:c + 3]

The 3 × 3 part of the image under the kernel, centred on position (r, c).

(patch * kernel).sum()

Multiplies each of the 9 pixels by the matching kernel number and adds the 9 results — one value of the output.

cv2.filter2D(...)

OpenCV's built-in function slides a kernel the same way. The program checks that both answers agree.

edge_kernel

−1 all round and 8 in the centre: on a smooth area the values cancel to 0; at an edge they do not, so edges light up.

Note
The handbook's image array is the 5 × 5 magic square and its kernel is the 3 × 3 magic square rotated, so the answer can be checked against any standard 2D convolution. The result was also checked with SciPy's convolve2d using the unrotated kernel: identical.

5Try this

  • Work out the second value of the result (441) by hand and check it.
  • Use np.ones((3, 3)) / 9 as the kernel on the photo. What does it do?
  • Run the convolution without padding. What size is the result?

6Viva questions

▸What is a kernel?

A small matrix of numbers that is slid across an image; at each position, the overlapping values are multiplied and added.

▸Why are the edges padded with zeros?

So the kernel can be centred on the edge pixels and the output image is the same size as the input.

▸What is the first value of the result, and how is it found?

220: 5 × 17 + 3 × 24 + 1 × 23 + 8 × 5, because the other kernel cells lie on the zero padding.

Tip
In your practical file, write the aim, the program and the output for each program — run it yourself and copy your own output. See The Practical File for how to set the file out.