Lucas-Kanade Optical Flow
Lucas-Kanade tracks a point by assuming its whole neighborhood moves together, turning many shaky single-pixel guesses into one steady motion vector.
Why Does This Exist?
Optical flow starts from one equation with two unknowns: , where are the image gradients, is the change between frames, and is the motion you want. One pixel cannot determine its own motion; that is the aperture problem. You need an extra assumption from somewhere.
Lucas and Kanade (1981) supplied the most practical one: motion is constant inside a small window. A patch gives 25 equations for the same 2 unknowns, and least squares finds the vector that satisfies them best. Trackers use it on corners and keypoints because textured windows constrain the answer while blank walls do not. It is sparse flow: fast, local, and the backbone of point tracking pipelines.
Think of It Like This
Twenty witnesses, one getaway car
One witness saw a blur and guesses the car went north. Useless alone. But twenty witnesses around the same intersection each caught a fragment, and all fragments agree the car went northeast at speed. You trust the consensus, not any single account.
Each pixel in the window is a witness with one equation. Least squares is the detective combining them: the shared that contradicts the fewest accounts wins. And just like witnesses, blank-wall pixels saw nothing and contribute no information, which the math reveals as an unsolvable system.
How It Actually Works
1. Stack the window into a system
For every pixel in the window, write its constraint . Stack them into with one row per pixel. The least-squares answer is , where is a matrix summarizing the window's texture.
2. Worked example
Three pixels give gradients with values . Then and . Solving: determinant , , . The window moves pixels per frame. Check pixel two: . Exact.
3. Good features to track
The solve needs invertible with large eigenvalues, which happens exactly at corners and textured spots. Shi-Tomasi scoring keeps windows whose smaller eigenvalue clears a threshold and drops edges and flat regions. Pyramids extend the range: solve coarsely at low resolution, refine upward, so large jumps become small ones per level.
Code
import numpy as np
A = np.array([[2, 0], [2, 1], [0, 2]], dtype=float)b = np.array([4, 5, 2], dtype=float)u, v = np.linalg.solve(A.T @ A, A.T @ b)print(u, v) # -> 2.0 1.0Watch Out For
Big jumps break the linearization
The constraint equation assumes motion under a couple of pixels; a fast object moves ten and the gradients no longer describe the change. The fix is a coarse-to-fine pyramid, never a bigger window alone. If tracks die on fast motion but survive slow scenes, add pyramid levels.
Flat windows lie confidently
On blank walls is near-singular and the inverse explodes or returns noise dressed as motion. Always gate on texture (Shi-Tomasi score) before trusting a vector. Horn-Schunck handles blank regions differently, by filling motion in from neighbors.
The Quick Version
- Lucas-Kanade assumes one shared motion vector per small window, giving many equations for 2 unknowns.
- Least squares solves ; the example window moves pixels.
- Only textured windows (corners) constrain the answer; flat regions are unsolvable.
- Pyramids extend it to large motions by solving coarse-to-fine.
- Sparse and fast: the standard choice for tracking points, not full scenes.