Skillversitas

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

NeedDetector
Tracking points frame to framegoodFeaturesToTrack
Fast matching on embedded / real-timeORB
Corners only, maximum speedFAST
Heavy viewpoint / scale change, quality firstSIFT / AKAZE
Whole-image corner mapcornerHarris

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.