Corner and Keypoint Detection
Feature detection finds the points in an image that are stable, distinctive, and repeatable - corners, blobs, and the keypoint detectors (ORB, FAST, SIFT) that modern matching is built on. A good feature has the property that the same physical point, seen from a slightly different angle or scale, is detected again at the same location - that repeatability is what makes matching across two images possible. OpenCV ships both the classic corner detectors (GoodFeaturesToTrack, Harris) and the modern detector family, all with a common interface.
Corners: GoodFeaturesToTrack and Harris
import cv2
img = cv2.imread("photo.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Shi-Tomasi: the top-N corners by corner response
corners = cv2.goodFeaturesToTrack(gray, maxCorners=100,
qualityLevel=0.01, minDistance=10)
corners = corners.reshape(-1, 2) if corners is not None else []
overlay = img.copy()
for (x, y) in corners:
cv2.circle(overlay, (int(x), int(y)), 4, (0, 0, 255), -1)
cv2.imwrite("corners.jpg", overlay)
# Harris: a corner response map over the whole image
dst = cv2.cornerHarris(gray, blockSize=5, ksize=3, k=0.04)
mask = (dst > 0.01 * dst.max()).astype("uint8") * 255
# the same detector as a filter
shitomasi = cv2.cornerMinEigenVal(gray, ksize=3)
goodFeaturesToTrack (Shi-Tomasi) returns the maxCorners strongest corner points, subject to a qualityLevel (a fraction of the best response - lower = more corners) and a minDistance to avoid clustering. It is the default for point tracking and the seed for optical flow. cornerHarris instead produces a full response image you threshold yourself - useful when you want a corner map rather than a list. Both work on grayscale; both measure "does intensity change in all directions," which is what makes a corner a corner versus a flat region or an edge.
Modern KeyPoints: ORB, FAST, SIFT
import cv2
img = cv2.imread("photo.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# ORB: rotation-scale tolerant, fast, binary descriptors
orb = cv2.ORB_create(nfeatures=500)
kp, des = orb.detectAndCompute(gray, None)
print("ORB points:", len(kp))
# FAST: corners only, no descriptors - pair it with a descriptor
fast = cv2.FAST_create(threshold=20, nonmaxSuppression=True)
fast_kp = fast.detect(gray)
# SIFT/_SURF in contrib; SIFT is in the main package since 4.4
sift = cv2.SIFT_create()
sift_kp, sift_des = sift.detectAndCompute(gray, None)
vis = cv2.drawKeypoints(img, kp, None, (0, 255, 0),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imwrite("orb.jpg", vis)
The modern detectors share one interface: detectAndCompute(image, mask) returns keypoints and an optional descriptor matrix. ORB is the workhorse - fast, rotation- and scale-tolerant, with short binary descriptors that are cheap to match (XOR + popcount). FAST is corner detection only (no descriptors), the fastest of all. SIFT (and its cousins SURF, AKAZE) is the quality benchmark - larger float descriptors, stronger under heavy viewpoint change. The keypoint object itself carries the position, the scale, the orientation, and the response strength - all of which you can inspect and filter on.
Drawing and Filtering KeyPoints
import cv2
img = cv2.imread("photo.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
orb = cv2.ORB_create(nfeatures=1000)
kp, _ = orb.detectAndCompute(gray, None)
# filter by response strength: keep only the confident detections
strong = [k for k in kp if k.score > 10]
vis = cv2.drawKeypoints(img, strong, None, (0, 255, 0),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imwrite("filtered.jpg", vis)
drawKeypoints renders each keypoint as a circle sized and oriented by the detector's scale and angle - the rich flag draws the orientation tick. Filtering by kp.score (the response strength) is the standard way to reduce clutter: a detector can return thousands of weak points, and the top few hundred by score are almost always the useful set. The same filtering applies to descriptors downstream - weak points produce unreliable matches.
Choosing a Detector
| Need | Detector |
|---|---|
| Tracking points frame to frame | goodFeaturesToTrack |
| Fast matching on embedded / real-time | ORB |
| Corners only, maximum speed | FAST |
| Heavy viewpoint / scale change, quality first | SIFT / AKAZE |
| Whole-image corner map | cornerHarris |
The decision is speed versus invariance. ORB covers most real-time matching; SIFT-class detectors earn their cost when the two images differ a lot in scale or angle. All of them want a grayscale input and benefit from a light blur on noisy sources - the corner response is a second-derivative measure, exactly as noise-sensitive as the Laplacian from the edge chapter.
Summary
Detect corners with goodFeaturesToTrack (top-N by response, quality and min-distance controls) or a full cornerHarris map, and use the modern family - ORB for fast real-time matching, FAST for corners only, SIFT/AKAZE for heavy invariance - all through the detectAndCompute interface that returns keypoints plus descriptors. Filter by kp.score to keep only confident points, draw with drawKeypoints, and remember the requirement every detector shares: grayscale input, and the repeatability of the point is the whole value - a feature that is not detected again under change is not a feature, it is noise.