LECTURE 5 · PART I

Computer Vision

Images, Colour Spaces, Filters & Features

With practical implementation in OpenCV · from pixels to deep learning

Dr. Muhammad Kazim

Department of Intelligent Systems
University of Lahore

One road image as RGB, grayscale, LAB b* channel and Sobel edges

Learning outcomes

By the end of this lecture you should be able to:

  1. Explain how a computer stores an image: pixels, uint8 values 0–255 and H × W × C arrays (tensors).
  2. Convert between colour spaces (RGB/BGR, HSV, LAB, YCbCr) and pick the right one for a task.
  3. Explain convolution with a kernel and compute an averaging filter by hand.
  4. Choose between averaging, Gaussian and median blur for a given type of noise.
  5. Find edges (Sobel, Laplacian, Canny), contours and simple shapes with OpenCV.
  6. Explain features (keypoints + descriptors), match them with ORB, and contrast hand-crafted with learned (deep) features.

Roadmap: from pixels to deep learning

① Seeinghuman vs machine
→
② Pixelsimages are numbers
→
③ Colourcolour spaces
→
④ Filterskernels & blur
→
⑤ Edgesshapes & contours
→
⑥ Featureskeypoints & matching
→
⑦ Deep learninglearned features, ViT
An image patch as numbers

② every pixel is a number

Edge detectors

④–⑤ filters reveal edges and shapes

ORB feature matching

⑥ features find the same object again

01

Human vs computer vision

We see a mountain. What does the computer see?

Natural vs artificial intelligence

Human natural intelligence vs computer artificial intelligence

Natural intelligence

  • Biological — present from birth
  • Understands, imagines, feels
  • Learns from a few experiences

Artificial intelligence

  • Built by humans
  • Follows algorithms (programs)
  • Learns from lots of data

Computer vision is the part of AI that turns pixels into understanding: what is in the image, where it is, and what is happening.

Human vision vs computer vision

Human vision sees a mountain ridge; computer vision sees data

You see a green ridge, mist and mountains — instantly and without effort — your brain has learned to see over a lifetime.

The computer receives only a grid of numbers. Every task — classify, detect, segment, track — starts from those numbers.

02

Images are numbers

Pixels, intensity, colour channels and tensors

Zoom in: an image is made of pixels

Zooming into a mountain photo reveals square pixels
  • A digital image is a grid of tiny squares called pixels (picture elements).
  • Each pixel stores one value (grayscale) or three values (colour).
  • Resolution = width × height.
    Full HD: 1920 × 1080 = 2,073,600 pixels.
  • Zoom in far enough and the smooth mountain becomes visible squares.

Grayscale intensity: 0 to 255

Gray levels 0, 50, 100, 150, 200, 255

Why 0–255?

Each pixel uses 8 bits = 28 = 256 levels. 0 = black, 255 = white, everything else is a shade of gray. In NumPy the type is uint8.

Overflow trap. uint8 wraps around: np.uint8(250) + 10 → 4. OpenCV saturates instead: cv2.add gives 255. Convert to float32 before doing arithmetic.

A real image patch, as numbers

Grayscale road image with a small patch shown as pixel values

import cv2
img = cv2.imread("road.jpg", cv2.IMREAD_GRAYSCALE)
print(img.shape, img.dtype)   # (847, 1344) uint8
print(img[606, 452])          # row (y) first, then column (x)
patch = img[606:633:3, 452:494:3]

The red box covers road and lane paint. Road pixels are about 80; the paint jumps to about 200. A sudden jump like this is an edge — that is exactly what the filters in section 4 will find.

Colour images: three channels

Road image split into red, green and blue channels
Red, green and blue planes stacked
  • A colour pixel = (R, G, B), three 8-bit numbers.
  • Each channel is a grayscale image on its own.
  • The yellow line is bright in R and G but dark in B — yellow = red + green.
  • OpenCV stores channels in B, G, R order!

256 × 256 × 256 = 16,777,216 colours

16,777,216 colours

3 channels × 8 bits = 24-bit “true colour”, enough for more shades than the eye can tell apart.

How big is one frame?

1920 × 1080 × 3 bytes = 6,220,800 bytes ≈ 6.2 MB

Video at 30 fps → ≈ 187 MB every second, uncompressed.

