Harris Corner Detector Finds Stable Points
Harris turns every pixel into a corner score from local gradients, so tracking keeps points that stay put when the camera shifts.
Why Does This Exist?
Tracking and stitching need points that can be found again. Flat patches all look alike, and edges slide along their own direction, so neither survives a viewpoint change. Corners lock in two directions at once, which makes them the only local features stable under small camera motion. Harris gave this a cheap test that uses only first derivatives in a small window, with no eigenvalue solver and one response value per pixel. Read the overview in corner and keypoint detection first, then come back for the exact score; when scale also changes, move up to scale invariant features.
Think of It Like This
Tent stakes in the wind
Picture a tent stretched on a lawn. Push the middle of a flat sheet and you cannot tell where you pushed. Push along a seam and the fabric slides sideways. Pull the corner stake and the whole shape answers at once, because the stake constrains every direction.
Harris looks for tent stakes in the image. Flat means no pull in any direction, edge means pull in one direction, corner means pull in every direction. Where it stops: pixels do not stretch like fabric, so only gradient energy counts and a zoomed view breaks the verdict.
How It Actually Works
Start with horizontal and vertical derivatives and at each pixel, usually from Sobel filters, then form the structure tensor over a Gaussian window around each pixel:
summarizes gradient energy in the neighborhood. Let its eigenvalues be and : both small means flat, one large and one small means edge, both large means corner. Eigenvalues cost too much per pixel, so Harris uses the determinant and trace shortcut with near :
Here and . Threshold and keep local maxima through non-maximum suppression. Harris is rotation invariant because rotating the image rotates both eigenvectors together, but it is not scale invariant since a zoom turns the corner into a curve.
Worked example
Take a window where , , and . Then and with . With , the penalty is and , a large positive value that marks a corner. An edge window with sums , , and gives , with , penalty , and , so it is rejected.
Code
The point of this snippet is the response shortcut, not the filtering: it reproduces the 17.25 corner and the minus 0.17 edge the prose derived by hand.
def harris_r(sxx, syy, sxy, k=0.04): det = sxx * syy - sxy * sxy trace = sxx + syy return det - k * trace * trace
print(round(harris_r(5.0, 4.2, 0.6), 2))# -> 17.25print(round(harris_r(5.0, 0.2, 0.3), 2))# -> -0.17Positive large means corner, negative means edge, near zero means flat. Real pipelines feed these scores into thresholding plus local maxima.
Watch Out For
Threshold picked on one image
A threshold that looks clean on a textured poster fires everywhere on gravel and nowhere on soft faces. The symptom is corner counts that swing by orders of magnitude across lighting. Tune and the threshold on your own contrast range, then add non-maximum suppression, since a raw threshold without local maxima gives clusters instead of points.
Expecting scale invariance
Harris has no pyramid, so matching a close-up against a wide shot fails. The symptom is tracking that dies the moment the camera dollies in. Fix it by moving to scale invariant features or a pyramid detector instead of raising the threshold.
The Quick Version
- Harris builds a structure tensor from and in each window.
- Response avoids an eigenvalue solver.
- The fixture scores for the corner and for the edge.
- Rotation invariant but scale sensitive, so it suits tracking at fixed distance.
- Threshold plus non-maximum suppression turns scores into usable points.