Feature Detection
Feature Detection¶
Overview¶
Features are unique and repeatable points that can be detected in an image. These include corners, blobs, edge intersections, and more. They are used in image matching, object recognition, 3D reconstruction, and other applications. In this lesson, we will learn about various feature detection algorithms including Harris, FAST, SIFT, and ORB.
Table of Contents¶
- Feature Point Fundamentals
- Corner Detection - Harris
- Good Features to Track
- FAST Detector
- SIFT Detector
- ORB Detector
- Keypoints and Descriptors
- Practice Problems
1. Feature Point Fundamentals¶
What are Feature Points?¶
Feature Point / Keypoint:
A uniquely identifiable point in an image
Requirements for Good Features:
1. Repeatability: Same object should produce same features
2. Distinctiveness: Different features should be distinguishable
3. Invariance: Robust to rotation, scale, and lighting changes
4. Accuracy: Precise location detection
Types of Features:
+-------------------------------------------------------------+
| Corner Blob |
| |
| +------ ***** |
| | ******* |
| ---+ ******** |
| ******* |
| Change in two ***** |
| directions Specific region size |
+-------------------------------------------------------------+
Feature Detection Pipeline¶
1. Feature Detection
- Find keypoint locations in image
- Harris, FAST, SIFT, ORB, etc.
|
v
2. Feature Description
- Generate feature vector around each keypoint
- SIFT descriptor, ORB descriptor, BRIEF, etc.
|
v
3. Feature Matching
- Compare descriptors with other images
- BFMatcher, FLANN, etc.
Detector Comparison¶
+----------------+-----------+-----------+-----------+----------+
| Algorithm | Speed | Rotation | Scale | Patent |
| | | Invariant | Invariant | |
+----------------+-----------+-----------+-----------+----------+
| Harris | Fast | O | X | None |
| FAST | Very Fast | X | X | None |
| SIFT | Slow | O | O | Expired |
| SURF | Medium | O | O | Yes |
| ORB | Fast | O | O | None |
| AKAZE | Medium | O | O | None |
+----------------+-----------+-----------+-----------+----------+
2. Corner Detection - Harris¶
Concept¶
Harris Corner Detection:
Analyzes intensity changes when shifting an image patch
- Flat region: No change in any direction
- Edge: No change along edge direction, large change perpendicular
- Corner: Large change in all directions
Auto-correlation Matrix M:
M = sum [Ix^2 IxIy]
[IxIy Iy^2 ]
Ix, Iy: Derivatives in x, y directions
Corner Response Function:
R = det(M) - k * (trace(M))^2
R = lambda1*lambda2 - k(lambda1 + lambda2)^2
- R > threshold: Corner
- R ~ 0: Flat
- R < 0: Edge
cv2.cornerHarris()¶
import cv2
import numpy as np
def harris_corner_detection(image_path):
"""Harris corner detection"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)
# Harris corner detection
dst = cv2.cornerHarris(
gray,
blockSize=2, # Neighborhood size
ksize=3, # Sobel kernel size
k=0.04 # Harris parameter
)
# Dilate result (emphasize corners)
dst = cv2.dilate(dst, None)
# Mark corners above threshold
result = img.copy()
result[dst > 0.01 * dst.max()] = [0, 0, 255]
cv2.imshow('Harris Corners', result)
cv2.waitKey(0)
return dst
harris_corner_detection('chessboard.jpg')
Sub-pixel Accuracy¶
import cv2
import numpy as np
def harris_subpixel(image_path):
"""Harris corners with sub-pixel accuracy"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_float = np.float32(gray)
# Harris corners
dst = cv2.cornerHarris(gray_float, 2, 3, 0.04)
# Extract corner locations
dst = cv2.dilate(dst, None)
ret, dst_thresh = cv2.threshold(dst, 0.01 * dst.max(), 255, 0)
dst_thresh = np.uint8(dst_thresh)
# Find corner centroids using connected components
ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst_thresh)
# Refine to sub-pixel precision
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv2.cornerSubPix(
gray,
np.float32(centroids),
(5, 5), # Window size
(-1, -1), # Zero zone
criteria
)
result = img.copy()
for i, corner in enumerate(corners):
x, y = corner.ravel()
if i == 0: # First one is background
continue
cv2.circle(result, (int(x), int(y)), 5, (0, 255, 0), -1)
cv2.imshow('SubPixel Corners', result)
cv2.waitKey(0)
return corners
harris_subpixel('chessboard.jpg')
3. Good Features to Track¶
cv2.goodFeaturesToTrack()¶
Shi-Tomasi Corner Detection (Harris improvement):
R = min(lambda1, lambda2)
More stable corner detection than Harris
-> Suitable for optical flow tracking
import cv2
import numpy as np
def good_features_demo(image_path):
"""Good features detection"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Detect good features
corners = cv2.goodFeaturesToTrack(
gray,
maxCorners=100, # Maximum number of corners
qualityLevel=0.01, # Quality level (ratio of max response)
minDistance=10, # Minimum distance between corners
blockSize=3, # Neighborhood size
useHarrisDetector=False, # Use Shi-Tomasi
k=0.04 # Harris parameter (when using Harris)
)
result = img.copy()
if corners is not None:
corners = np.int_(corners)
for corner in corners:
x, y = corner.ravel()
cv2.circle(result, (x, y), 5, (0, 255, 0), -1)
print(f"Detected corners: {len(corners)}")
cv2.imshow('Good Features', result)
cv2.waitKey(0)
return corners
good_features_demo('building.jpg')
Using Mask to Restrict Region¶
import cv2
import numpy as np
def features_with_mask(image_path):
"""Detect features only in specific region"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
h, w = gray.shape
# Create ROI mask (center region only)
mask = np.zeros((h, w), dtype=np.uint8)
cv2.rectangle(mask, (w//4, h//4), (3*w//4, 3*h//4), 255, -1)
# Detect features only in masked region
corners = cv2.goodFeaturesToTrack(
gray,
maxCorners=50,
qualityLevel=0.01,
minDistance=10,
mask=mask
)
result = img.copy()
# Show mask region
cv2.rectangle(result, (w//4, h//4), (3*w//4, 3*h//4), (128, 128, 128), 2)
if corners is not None:
for corner in corners:
x, y = corner.ravel()
cv2.circle(result, (int(x), int(y)), 5, (0, 255, 0), -1)
cv2.imshow('Features with Mask', result)
cv2.waitKey(0)
features_with_mask('scene.jpg')
4. FAST Detector¶
Concept¶
FAST (Features from Accelerated Segment Test):
Very fast corner detection algorithm
Principle:
Examine circular pattern (16 pixels) around center pixel P
1 2 3
16 4
15 5
14 P 6
13 7
12 8
11 10 9
Decision criterion (N=12):
- If N consecutive pixels are brighter than P: Corner
- If N consecutive pixels are darker than P: Corner
Characteristics:
- Very fast (real-time processing)
- No rotation invariance
- No scale invariance
- Non-maximum suppression (NMS) prevents multiple detections
cv2.FastFeatureDetector¶
import cv2
import numpy as np
def fast_detection(image_path):
"""FAST feature detection"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Create FAST detector
fast = cv2.FastFeatureDetector_create(
threshold=20, # Intensity threshold
nonmaxSuppression=True # Non-maximum suppression
)
# Detect features
keypoints = fast.detect(gray, None)
# Draw results
result = cv2.drawKeypoints(
img, keypoints, None,
color=(0, 255, 0),
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
print(f"Detected features: {len(keypoints)}")
cv2.imshow('FAST', result)
cv2.waitKey(0)
return keypoints
fast_detection('building.jpg')
Comparing FAST Thresholds¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
def compare_fast_thresholds(image_path):
"""Compare FAST thresholds"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresholds = [10, 20, 30, 50]
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes = axes.flatten()
for ax, thresh in zip(axes, thresholds):
fast = cv2.FastFeatureDetector_create(
threshold=thresh,
nonmaxSuppression=True
)
kps = fast.detect(gray, None)
result = cv2.drawKeypoints(img, kps, None, color=(0, 255, 0))
ax.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
ax.set_title(f'Threshold={thresh}, Points={len(kps)}')
ax.axis('off')
plt.tight_layout()
plt.show()
compare_fast_thresholds('building.jpg')
5. SIFT Detector¶
Concept¶
SIFT (Scale-Invariant Feature Transform):
Feature detection and description invariant to scale and rotation
Steps:
1. Scale-space extrema detection (DoG: Difference of Gaussians)
2. Keypoint localization (sub-pixel accuracy, edge removal)
3. Orientation assignment (gradient histogram)
4. Descriptor computation (4x4 grid x 8 directions = 128 dimensions)
Scale Space:
+-------------------------------------------------+
| Octave 0 Octave 1 Octave 2 |
| +-------+ +-----+ +---+ |
| | s=1.6| | s=1.6| |s=1.6| -> Scale-wise |
| | s=2.0| | s=2.0| |s=2.0| Gaussian |
| | s=2.5| | s=2.5| |s=2.5| blur |
| | s=3.2| | s=3.2| |s=3.2| |
| +-------+ +-----+ +---+ |
| Original 1/2 size 1/4 size |
+-------------------------------------------------+
DoG (Difference of Gaussians):
D(x, y, sigma) = L(x, y, k*sigma) - L(x, y, sigma)
-> Blob detection via difference between adjacent scales
cv2.SIFT_create()¶
import cv2
import numpy as np
def sift_detection(image_path):
"""SIFT feature detection"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Create SIFT detector
sift = cv2.SIFT_create(
nfeatures=0, # Max features (0=unlimited)
nOctaveLayers=3, # Layers per octave
contrastThreshold=0.04, # Contrast threshold
edgeThreshold=10, # Edge threshold
sigma=1.6 # Initial Gaussian sigma
)
# Compute keypoints and descriptors
keypoints, descriptors = sift.detectAndCompute(gray, None)
# Draw results
result = cv2.drawKeypoints(
img, keypoints, None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
print(f"Detected features: {len(keypoints)}")
if descriptors is not None:
print(f"Descriptor size: {descriptors.shape}")
cv2.imshow('SIFT', result)
cv2.waitKey(0)
return keypoints, descriptors
kps, descs = sift_detection('object.jpg')
Analyzing SIFT Keypoints¶
import cv2
import numpy as np
def analyze_sift_keypoints(image_path):
"""Detailed analysis of SIFT keypoints"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift = cv2.SIFT_create()
keypoints, descriptors = sift.detectAndCompute(gray, None)
print("SIFT Keypoint Analysis:")
print("-" * 50)
# Keypoint attributes
for i, kp in enumerate(keypoints[:5]):
print(f"Keypoint {i}:")
print(f" Location (x, y): ({kp.pt[0]:.1f}, {kp.pt[1]:.1f})")
print(f" Size (scale): {kp.size:.1f}")
print(f" Angle: {kp.angle:.1f} degrees")
print(f" Response: {kp.response:.4f}")
print(f" Octave: {kp.octave}")
# Scale distribution
scales = [kp.size for kp in keypoints]
print(f"\nScale range: {min(scales):.1f} ~ {max(scales):.1f}")
# Descriptor analysis
if descriptors is not None:
print(f"\nDescriptors:")
print(f" Dimensions: {descriptors.shape[1]}")
print(f" Value range: {descriptors.min():.1f} ~ {descriptors.max():.1f}")
analyze_sift_keypoints('object.jpg')
6. ORB Detector¶
Concept¶
ORB (Oriented FAST and Rotated BRIEF):
Improved version of FAST + BRIEF, patent-free
Components:
1. oFAST: FAST with orientation information
- Computes orientation for rotation invariance
- Image pyramid for scale invariance
2. rBRIEF: Rotated BRIEF
- BRIEF: Binary descriptor (256 bits)
- Learned comparison patterns for better discrimination
- Fast matching with Hamming distance
Characteristics:
- Much faster than SIFT/SURF
- Patent-free
- Suitable for real-time processing
- Binary descriptor -> Fast matching
BRIEF Descriptor:
Compare intensities of two points in a patch
tau(P; x, y) = { 1 if P(x) < P(y)
{ 0 otherwise
-> n comparisons yield n-bit binary string
cv2.ORB_create()¶
import cv2
import numpy as np
def orb_detection(image_path):
"""ORB feature detection"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Create ORB detector
orb = cv2.ORB_create(
nfeatures=500, # Maximum features
scaleFactor=1.2, # Pyramid scale factor
nlevels=8, # Number of pyramid levels
edgeThreshold=31, # Edge threshold
firstLevel=0, # First pyramid level
WTA_K=2, # Points to compare in BRIEF (2, 3, 4)
scoreType=cv2.ORB_HARRIS_SCORE, # Score type
patchSize=31, # BRIEF patch size
fastThreshold=20 # FAST threshold
)
# Compute keypoints and descriptors
keypoints, descriptors = orb.detectAndCompute(gray, None)
# Draw results
result = cv2.drawKeypoints(
img, keypoints, None,
color=(0, 255, 0),
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
print(f"Detected features: {len(keypoints)}")
if descriptors is not None:
print(f"Descriptor size: {descriptors.shape}")
print(f"Descriptor type: {descriptors.dtype}") # uint8 (binary)
cv2.imshow('ORB', result)
cv2.waitKey(0)
return keypoints, descriptors
kps, descs = orb_detection('object.jpg')
SIFT vs ORB Comparison¶
import cv2
import numpy as np
import time
import matplotlib.pyplot as plt
def compare_sift_orb(image_path):
"""Compare SIFT and ORB performance"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# SIFT
sift = cv2.SIFT_create()
start = time.time()
kps_sift, descs_sift = sift.detectAndCompute(gray, None)
sift_time = time.time() - start
# ORB
orb = cv2.ORB_create(nfeatures=len(kps_sift))
start = time.time()
kps_orb, descs_orb = orb.detectAndCompute(gray, None)
orb_time = time.time() - start
print("Performance Comparison:")
print("-" * 50)
print(f"SIFT: {len(kps_sift)} points, {sift_time*1000:.1f}ms")
print(f"ORB: {len(kps_orb)} points, {orb_time*1000:.1f}ms")
print(f"Speed ratio: ORB is {sift_time/orb_time:.1f}x faster")
if descs_sift is not None and descs_orb is not None:
print(f"\nSIFT descriptor: {descs_sift.shape}, {descs_sift.dtype}")
print(f"ORB descriptor: {descs_orb.shape}, {descs_orb.dtype}")
# Visualization
result_sift = cv2.drawKeypoints(img, kps_sift, None, color=(0, 255, 0))
result_orb = cv2.drawKeypoints(img, kps_orb, None, color=(0, 0, 255))
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
axes[0].imshow(cv2.cvtColor(result_sift, cv2.COLOR_BGR2RGB))
axes[0].set_title(f'SIFT: {len(kps_sift)} points')
axes[0].axis('off')
axes[1].imshow(cv2.cvtColor(result_orb, cv2.COLOR_BGR2RGB))
axes[1].set_title(f'ORB: {len(kps_orb)} points')
axes[1].axis('off')
plt.tight_layout()
plt.show()
compare_sift_orb('object.jpg')
7. Keypoints and Descriptors¶
KeyPoint Structure¶
import cv2
import numpy as np
def keypoint_structure():
"""Understanding keypoint structure"""
# Manually create keypoint
kp = cv2.KeyPoint(
x=100.5, # x coordinate
y=200.5, # y coordinate
size=20, # Feature size (diameter)
angle=45, # Orientation (degrees)
response=0.8, # Response strength
octave=0, # Octave (scale)
class_id=-1 # Class ID
)
print("KeyPoint Attributes:")
print(f" Location: ({kp.pt[0]}, {kp.pt[1]})")
print(f" Size: {kp.size}")
print(f" Angle: {kp.angle}")
print(f" Response: {kp.response}")
print(f" Octave: {kp.octave}")
print(f" Class ID: {kp.class_id}")
keypoint_structure()
Understanding Descriptors¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
def visualize_descriptors(image_path):
"""Visualize descriptors"""
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# SIFT descriptor (128-dimensional float)
sift = cv2.SIFT_create()
kps_sift, descs_sift = sift.detectAndCompute(img, None)
# ORB descriptor (32 bytes = 256 bits)
orb = cv2.ORB_create()
kps_orb, descs_orb = orb.detectAndCompute(img, None)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# SIFT descriptor histogram
if descs_sift is not None and len(descs_sift) > 0:
axes[0, 0].bar(range(128), descs_sift[0])
axes[0, 0].set_title('SIFT Descriptor (128D)')
axes[0, 0].set_xlabel('Dimension')
axes[0, 1].imshow(descs_sift[:50], aspect='auto', cmap='viridis')
axes[0, 1].set_title('SIFT Descriptors (first 50)')
axes[0, 1].set_xlabel('Dimension')
axes[0, 1].set_ylabel('Keypoint')
# ORB descriptor (binary)
if descs_orb is not None and len(descs_orb) > 0:
# Convert binary to bits
bits = np.unpackbits(descs_orb[0])
axes[1, 0].bar(range(256), bits)
axes[1, 0].set_title('ORB Descriptor (256 bits)')
axes[1, 0].set_xlabel('Bit')
# Multiple descriptors
bits_all = np.unpackbits(descs_orb[:50], axis=1)
axes[1, 1].imshow(bits_all, aspect='auto', cmap='binary')
axes[1, 1].set_title('ORB Descriptors (first 50)')
axes[1, 1].set_xlabel('Bit')
axes[1, 1].set_ylabel('Keypoint')
plt.tight_layout()
plt.show()
visualize_descriptors('object.jpg')
Using Various Detectors¶
import cv2
import numpy as np
def use_various_detectors(image_path):
"""Use various feature detectors"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
detectors = {
'SIFT': cv2.SIFT_create(),
'ORB': cv2.ORB_create(),
'BRISK': cv2.BRISK_create(),
'AKAZE': cv2.AKAZE_create(),
# 'KAZE': cv2.KAZE_create(), # Slow
}
results = {}
for name, detector in detectors.items():
kps, descs = detector.detectAndCompute(gray, None)
results[name] = {
'keypoints': kps,
'descriptors': descs,
'count': len(kps),
'desc_size': descs.shape[1] if descs is not None else 0
}
print(f"{name}:")
print(f" Feature count: {len(kps)}")
if descs is not None:
print(f" Descriptor: {descs.shape}, {descs.dtype}")
print()
return results
results = use_various_detectors('object.jpg')
8. Practice Problems¶
Problem 1: Select Best Keypoints¶
Select only the 50 strongest keypoints from an image.
Solution Code
import cv2
import numpy as np
def select_best_keypoints(image_path, n=50):
"""Select N strongest keypoints"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Detect many keypoints with ORB
orb = cv2.ORB_create(nfeatures=500)
keypoints, descriptors = orb.detectAndCompute(gray, None)
# Sort by response strength
keypoints_sorted = sorted(keypoints, key=lambda x: x.response, reverse=True)
# Select top N
best_keypoints = keypoints_sorted[:n]
# Select corresponding descriptors
indices = [keypoints.index(kp) for kp in best_keypoints]
best_descriptors = descriptors[indices] if descriptors is not None else None
result = cv2.drawKeypoints(
img, best_keypoints, None,
color=(0, 255, 0),
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
cv2.imshow(f'Best {n} Keypoints', result)
cv2.waitKey(0)
return best_keypoints, best_descriptors
kps, descs = select_best_keypoints('building.jpg', n=50)
Problem 2: Uniformly Distributed Keypoints¶
Divide the image into a grid and select one keypoint from each cell.
Solution Code
import cv2
import numpy as np
def uniform_keypoints(image_path, grid_size=(8, 8)):
"""Select keypoints uniformly per grid cell"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
h, w = gray.shape
orb = cv2.ORB_create(nfeatures=1000)
keypoints, descriptors = orb.detectAndCompute(gray, None)
# Calculate grid size
cell_h = h // grid_size[0]
cell_w = w // grid_size[1]
# Select strongest keypoint per cell
selected_kps = []
selected_indices = []
for row in range(grid_size[0]):
for col in range(grid_size[1]):
# Cell region
x_min = col * cell_w
x_max = (col + 1) * cell_w
y_min = row * cell_h
y_max = (row + 1) * cell_h
# Filter keypoints in cell
cell_kps = []
for i, kp in enumerate(keypoints):
if x_min <= kp.pt[0] < x_max and y_min <= kp.pt[1] < y_max:
cell_kps.append((i, kp))
if cell_kps:
# Select strongest keypoint
best_idx, best_kp = max(cell_kps, key=lambda x: x[1].response)
selected_kps.append(best_kp)
selected_indices.append(best_idx)
# Descriptors
selected_descs = descriptors[selected_indices] if descriptors is not None else None
result = cv2.drawKeypoints(
img, selected_kps, None,
color=(0, 255, 0),
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
# Draw grid
for row in range(1, grid_size[0]):
cv2.line(result, (0, row * cell_h), (w, row * cell_h), (128, 128, 128), 1)
for col in range(1, grid_size[1]):
cv2.line(result, (col * cell_w, 0), (col * cell_w, h), (128, 128, 128), 1)
cv2.imshow('Uniform Keypoints', result)
cv2.waitKey(0)
return selected_kps, selected_descs
kps, descs = uniform_keypoints('building.jpg', grid_size=(6, 8))
Problem 3: Rotation Invariance Test¶
Rotate an image and verify that the same features are detected.
Solution Code
import cv2
import numpy as np
def test_rotation_invariance(image_path, angle=45):
"""Test rotation invariance"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
h, w = gray.shape
# Rotate image
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(gray, M, (w, h))
# SIFT (rotation invariant)
sift = cv2.SIFT_create(nfeatures=100)
kps1, descs1 = sift.detectAndCompute(gray, None)
kps2, descs2 = sift.detectAndCompute(rotated, None)
# Feature matching
bf = cv2.BFMatcher()
matches = bf.knnMatch(descs1, descs2, k=2)
# Filter good matches (Lowe's ratio test)
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good_matches.append(m)
print(f"Original features: {len(kps1)}")
print(f"Rotated image features: {len(kps2)}")
print(f"Matched features: {len(good_matches)}")
print(f"Match rate: {len(good_matches) / len(kps1) * 100:.1f}%")
# Visualization
result = cv2.drawMatches(
gray, kps1, rotated, kps2,
good_matches, None,
flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS
)
cv2.imshow('Rotation Invariance Test', result)
cv2.waitKey(0)
test_rotation_invariance('object.jpg', angle=30)
Recommended Problems¶
| Difficulty | Topic | Description |
|---|---|---|
| * | Basic Detection | Compare Harris, FAST, ORB |
| ** | Performance Comparison | Compare detection speed and count |
| ** | Parameter Tuning | Find optimal parameters |
| *** | Scale Invariance | Test against size changes |
| *** | Real-time Detection | Display features in real-time from webcam |
Next Steps¶
- 14_Feature_Matching.md - BFMatcher, FLANN, Homography