That is why images and video are compressed (JPEG, H.264) — and why compression uses the YCbCr colour space you will meet shortly.

Images as tensors

Understanding images as tensors: grayscale grid, colour channels, 3D stack

A tensor is an n-dimensional array. Libraries disagree on the order of the axes:

LibraryShapeValues
OpenCV / NumPy(H, W, C), BGRuint8 0–255
Matplotlib / PIL(H, W, C), RGBuint8
PyTorch(N, C, H, W)float32 0–1

rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
t = torch.from_numpy(rgb)      # (H, W, 3)
t = t.permute(2, 0, 1) / 255.  # (3, H, W)
batch = t.unsqueeze(0)         # (1, 3, H, W)

Quiz: images as numbers

Question 1

img.shape returns (480, 640, 3). How wide is the image?

640 px. NumPy shape is (rows = height, columns = width, channels).

Question 2

How many bytes does one uncompressed 4K RGB frame (3840 × 2160) need?

24,883,200 bytes ≈ 24.9 MB = 3840 × 2160 × 3.

Question 3

A pixel reads (0, 255, 255) in OpenCV. What colour is it?

Yellow. OpenCV order is B, G, R → R = 255, G = 255, B = 0.

03

Colour spaces

One image, many ways to describe its colours

Same image, different descriptions

One mountain image shown in RGB, BGR, HSV, CMYK, LAB, Gray and YCrCb

A colour space is a coordinate system for colour. Converting changes the numbers, not the information.

We switch spaces to separate what we care about:

  • HSV / HLS — colour (hue) apart from brightness
  • LAB — distances match human perception
  • YCbCr — brightness apart from colour, for compression
  • Gray — one channel, when colour does not matter

hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)

OpenCV’s first gotcha: BGR, not RGB

The same image displayed correctly and with red and blue swapped

img = cv2.imread("road.jpg")  # B, G, R
plt.imshow(img)               # wrong colours

rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(rgb)               # correct

OpenCV uses BGR for historical reasons: it was the common byte order of camera drivers and Windows bitmaps when the library was written. Everything else (Matplotlib, PIL, PyTorch models) expects RGB. Convert once, at the boundary.

Every space, channel by channel

HSV, LAB and YCrCb channels of a road image

The same road in three colour spaces. Things to notice:

  • V, L* and Y all look like a grayscale photo — they hold the brightness.
  • H (hue) is flat across the whole road: hue ignores how bright things are.
  • S (saturation) makes the colourful yellow line stand out against the gray road.
  • b* and Cr/Cb separate yellow and blue: sky dark, yellow line bright.

Channels with a small range (H, a*, b*, Cr, Cb) are stretched to 0–255 for display.

HSV: hue, saturation, value

HSV cylinder with hue angle, saturation radius and value height
  • H — hue: the colour itself, an angle around the cylinder (red → yellow → green → blue → red).
  • S — saturation: how pure the colour is (0 = gray, max = vivid).
  • V — value: how bright it is (0 = black).
OpenCV scaling: H is 0–179 (degrees ÷ 2, so it fits in a uint8); S and V are 0–255.

Why it helps: a yellow line in shadow changes V a lot but H very little, so thresholding H is much more robust than thresholding RGB.

HSV in practice: select the lane paint

Original road, HSV mask of yellow and white, masked result

hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# bounds are (H, S, V)
yellow = cv2.inRange(hsv, (15, 80, 120), (35, 255, 255))
white  = cv2.inRange(hsv, (0, 0, 200), (179, 40, 255))
mask   = cv2.bitwise_or(yellow, white)
result = cv2.bitwise_and(img, img, mask=mask)

Look closely: the dry grass is yellow too, so colour alone is not enough. In Part II we add a region-of-interest mask to keep only the road.

Live demo

HSV threshold explorer

Selected: – of the pixels. H uses OpenCV’s 0–179 scale.

LAB: a colour space built around human perception

LAB colour sphere: L* white to black, a* green to red, b* blue to yellow
  • L* — lightness, black (0) → white (100)
  • a* — green (−) ↔ red (+)
  • b* — blue (−) ↔ yellow (+)

Perceptually uniform: the same distance in LAB looks like the same colour difference to a person (ΔE). Used for colour matching, printing quality checks and robust segmentation.

OpenCV 8-bit scaling: L* × 255/100, and a*, b* are shifted by +128 (so 128 = neutral gray).

