Corner detector
A corner is an image location where the intensity changes significantly in multiple directions. Corners are stable, repeatable landmarks: unlike flat regions (no gradient) or edges (gradient in only one direction), a corner can be localized unambiguously in 2D, which makes it ideal for tracking and matching. The classic detector is the Harris corner detector.
Structure Tensor
The structure tensor (second-moment matrix) at a pixel, computed over a local window weighted by (often a Gaussian):
where and are image gradients (computed with Sobel operators). The eigenvalues of characterize the local structure:
| Interpretation | ||
|---|---|---|
| Flat region (no gradients) | ||
| Edge (gradient in one direction only) | ||
| Corner (gradients in both directions) |
The intuition: summarizes how the sum of squared intensity differences changes when the window is shifted. A corner is a point where any shift direction produces a large change — which is precisely the condition “both eigenvalues large”.
Corner Response Function
Rather than computing eigenvalues directly (expensive), Harris proposed the response:
with empirically. indicates a corner, an edge, and small a flat region. Non-maximum suppression then selects the strongest local maxima as the final corners.
The closely related Shi-Tomasi criterion (“Good Features to Track”) scores each pixel by directly — the worst-case trackability of the patch — and is what OpenCV’s cv::goodFeaturesToTrack implements.
A from-scratch sketch
Implementing Harris in a few lines of NumPy/OpenCV is one of the best beginner exercises:
import cv2, numpy as np
I = cv2.imread("frame.png", cv2.IMREAD_GRAYSCALE).astype(np.float32)
Ix = cv2.Sobel(I, cv2.CV_32F, 1, 0)
Iy = cv2.Sobel(I, cv2.CV_32F, 0, 1)
# window-averaged structure tensor entries
Sxx = cv2.GaussianBlur(Ix * Ix, (5, 5), 1.0)
Syy = cv2.GaussianBlur(Iy * Iy, (5, 5), 1.0)
Sxy = cv2.GaussianBlur(Ix * Iy, (5, 5), 1.0)
k = 0.04
R = (Sxx * Syy - Sxy**2) - k * (Sxx + Syy)**2 # Harris response
corners = R > 0.01 * R.max() # threshold before NMS
Practical notes for SLAM front-ends
- FAST, the detector behind ORB, replaces the structure tensor with a fast segment test on a circle of 16 pixels around the candidate — much cheaper, which is what makes real-time detection of thousands of keypoints feasible. Conceptually it still targets the same “intensity changes in multiple directions” property.
- Spatial distribution matters: raw response thresholds concentrate corners on highly textured regions. SLAM systems therefore bucket the image into a grid and keep the best corners per cell so that pose estimation is constrained in all image regions.
- Sub-pixel refinement (fitting a quadratic to the response around the maximum, e.g.
cv::cornerSubPix) noticeably improves downstream geometry.
Common pitfalls
- Window/blur size trade-off: too small a window makes the tensor noisy; too large blurs distinct corners together and displaces their locations.
- Threshold as a fraction of the max response adapts poorly across scenes; per-cell selection is more robust than a single global threshold.
- Corners on moving objects or specular highlights are geometrically useless; detection quality is only half the story — the rest is outlier rejection downstream.
Why it matters for SLAM
Corners are the raw material of feature-based SLAM front-ends: they become the keypoints that get described, matched, and triangulated into map points. The structure tensor reappears almost verbatim in Lucas-Kanade optical flow — a corner (both eigenvalues large) is exactly the kind of point that can be tracked reliably, which is why “good features to track” and Harris corners are so closely related. Later detectors used in real-time SLAM (FAST, ORB’s oriented FAST) are faster heirs of the same idea.