0
# Feature Detection and Description
1
2
OpenCV's 2D Features Framework provides comprehensive tools for detecting, describing, and matching distinctive features in images. These features are essential for tasks like object recognition, image stitching, 3D reconstruction, and visual tracking.
3
4
## Capabilities
5
6
### Feature Detectors
7
8
#### SIFT (Scale-Invariant Feature Transform)
9
10
Patent-free since 2020, SIFT is one of the most robust feature detection algorithms, invariant to scale, rotation, and illumination changes.
11
12
```python { .api }
13
cv2.SIFT_create(
14
nfeatures=0, # Number of best features to retain (0 = all)
15
nOctaveLayers=3, # Number of layers in each octave
16
contrastThreshold=0.04, # Contrast threshold for filtering weak features
17
edgeThreshold=10, # Edge threshold for filtering edge responses
18
sigma=1.6 # Gaussian sigma for the first octave
19
) -> cv2.SIFT
20
```
21
22
**Returns**: SIFT detector/descriptor object
23
24
**Common usage**:
25
```python
26
sift = cv2.SIFT_create()
27
keypoints, descriptors = sift.detectAndCompute(image, None)
28
```
29
30
#### ORB (Oriented FAST and Rotated BRIEF)
31
32
A fast, efficient alternative to SIFT and SURF, ORB is rotation invariant and resistant to noise.
33
34
```python { .api }
35
cv2.ORB_create(
36
nfeatures=500, # Maximum number of features to retain
37
scaleFactor=1.2, # Pyramid decimation ratio
38
nlevels=8, # Number of pyramid levels
39
edgeThreshold=31, # Size of border where features are not detected
40
firstLevel=0, # Level of pyramid to put source image
41
WTA_K=2, # Number of points for BRIEF descriptor (2, 3, 4)
42
scoreType=cv2.ORB_HARRIS_SCORE, # HARRIS_SCORE or FAST_SCORE
43
patchSize=31, # Size of patch used by oriented BRIEF
44
fastThreshold=20 # FAST threshold
45
) -> cv2.ORB
46
```
47
48
**Returns**: ORB detector/descriptor object
49
50
**Score types**:
51
- `cv2.ORB_HARRIS_SCORE` - Harris corner measure for ranking features
52
- `cv2.ORB_FAST_SCORE` - FAST score for ranking features
53
54
#### AKAZE (Accelerated-KAZE)
55
56
A fast variant of KAZE that uses binary descriptors, providing good performance with lower computational cost.
57
58
```python { .api }
59
cv2.AKAZE_create(
60
descriptor_type=cv2.AKAZE_DESCRIPTOR_MLDB, # Descriptor type
61
descriptor_size=0, # Size of descriptor (0 = full size)
62
descriptor_channels=3, # Number of channels in descriptor
63
threshold=0.001, # Detector response threshold
64
nOctaves=4, # Number of octaves
65
nOctaveLayers=4, # Number of sublevels per octave
66
diffusivity=cv2.KAZE_DIFF_PM_G2 # Diffusivity type
67
) -> cv2.AKAZE
68
```
69
70
**Returns**: AKAZE detector/descriptor object
71
72
**Descriptor types**:
73
- `cv2.AKAZE_DESCRIPTOR_KAZE` - Upright KAZE descriptor
74
- `cv2.AKAZE_DESCRIPTOR_KAZE_UPRIGHT` - Upright KAZE descriptor
75
- `cv2.AKAZE_DESCRIPTOR_MLDB` - Modified Local Difference Binary descriptor
76
- `cv2.AKAZE_DESCRIPTOR_MLDB_UPRIGHT` - Upright MLDB descriptor
77
78
**Diffusivity types**:
79
- `cv2.KAZE_DIFF_PM_G1` - Perona-Malik diffusivity
80
- `cv2.KAZE_DIFF_PM_G2` - Perona-Malik diffusivity (default)
81
- `cv2.KAZE_DIFF_WEICKERT` - Weickert diffusivity
82
- `cv2.KAZE_DIFF_CHARBONNIER` - Charbonnier diffusivity
83
84
#### BRISK (Binary Robust Invariant Scalable Keypoints)
85
86
A binary descriptor algorithm that is fast and provides good performance for real-time applications.
87
88
```python { .api }
89
cv2.BRISK_create(
90
thresh=30, # AGAST detection threshold score
91
octaves=3, # Detection octaves (0 = single scale)
92
patternScale=1.0 # Scale of sampling pattern
93
) -> cv2.BRISK
94
```
95
96
**Returns**: BRISK detector/descriptor object
97
98
#### KAZE
99
100
A multiscale feature detection and description algorithm using nonlinear diffusion filtering.
101
102
```python { .api }
103
cv2.KAZE_create(
104
extended=False, # Use extended 128-element descriptor
105
upright=False, # Use upright (non-rotation invariant) descriptor
106
threshold=0.001, # Detector response threshold
107
nOctaves=4, # Number of octaves
108
nOctaveLayers=4, # Number of sublevels per octave
109
diffusivity=cv2.KAZE_DIFF_PM_G2 # Diffusivity type
110
) -> cv2.KAZE
111
```
112
113
**Returns**: KAZE detector/descriptor object
114
115
#### SimpleBlobDetector
116
117
Detects blobs (regions of connected pixels) in images based on various properties like size, color, shape, and convexity.
118
119
```python { .api }
120
cv2.SimpleBlobDetector_create(
121
parameters=None # SimpleBlobDetector_Params object
122
) -> cv2.SimpleBlobDetector
123
```
124
125
**Returns**: SimpleBlobDetector object
126
127
**Parameters class**:
128
```python
129
params = cv2.SimpleBlobDetector_Params()
130
131
# Threshold parameters
132
params.minThreshold = 50
133
params.maxThreshold = 220
134
params.thresholdStep = 10
135
136
# Filter by area
137
params.filterByArea = True
138
params.minArea = 100
139
params.maxArea = 5000
140
141
# Filter by circularity
142
params.filterByCircularity = True
143
params.minCircularity = 0.1
144
145
# Filter by convexity
146
params.filterByConvexity = True
147
params.minConvexity = 0.87
148
149
# Filter by inertia
150
params.filterByInertia = True
151
params.minInertiaRatio = 0.01
152
153
# Filter by color
154
params.filterByColor = True
155
params.blobColor = 255 # 0 for dark blobs, 255 for light blobs
156
157
detector = cv2.SimpleBlobDetector_create(params)
158
```
159
160
#### MSER (Maximally Stable Extremal Regions)
161
162
Detects stable regions in images across different threshold levels, useful for text detection and wide baseline matching.
163
164
```python { .api }
165
cv2.MSER_create(
166
delta=5, # Delta for MSER test
167
min_area=60, # Minimum area of region
168
max_area=14400, # Maximum area of region
169
max_variation=0.25, # Maximum variation in region stability
170
min_diversity=0.2, # Minimum diversity
171
max_evolution=200, # Maximum evolution steps
172
area_threshold=1.01, # Area threshold for filtering
173
min_margin=0.003, # Minimum margin
174
edge_blur_size=5 # Edge blur size
175
) -> cv2.MSER
176
```
177
178
**Returns**: MSER detector object
179
180
**Note**: MSER.detect() returns regions as contours, not KeyPoint objects.
181
182
#### FAST (Features from Accelerated Segment Test)
183
184
A high-speed corner detection algorithm, often used as the first stage in feature detection pipelines.
185
186
```python { .api }
187
cv2.FastFeatureDetector_create(
188
threshold=10, # Threshold on difference between intensity
189
nonmaxSuppression=True, # Apply non-maximum suppression
190
type=cv2.FAST_FEATURE_DETECTOR_TYPE_9_16 # FAST detector type
191
) -> cv2.FastFeatureDetector
192
```
193
194
**Returns**: FAST detector object
195
196
**Detector types**:
197
- `cv2.FAST_FEATURE_DETECTOR_TYPE_5_8` - 8 pixels on circle of radius 1
198
- `cv2.FAST_FEATURE_DETECTOR_TYPE_7_12` - 12 pixels on circle of radius 2
199
- `cv2.FAST_FEATURE_DETECTOR_TYPE_9_16` - 16 pixels on circle of radius 3
200
201
#### AGAST (Adaptive and Generic Accelerated Segment Test)
202
203
An improved version of FAST with better performance and adaptivity.
204
205
```python { .api }
206
cv2.AgastFeatureDetector_create(
207
threshold=10, # Threshold on difference between intensity
208
nonmaxSuppression=True, # Apply non-maximum suppression
209
type=cv2.AGAST_FEATURE_DETECTOR_OAST_9_16 # AGAST detector type
210
) -> cv2.AgastFeatureDetector
211
```
212
213
**Returns**: AGAST detector object
214
215
**Detector types**:
216
- `cv2.AGAST_FEATURE_DETECTOR_AGAST_5_8` - AGAST-5_8 decision tree
217
- `cv2.AGAST_FEATURE_DETECTOR_AGAST_7_12d` - AGAST-7_12d decision tree
218
- `cv2.AGAST_FEATURE_DETECTOR_AGAST_7_12s` - AGAST-7_12s decision tree
219
- `cv2.AGAST_FEATURE_DETECTOR_OAST_9_16` - OAST-9_16 decision tree
220
221
#### GFTT (Good Features To Track)
222
223
Shi-Tomasi corner detector, which finds the most prominent corners in an image.
224
225
```python { .api }
226
cv2.GFTTDetector_create(
227
maxCorners=1000, # Maximum number of corners to return
228
qualityLevel=0.01, # Minimal accepted quality of corners
229
minDistance=1, # Minimum distance between corners
230
blockSize=3, # Size of averaging block
231
useHarrisDetector=False, # Use Harris detector (vs. Shi-Tomasi)
232
k=0.04 # Free parameter of Harris detector
233
) -> cv2.GFTTDetector
234
```
235
236
**Returns**: GFTT detector object
237
238
### Feature2D Base Class Interface
239
240
All feature detectors inherit from the `Feature2D` base class and share these common methods:
241
242
```python { .api }
243
detector.detect(
244
image, # Input image (grayscale)
245
mask=None # Optional mask specifying detection region
246
) -> keypoints
247
```
248
249
**Returns**: List of KeyPoint objects
250
251
```python { .api }
252
detector.compute(
253
image, # Input image (grayscale)
254
keypoints # List of KeyPoint objects to compute descriptors for
255
) -> keypoints, descriptors
256
```
257
258
**Returns**:
259
- `keypoints` - Input keypoints (may be modified)
260
- `descriptors` - Computed descriptors as numpy array (N × descriptor_size)
261
262
```python { .api }
263
detector.detectAndCompute(
264
image, # Input image (grayscale)
265
mask=None # Optional mask specifying detection region
266
) -> keypoints, descriptors
267
```
268
269
**Returns**:
270
- `keypoints` - List of detected KeyPoint objects
271
- `descriptors` - Computed descriptors as numpy array (N × descriptor_size)
272
273
**Note**: `detectAndCompute()` is more efficient than calling `detect()` and `compute()` separately.
274
275
```python { .api }
276
detector.empty() -> bool
277
```
278
279
**Returns**: True if the detector object is empty
280
281
```python { .api }
282
detector.getDefaultName() -> str
283
```
284
285
**Returns**: Algorithm name as string
286
287
### Descriptor Matching
288
289
#### BFMatcher (Brute-Force Matcher)
290
291
Matches descriptors by comparing each descriptor in the first set against all descriptors in the second set using a specified distance metric.
292
293
```python { .api }
294
cv2.BFMatcher_create(
295
normType=cv2.NORM_L2, # Distance norm type
296
crossCheck=False # Enable cross-check for more robust matching
297
) -> cv2.BFMatcher
298
299
# Alternative constructor
300
cv2.BFMatcher(
301
normType=cv2.NORM_L2,
302
crossCheck=False
303
) -> cv2.BFMatcher
304
```
305
306
**Returns**: BFMatcher object
307
308
**Norm types**:
309
- `cv2.NORM_L1` - L1 norm (Manhattan distance)
310
- `cv2.NORM_L2` - L2 norm (Euclidean distance) - for SIFT, SURF
311
- `cv2.NORM_HAMMING` - Hamming distance - for ORB, BRISK, BRIEF
312
- `cv2.NORM_HAMMING2` - Hamming distance with 2 bits per dimension
313
- `cv2.NORM_INF` - Infinity norm
314
315
**Cross-check**: When enabled, only returns matches (i,j) where descriptor i in set A is the best match for descriptor j in set B, and vice versa.
316
317
#### FlannBasedMatcher
318
319
Fast Library for Approximate Nearest Neighbors (FLANN) based matcher, faster than brute-force for large datasets.
320
321
```python { .api }
322
cv2.FlannBasedMatcher_create() -> cv2.FlannBasedMatcher
323
324
# Constructor with index parameters
325
cv2.FlannBasedMatcher(
326
indexParams=None, # Index parameters dictionary
327
searchParams=None # Search parameters dictionary
328
) -> cv2.FlannBasedMatcher
329
```
330
331
**Returns**: FlannBasedMatcher object
332
333
**Index parameters examples**:
334
```python
335
# For SIFT/SURF descriptors (float)
336
index_params = dict(algorithm=1, trees=5) # FLANN_INDEX_KDTREE
337
338
# For ORB descriptors (binary)
339
index_params = dict(
340
algorithm=6, # FLANN_INDEX_LSH
341
table_number=6,
342
key_size=12,
343
multi_probe_level=1
344
)
345
```
346
347
**Search parameters**:
348
```python
349
search_params = dict(checks=50) # Number of times trees should be recursively traversed
350
```
351
352
**FLANN algorithm constants**:
353
- `FLANN_INDEX_LINEAR = 0` - Linear brute-force search
354
- `FLANN_INDEX_KDTREE = 1` - Randomized kd-tree (for float descriptors)
355
- `FLANN_INDEX_KMEANS = 2` - Hierarchical k-means tree
356
- `FLANN_INDEX_COMPOSITE = 3` - Combination of trees and kmeans
357
- `FLANN_INDEX_KDTREE_SINGLE = 4` - Single kd-tree
358
- `FLANN_INDEX_HIERARCHICAL = 5` - Hierarchical clustering
359
- `FLANN_INDEX_LSH = 6` - Locality-sensitive hashing (for binary descriptors)
360
361
#### DescriptorMatcher Common Methods
362
363
Both BFMatcher and FlannBasedMatcher inherit from DescriptorMatcher and share these methods:
364
365
```python { .api }
366
matcher.match(
367
queryDescriptors, # Query descriptors (N1 × descriptor_size)
368
trainDescriptors, # Train descriptors (N2 × descriptor_size)
369
mask=None # Optional mask for valid matches
370
) -> matches
371
```
372
373
**Returns**: List of DMatch objects (best match for each query descriptor)
374
375
```python { .api }
376
matcher.knnMatch(
377
queryDescriptors, # Query descriptors
378
trainDescriptors, # Train descriptors
379
k, # Number of best matches to return per descriptor
380
mask=None, # Optional mask
381
compactResult=False # Remove empty matches from result
382
) -> matches
383
```
384
385
**Returns**: List of lists of DMatch objects (k best matches per query descriptor)
386
387
**Common usage with ratio test**:
388
```python
389
matches = matcher.knnMatch(desc1, desc2, k=2)
390
# Apply Lowe's ratio test
391
good_matches = []
392
for m, n in matches:
393
if m.distance < 0.75 * n.distance:
394
good_matches.append(m)
395
```
396
397
```python { .api }
398
matcher.radiusMatch(
399
queryDescriptors, # Query descriptors
400
trainDescriptors, # Train descriptors
401
maxDistance, # Maximum allowed distance
402
mask=None, # Optional mask
403
compactResult=False # Remove empty matches from result
404
) -> matches
405
```
406
407
**Returns**: List of lists of DMatch objects (all matches within maxDistance)
408
409
```python { .api }
410
matcher.add(
411
descriptors # List of descriptor arrays to add to training set
412
) -> None
413
```
414
415
Adds descriptors to the training set for multi-image matching.
416
417
```python { .api }
418
matcher.train() -> None
419
```
420
421
Trains the matcher (required for some FLANN-based matchers).
422
423
```python { .api }
424
matcher.clear() -> None
425
```
426
427
Clears the training descriptor set.
428
429
```python { .api }
430
matcher.empty() -> bool
431
```
432
433
**Returns**: True if the matcher has no training data
434
435
### Drawing Functions
436
437
#### drawKeypoints
438
439
Draws keypoints on an image, optionally showing their size, orientation, and other properties.
440
441
```python { .api }
442
cv2.drawKeypoints(
443
image, # Source image
444
keypoints, # List of KeyPoint objects to draw
445
outImage, # Output image (can be the same as input)
446
color=(0, 255, 0), # Keypoint color (B, G, R)
447
flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT # Drawing flags
448
) -> outImage
449
```
450
451
**Returns**: Output image with keypoints drawn
452
453
**Drawing flags**:
454
- `cv2.DRAW_MATCHES_FLAGS_DEFAULT` - Simple circles at keypoint locations
455
- `cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS` - Draw circles with size and orientation
456
- `cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS` - Don't draw single points
457
- `cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG` - Draw on existing output image
458
459
**Usage**:
460
```python
461
# Simple drawing
462
img_keypoints = cv2.drawKeypoints(img, keypoints, None, color=(0, 255, 0))
463
464
# Rich keypoints showing size and orientation
465
img_keypoints = cv2.drawKeypoints(
466
img, keypoints, None,
467
color=(0, 255, 0),
468
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
469
)
470
```
471
472
#### drawMatches
473
474
Draws matches between keypoints from two images side by side.
475
476
```python { .api }
477
cv2.drawMatches(
478
img1, # First source image
479
keypoints1, # Keypoints from first image
480
img2, # Second source image
481
keypoints2, # Keypoints from second image
482
matches1to2, # List of DMatch objects
483
outImg, # Output image (None = create new)
484
matchColor=(0, 255, 0), # Color for matches (B, G, R)
485
singlePointColor=(255, 0, 0), # Color for single points
486
matchesMask=None, # Mask determining which matches to draw
487
flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT # Drawing flags
488
) -> outImg
489
```
490
491
**Returns**: Output image showing both images with matches drawn between them
492
493
**Parameters**:
494
- `matchesMask` - List of bytes (0/1) same length as matches, 1 = draw match
495
- `matchColor` - Color for match lines, or tuple for random colors per match
496
- `singlePointColor` - Color for keypoints without matches
497
498
**Usage**:
499
```python
500
# Draw all matches
501
img_matches = cv2.drawMatches(
502
img1, kp1, img2, kp2, matches,
503
None, flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS
504
)
505
506
# Draw only good matches with mask
507
mask = [[1] if m.distance < 50 else [0] for m in matches]
508
img_matches = cv2.drawMatches(
509
img1, kp1, img2, kp2, matches, None,
510
matchesMask=mask, flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS
511
)
512
```
513
514
#### drawMatchesKnn
515
516
Draws k best matches for each keypoint from two images.
517
518
```python { .api }
519
cv2.drawMatchesKnn(
520
img1, # First source image
521
keypoints1, # Keypoints from first image
522
img2, # Second source image
523
keypoints2, # Keypoints from second image
524
matches1to2, # List of lists of DMatch objects (from knnMatch)
525
outImg, # Output image (None = create new)
526
matchColor=(0, 255, 0), # Color for matches
527
singlePointColor=(255, 0, 0), # Color for single points
528
matchesMask=None, # Mask for matches (list of lists)
529
flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT # Drawing flags
530
) -> outImg
531
```
532
533
**Returns**: Output image showing both images with k best matches per keypoint
534
535
**Usage with ratio test**:
536
```python
537
# Get k=2 best matches
538
matches = matcher.knnMatch(desc1, desc2, k=2)
539
540
# Apply ratio test and create mask
541
mask = []
542
for m, n in matches:
543
if m.distance < 0.75 * n.distance:
544
mask.append([1, 0]) # Draw first match only
545
else:
546
mask.append([0, 0]) # Don't draw
547
548
img_matches = cv2.drawMatchesKnn(
549
img1, kp1, img2, kp2, matches, None,
550
matchesMask=mask
551
)
552
```
553
554
### KeyPoint Class
555
556
Represents a detected feature point with its properties.
557
558
```python { .api }
559
cv2.KeyPoint(
560
x, # X coordinate
561
y, # Y coordinate
562
size, # Diameter of meaningful keypoint neighborhood
563
angle=-1, # Orientation in degrees [0, 360)
564
response=0, # Response strength
565
octave=0, # Pyramid octave where keypoint was detected
566
class_id=-1 # Object class (for categorized keypoints)
567
) -> cv2.KeyPoint
568
```
569
570
**Attributes**:
571
- `pt` - (x, y) tuple of keypoint coordinates
572
- `size` - Diameter of the meaningful keypoint neighborhood
573
- `angle` - Computed orientation of the keypoint (-1 if not applicable)
574
- `response` - Response strength (used for keypoint ranking)
575
- `octave` - Pyramid octave in which keypoint was detected
576
- `class_id` - Object ID for categorizing keypoints
577
578
**Methods**:
579
```python
580
# Convert keypoints to numpy array of (x, y) coordinates
581
points = cv2.KeyPoint_convert(keypoints)
582
583
# Convert numpy array to keypoints
584
keypoints = cv2.KeyPoint_convert(points, size=20)
585
586
# Get overlap between two keypoints
587
overlap = cv2.KeyPoint_overlap(kp1, kp2) # Returns 0-1
588
```
589
590
### DMatch Class
591
592
Represents a match between two feature descriptors.
593
594
```python { .api }
595
cv2.DMatch(
596
queryIdx, # Query descriptor index
597
trainIdx, # Train descriptor index
598
distance # Distance between descriptors (lower is better)
599
) -> cv2.DMatch
600
601
# Constructor with image index (for multi-image matching)
602
cv2.DMatch(
603
queryIdx,
604
trainIdx,
605
imgIdx, # Train image index
606
distance
607
) -> cv2.DMatch
608
```
609
610
**Attributes**:
611
- `queryIdx` - Index of descriptor in query set
612
- `trainIdx` - Index of descriptor in train set
613
- `imgIdx` - Index of train image (for multi-image matching)
614
- `distance` - Distance between descriptors (lower = better match)
615
616
**Usage**:
617
```python
618
# Sort matches by distance (best matches first)
619
matches = sorted(matches, key=lambda x: x.distance)
620
621
# Get top N matches
622
good_matches = matches[:50]
623
624
# Extract matched keypoint coordinates
625
src_pts = np.float32([kp1[m.queryIdx].pt for m in matches])
626
dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches])
627
```
628
629
## Common Workflows
630
631
### Basic Feature Detection and Matching
632
633
```python
634
import cv2
635
import numpy as np
636
637
# Read images
638
img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)
639
img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)
640
641
# Create detector
642
detector = cv2.SIFT_create()
643
644
# Detect and compute
645
kp1, desc1 = detector.detectAndCompute(img1, None)
646
kp2, desc2 = detector.detectAndCompute(img2, None)
647
648
# Create matcher
649
matcher = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)
650
651
# Match descriptors
652
matches = matcher.knnMatch(desc1, desc2, k=2)
653
654
# Apply ratio test
655
good_matches = []
656
for m, n in matches:
657
if m.distance < 0.75 * n.distance:
658
good_matches.append(m)
659
660
# Draw matches
661
img_matches = cv2.drawMatches(
662
img1, kp1, img2, kp2, good_matches, None,
663
flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS
664
)
665
666
cv2.imshow('Matches', img_matches)
667
cv2.waitKey(0)
668
```
669
670
### Finding Homography from Matches
671
672
```python
673
# Extract matched point coordinates
674
if len(good_matches) >= 4:
675
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
676
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
677
678
# Find homography
679
H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
680
681
# Use homography to transform image
682
h, w = img1.shape
683
img1_warped = cv2.warpPerspective(img1, H, (w, h))
684
```
685
686
### Feature Detection with Region of Interest
687
688
```python
689
# Create mask (detect features only in specific region)
690
mask = np.zeros(img.shape[:2], dtype=np.uint8)
691
mask[100:400, 200:600] = 255 # ROI
692
693
# Detect keypoints in masked region
694
keypoints = detector.detect(img, mask=mask)
695
keypoints, descriptors = detector.compute(img, keypoints)
696
```
697