Why it matters: finding a yellow line

Original, grayscale, HLS S and LAB b* channels of a road with a yellow line

Grayscale

Yellow paint ends up a similar gray to the road: low contrast, easy to miss in shadow.

HLS S channel

Saturated paint is bright, gray asphalt is dark — but the grass is bright too.

LAB b* channel

Yellow is large positive b*: the line pops out and the sky turns dark.

Application: lane finding with LAB

Lane area painted green on a highway, with curvature and centre offset

An advanced lane finder thresholds the LAB b* channel for yellow paint and the L* channel for white paint, fits curves to the lane pixels, and reports:

  • radius of curvature (here 365 m)
  • how far the car is from the lane centre

In Part II you build the simpler version step by step: colour selection → region mask → Canny → Hough lines.

YCbCr: brightness plus two colour differences

Y brightness, Cb blue difference, Cr red difference
  • Y (luma) = 0.299 R + 0.587 G + 0.114 B — the eye is most sensitive to green.
  • Cb = blue − Y, Cr = red − Y (colour differences). OpenCV order: Y, Cr, Cb.

Where it is used

  • JPEG and video: the eye sees less colour detail than brightness detail, so Cb and Cr are stored at lower resolution (4:2:0).
  • Skin detection: skin tones form a tight cluster in Cb–Cr.

CMYK: colour for printers

Subtractive mixing of cyan, magenta and yellow
  • Subtractive: inks absorb light. Cyan + magenta + yellow → (almost) black.
  • K = key (black) ink: a real black, cheaper and sharper text.
  • Screens emit light → RGB is additive. Paper reflects light → CMYK.

CMYK is a printing space. OpenCV has no CMYK conversion and you will rarely use it in a vision pipeline — but you should recognise it.

Colour spaces: when to use which

SpaceChannelsGood forOpenCV
RGB / BGRred, green, bluedisplay, storage, deep-learning inputcv2.COLOR_BGR2RGB
Grayintensityedges, features, speed (1 channel instead of 3)cv2.COLOR_BGR2GRAY
HSV / HLShue, saturation, value / lightnesscolour thresholding, robust to shadowscv2.COLOR_BGR2HSV · HLS
LABlightness, green–red, blue–yellowperceptual colour difference, yellow/blue separationcv2.COLOR_BGR2LAB
YCbCrluma, blue diff., red diff.compression (JPEG, video), skin detectioncv2.COLOR_BGR2YCrCb
CMYKcyan, magenta, yellow, keyprinting only—

Quiz: colour spaces

Question 1

Why is OpenCV’s hue range 0–179 and not 0–359?

An 8-bit channel holds at most 255, so the 360° hue angle is halved.

Question 2

You must find a yellow line that is partly in shadow. Which channels would you try?

H (+ S) in HSV/HLS, or b* in LAB — they describe colour separately from brightness.

Question 3

Why does JPEG convert images to YCbCr before compressing?

The eye sees fine detail in brightness (Y) more than in colour, so Cb and Cr can be stored at lower resolution.

04 · Lecture 5 · Part I · Image processing

Filters

Slide a small kernel over every pixel and replace it with a weighted sum of its neighbours. Same image, different kernel, very different result.

Original: the input image
Originalthe input image
000010000
identity
Gaussian blur: smooth noise & detail
Gaussian blursmooth noise & detail
121242121
÷ 16
Sharpen: boost edges & detail
Sharpenboost edges & detail
0−10−15−10−10
centre − neighbours
Median: remove salt-and-pepper noise
Medianremove salt-and-pepper noise
median
of 5×5
non-linear
Sobel edges: find where brightness jumps
Sobel edgesfind where brightness jumps
−101−202−101
x-gradient

Types of image filters

Blur, averaging, sharpen, contrast, edge detect, colour filter and median denoise on one landscape

Neighbourhood filters (this lecture) look at a pixel and its neighbours: blur, sharpen, edges, median.

Point operations look at one pixel at a time: contrast, brightness, colour tone (Lecture 3).

How a filter works: convolution

input image f

1010101010 1010101010 10101001010 1010101010 1010101010
⊗

kernel w (box, ÷ 9)

111111111
=

output g

    20    

