Understand the L2 norm as a measure of image energy, how normalisation removes brightness, and why cosine similarity handles contrast scaling but fails on brightness offsets.
31.2 L2 norm — vector length
The L2 norm is the length of a vector — the distance from the origin to the point. For an image patch, it measures the overall energy or magnitude of the pixel values.
A bright image has a large L2 norm. A dark image has a small L2 norm. Two images with the same pattern but different brightness have different L2 norms.
The squared L2 norm is the dot product of a vector with itself:
\|\vec v\|^2 \;=\; \vec v \cdot \vec v \;=\; \sum v_i^2
This is why the L2 norm is also called the energy — total “power” in the pixel values.
import numpy as npimport matplotlib.pyplot as pltCOLORS = {"primary": "#2196F3","secondary": "#4CAF50","result": "#FFC107","highlight": "#F44336","transform": "#9C27B0",}bright = np.array([200, 220, 240], dtype=float)dim = np.array([ 50, 70, 90], dtype=float)print(f"||bright|| = {np.linalg.norm(bright):.2f}")print(f"||dim|| = {np.linalg.norm(dim):.2f}")print(f"Same pattern, different brightness → different L2 norms.")
||bright|| = 382.10
||dim|| = 124.50
Same pattern, different brightness → different L2 norms.
31.3 Unit vectors — normalisation removes brightness
A unit vector has length 1. Dividing any vector by its L2 norm gives a unit vector pointing in the same direction:
\hat v \;=\; \frac{\vec v}{\|\vec v\|}
For image patches, this operation removes brightness (the length) and keeps only the pattern (the direction). Two patches with the same pattern but different brightness produce the same unit vector.
All normalised patches lie on the unit sphere. Patches with the same pattern map to the same point on that sphere regardless of brightness.
bright (raw) : [200. 220. 240.]
bright (unit) : [0.5234 0.5758 0.6281]
dim (raw) : [50. 70. 90.]
dim (unit) : [0.4016 0.5623 0.7229]
Are unit versions equal? False
(They aren’t quite equal — bright and dim aren’t true constant-multiples of each other. They’re close in pattern but not identical. Picking two perfectly-scaled patches would make the unit-vector equality exact.)
31.4 Cosine similarity
From the geometric definition of the dot product:
\vec a \cdot \vec b \;=\; \|\vec a\|\,\|\vec b\|\,\cos\theta
Rearranging:
\cos\theta \;=\; \frac{\vec a \cdot \vec b}{\|\vec a\|\,\|\vec b\|}
This is the cosine similarity — the dot product of the two unit vectors. It measures the angle between them, ignoring their lengths (brightness).
Cosine similarity handles contrast scaling (a \cdot T) perfectly because scaling changes the vector length but not its direction.
31.5 The blind spot — brightness offset
A brightness offset (T + b) shifts every pixel by the same constant. This pushes the vector toward the [1, 1, \ldots, 1] direction, changing the angle — so cosine similarity gives the wrong answer.
shifted = template +50# brightness offsetprint(f"cos(template, template + 50) = {cosine_similarity(template, shifted):+.4f}")print(f"(Even though only brightness changed, similarity dropped from 1.0)")
cos(template, template + 50) = +0.9939
(Even though only brightness changed, similarity dropped from 1.0)
This is why TM_SQDIFF_NORMED fails on scenes with illumination offsets. Fixing it requires mean subtraction — covered in the next chapter.