(8 × 10 + 100) ÷ 9 = 180 ÷ 9 = 20. The bright spike is spread out and dimmed: that is blurring.

  1. Place the kernel over a pixel.
  2. Multiply each neighbour by the kernel weight under it.
  3. Add them up → the new value of the centre pixel.
  4. Slide to the next pixel and repeat — for every pixel.

\[ g(x,y) = \sum_{i=-k}^{k}\sum_{j=-k}^{k} w(i,j)\, f(x+i,\, y+j) \]

Strictly, this is correlation; convolution flips the kernel first. For symmetric kernels they are identical. At the borders OpenCV reflects the image (BORDER_REFLECT_101).


k = np.ones((3, 3), np.float32) / 9
out = cv2.filter2D(img, -1, k)  # any kernel

The averaging filter, worked by hand

A white cross of 255 on black zeros, 3x3 window on the centre

① a white cross (255) on black (0); 3 × 3 window on the centre

Adding the nine values gives 1275

② add the 9 values: 5 × 255 = 1275

1275 divided by 9 is 142

③ divide by 9: 1275 ÷ 9 = 141.7 → 142

The centre drops from 255 to 142, and the black pixels next to the line become gray. Repeat everywhere: the sharp cross becomes a soft, wider blur. Averaging removes noise — but also detail.

Kernel size matters

Box blur with 3x3, 9x9 and 21x21 kernels

for k in (3, 9, 21):
    out = cv2.blur(img, (k, k))     # k x k box filter
  • Bigger kernel → more smoothing, more detail lost.
  • Sizes are odd (3, 5, 7…) so there is a centre pixel.
  • Cost grows with k² — use the smallest size that works.

Gaussian blur: closer neighbours count more

Harbour photo: Gaussian blurred half and original half

Instead of equal weights, use a bell curve:

\[ G(x,y) = \frac{1}{2\pi\sigma^2}\, e^{-\frac{x^2+y^2}{2\sigma^2}} \]

121242121

3 × 3 Gaussian kernel, ÷ 16.
Centre weight 4, corners 1.

  • σ (sigma) controls the strength of the blur.
  • Smoother and more natural than a box filter, with no “blocky” artefacts.
  • Standard first step before edge detection.

out = cv2.GaussianBlur(img, (5, 5), 0)   # sigma 0 → from size

Median blur: take the middle value

Pixel grid with a white patch of 255 values; the 3x3 neighbourhood values sorted with the median highlighted
  1. Collect the 9 values in the 3 × 3 window.
  2. Sort them.
  3. Take the middle (5th) value: here 28.
  • Non-linear: no kernel weights, no sum.
  • One extreme value (a 255 speck) can never become the answer → perfect for salt-and-pepper noise.
  • Keeps edges sharp, because the output is always a value that really occurs in the window.

out = cv2.medianBlur(img, 5)   # kernel size must be odd

Which blur for which noise?

Gaussian noise and salt-and-pepper noise cleaned by averaging, Gaussian and median filters

Gaussian noise (every pixel a bit off) → Gaussian blur averages it away.

Salt & pepper (a few pixels completely wrong) → median ignores the outliers.

Live demo

Kernel playground: design your own filter

Left: input · right: output. Edit any number and the output updates. Try: what happens if the weights do not add up to the divisor?

Quiz: filters by hand

A 3 × 3 neighbourhood:

1012111325012101113

The centre 250 is a noise speck.

Quiz question

  1. What does a 3 × 3 averaging filter output for the centre?
  2. What does a 3 × 3 median filter output?
  3. Which one removed the speck?
① sum = 342 → 342 ÷ 9 = 38 (the speck is smeared into its neighbours)
② sorted: 10 10 11 11 12 12 13 13 250 → 12
③ the median — the outlier never reaches the middle.

05

Edges, shapes & contours

From brightness changes to object outlines

An edge is a big change in brightness

Remember the lane-line patch: 81, 79, 113, 168, 200. Edges are where the image changes fast — the derivative (gradient) is large.

Sobel x — finds vertical edges

−101−202−101

Sobel y — finds horizontal edges

−1−2−1000121

\[ |\nabla f| = \sqrt{G_x^2 + G_y^2}, \qquad \theta = \arctan\frac{G_y}{G_x} \]

Three classic edge detectors

  • Sobel — first derivative in x and y; gives strength and direction.
  • Laplacian — second derivative; finds edges in all directions at once, but very sensitive to noise.
  • Canny — blur → Sobel → thin the edges → keep strong edges plus weak ones connected to them. Clean, one-pixel-wide edges. (Full details in Part II.)

Edge detectors compared

Tower Bridge: original, Sobel x, Sobel y, magnitude, Laplacian and Canny

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (3, 3), 0)

sx  = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sy  = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
mag = cv2.magnitude(sx, sy)

lap = cv2.Laplacian(gray, cv2.CV_64F)
can = cv2.Canny(gray, 80, 180)

Use CV_64F, not uint8: a dark→bright edge is positive and bright→dark is negative; uint8 would clip it to 0 and lose half of the edges.

From edges to shapes

EXIT sign: original, Canny edges, dilated edges, detected octagon with 8 points

The classic shape pipeline:

gray + Gaussian blur
Canny edges
dilate — close small gaps
findContours — outlines
approxPolyDP — count corners

The EXIT sign has 8 corners → octagon. The trackbars set the two Canny thresholds and the minimum area.

Contours: classify a shape by counting its corners

Input shapes, binary mask and labelled contours with their corner counts

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, mask = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL,
                           cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
    peri   = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    corners = len(approx)        # 3 triangle, 4 square/rect, ...
    x, y, w, h = cv2.boundingRect(approx)

A contour is the list of boundary points of a white region in a binary mask.

  • approxPolyDP simplifies the outline to a polygon; ε = 2% of the perimeter.
  • 4 corners: square if w ≈ h, else rectangle.
  • Many corners → circle or ellipse.

Contours step by step

Six stages: original, blurred gray, Canny, dilated, contour with corner points, result

Top row → bottom row:

  1. original image
  2. gray + blur
  3. Canny edges
  4. dilated edges (gaps closed)
  5. contour with its corner points — small specks are also found
  6. final result: only contours larger than a minimum area are kept

Clean the mask before you count

Coloured objects, noisy mask, cleaned mask and 5 counted objects

k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN,  k)  # remove specks
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, k)  # fill holes
n = len(cv2.findContours(mask, cv2.RETR_EXTERNAL,
                         cv2.CHAIN_APPROX_SIMPLE)[0])

Without cleanup: 20 “objects” — every speck of noise and every split piece count as separate objects.

With morphology: 5 objects, the correct count. Opening removes specks; closing fills small holes.

Contour features: points and area

Three objects with green contours labelled with their number of points and area

for c in cnts:
    area = cv2.contourArea(c)
    if area < 1000:   # ignore noise
        continue
    peri   = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    cv2.drawContours(img, [c], -1, (0, 255, 0), 3)
    txt = f"Points: {len(approx)}  Area: {int(area)}"
    cv2.putText(img, txt, (x, y), font, 1, red, 2)

Area and corner count are simple features: numbers that describe an object. They work for clean shapes — but can they tell two different logos apart? →

Quiz: edges & shapes

Question 1

Why do we usually blur before detecting edges?

Derivatives amplify noise; a Gaussian blur removes it first, so we only get real edges.

Question 2

How many points should approxPolyDP return for a STOP sign?

8 — a STOP sign is an octagon (like the EXIT sign).

Question 3

Your object counter reports 37 objects for 5 coins. What is the most likely fix?

Clean the mask with morphological opening and ignore contours below a minimum area.

06

Feature extraction

When shapes are not enough

Shapes aren’t enough to find a specific logo

Hundreds of company logos

Hundreds of logos — most are circles, rectangles or words.

Shape detection finds a box around every logo

A shape detector boxes every logo — it cannot say which one is which.

To recognise one particular object we need distinctive local patterns — corners, textures, letter shapes — that appear on that object and nowhere else. These are features.

Shape-based vs feature-based detection

Graph rising from shape-based detection to feature-based detection
Shape-basedFeature-based
Looks atoutline, corners, areamany small, distinctive patches
Finds“a square”, “an octagon”“this logo”, “this face”
Rotation / scalefragilerobust (with the right descriptor)
Occlusionfails if the outline breaksworks with some features hidden

Features in action: face landmarks

Dense face landmarks on a smiling man Face landmarks on a father and child Face landmarks on a woman

Hundreds of landmarks — eye corners, lip contours, nose, jaw line — are found and tracked in real time (here at 32 fps). They work across poses, expressions, ages and skin tones, and drive face filters, driver-drowsiness alerts and avatar animation.

What is a feature? A keypoint plus a descriptor

Shi-Tomasi corners and ORB keypoints on Tower Bridge

① Keypoint — where

A point that can be found again: corners are good, edges are ambiguous, flat areas are useless. ORB also stores a size and an orientation.

② Descriptor — what

A vector that summarises the patch around the keypoint. ORB uses 256 bits; SIFT uses 128 numbers.

Good features are …

repeatable, distinctive, and invariant to rotation, scale and lighting.

Features describe the whole scene

Keypoints drawn as circles on a night image of Earth, and descriptors shown as coloured points

Each yellow circle is a keypoint; its size is the scale at which it was found. Below, every keypoint becomes a descriptor — a point in a high-dimensional space.

Classic detectors and descriptors:

  • Harris (1988) — corners
  • SIFT (2004) — scale and rotation invariant; free in OpenCV since its patent expired in 2020
  • ORB (2011) — fast, binary, free: the default choice on a Raspberry Pi

Matching features works like fingerprint matching

Fingerprint with marked points next to a database fingerprint

① find distinctive points (minutiae)

Lines connecting matching points of two fingerprints

② compare their descriptions with the database

100% match

③ enough consistent matches → same finger

Image matching does the same: compare descriptors, keep the nearest neighbours, and check that the matches agree on one geometric transformation.

ORB feature matching in OpenCV

ORB matches between Tower Bridge and a rotated, scaled copy

orb = cv2.ORB_create(1500)
k1, d1 = orb.detectAndCompute(gray1, None)
k2, d2 = orb.detectAndCompute(gray2, None)

bf   = cv2.BFMatcher(cv2.NORM_HAMMING)
good = [m for m, n in bf.knnMatch(d1, d2, k=2)
        if m.distance < 0.75 * n.distance]  # ratio test
H, inl = cv2.findHomography(pts1, pts2, cv2.RANSAC, 5.0)

The right image is rotated 25° and scaled to 75%. Still:

  • 1500 keypoints per image
  • 541 pass the ratio test
  • 534 agree on one transformation (RANSAC)

Classroom demo: matching survives rotation

ORB matches between a dog photo and a rotated copy

The same ORB pipeline on a photo and a rotated copy of it. Most green lines connect the same point on the dog — the ear, the eye, the nose.

A few lines cross the image: these are wrong matches (outliers). That is why we use the ratio test and RANSAC.

Applications: panorama stitching, visual odometry and SLAM, augmented reality, object instance recognition.

Hand-crafted vs learned features

Corners detected on a garden bench

Hand-crafted: a corner detector designed by people finds bench corners and leaf tips.

Feature activation map from a pretrained CNN overlaid on the bench image

Learned: a pretrained CNN’s feature map — the network decided for itself which patterns matter.

Hand-crafted features (Harris, SIFT, ORB) follow rules we wrote. Deep networks learn their features from millions of examples — which brings us to the last section.

Quiz: features

Question 1

Why is a corner a better keypoint than a point on a straight edge?

Along an edge every point looks the same (it can slide). A corner changes in every direction, so it can be located exactly.

Question 2

What is the difference between a keypoint and a descriptor?

Keypoint = where (x, y, scale, angle). Descriptor = what the patch looks like (a vector we can compare).

Question 3

Why do we use NORM_HAMMING to match ORB descriptors?

ORB descriptors are binary strings; Hamming distance (count of differing bits) is the right, very fast measure.

07

Deep-learning-based computer vision

Let the machine learn the features

From writing rules to learning them

Code next to a brain labelled Machine with a progress bar: machine is learning itself

Classical computer vision

A person chooses the colour space, the thresholds, the kernels and the features. Everything in sections 2–6 of this lecture.

Deep learning

We provide labelled examples; the network learns its own kernels and features by trial and error.

Performing a task without explicit programming

Perform a task without programming
A dataset of labelled images feeding a neural network

Machine learning is the “field of study that gives computers the ability to learn without being explicitly programmed” (definition often attributed to Arthur Samuel, 1959). For vision, the program is replaced by a dataset: thousands of images of cars, dogs, bicycles, STOP signs and faces, each with its label.

How a network learns: predict, compare, adjust

Four steps: network processes input, predicts dog (wrong), feedback adjusts weights, network predicts again
  1. Forward pass: the cat image flows through the layers.
  2. The network predicts “dog” — wrong.
  3. The loss measures the error; back-propagation sends it backwards and every weight is nudged a little.
  4. Predict again. Repeat over millions of images.
After training the network predicts cat correctly

After training: “CAT (correct!)”

Recognising handwritten digits: still just numbers

A handwritten 9 fed as 784 inputs into a network with 10 outputs
  • Input: a 28 × 28 grayscale image = 784 numbers (section 2!).
  • Hidden layers combine pixels into strokes, then strokes into loops and lines.
  • Output: 10 neurons, one per digit 0–9; the brightest wins — here “9”.

The link to today

A convolutional network (CNN) learns 3 × 3 kernels just like the ones in section 4. Its first layer typically ends up with Sobel-like edge detectors and blob detectors — learned, not designed.

Vision Transformer (ViT): an image as patches

A kitten image divided into a grid of patches; one patch becomes a grid of numbers

split the image into patches; each patch is a block of numbers

8 by 8 patches give 64 patches

8 × 8 grid → 64 patches

Transformers were built for words. ViT (Dosovitskiy et al., 2021, “An Image is Worth 16×16 Words”) treats each image patch as a word: flatten it, map it to a vector (a token), add its position, and let self-attention relate every patch to every other patch.

ViT: patches become numbers, numbers become tokens

Each patch shown as red, green and blue number grids

every patch = R, G, B numbers (section 2 again)

The patch grid as a sequence of tokens with an embedding vector

each patch → one embedding vector (token)

A 224 × 224 RGB image with 16 × 16 patches → 14 × 14 = 196 tokens, each made from 16 × 16 × 3 = 768 numbers.

Running filters backwards: from noise to an image

Random noise passes through a transformer and becomes a landscape image

In section 4 we removed noise. Generative models (diffusion models with transformer backbones) learn to do this so well that they can start from pure noise and remove it step by step, until an image appears that matches a text prompt.

Classical vs deep-learning computer vision

Classical (sections 2–6)Deep learning (section 7)
Featuresdesigned by people: colour thresholds, kernels, ORBlearned from data
Data neededa few images to tune parametersthousands to millions of labelled images
Computetiny — runs on a microcontrollera GPU to train; often an accelerator to run
Explainable?yes — every step can be inspectedharder — feature maps, attention maps
Robustnessbreaks when conditions change (light, weather)generalises well if the training data is diverse
Todaypre-processing, measurement, embedded systemsclassification, detection, segmentation, generation

Real systems use both: classical pre-processing (colour conversion, resizing, normalisation) feeds every deep network.

Key takeaways

② Images are numbers

H × W × C arrays of uint8 0–255. OpenCV uses BGR.

③ Colour spaces

Same information, different axes. HSV/LAB separate colour from brightness.

④ Filters

A kernel slides over every pixel. Gaussian for Gaussian noise, median for salt & pepper.

⑤ Edges & shapes

Edges = large gradients. Contours + approxPolyDP count corners.

⑥ Features

Keypoint (where) + descriptor (what). Match with ratio test + RANSAC.

⑦ Deep learning

Networks learn the kernels and features themselves. ViT turns patches into tokens.

OpenCV cheat sheet for Part I


img  = cv2.imread("img.jpg")        # BGR
rgb  = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hsv  = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower, upper)
out  = cv2.bitwise_and(img, img, mask=mask)

box  = cv2.blur(img, (5, 5))
gaus = cv2.GaussianBlur(img, (5, 5), 0)
med  = cv2.medianBlur(img, 5)
any_ = cv2.filter2D(img, -1, kernel)

sx   = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
lap  = cv2.Laplacian(gray, cv2.CV_64F)
edge = cv2.Canny(gray, 50, 150)

k    = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, k)
cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL,
                           cv2.CHAIN_APPROX_SIMPLE)
peri   = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)

orb = cv2.ORB_create()
kp, des = orb.detectAndCompute(gray, None)

Practice & resources

Practise in the labs (Colab-ready)

Your turn: take a photo of objects on your desk, count them with threshold → morphology → contours, then match one object between two photos with ORB.

Further reading

Questions?

Next: Part II — Computer Vision for Self-Driving Cars