> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/opencv/opencv/llms.txt
> Use this file to discover all available pages before exploring further.

# Feature Detection and Matching

> Learn how to detect corners, edges, blobs, and match features between images using OpenCV

# Feature Detection and Matching

Learn how to detect and match distinctive features in images, essential for tasks like image stitching, object recognition, and camera calibration.

## What are Features?

Features are distinctive points or regions in an image that can be reliably detected across different views. Good features are:

* Repeatable (can be found in different images of the same scene)
* Distinctive (can be distinguished from nearby features)
* Local (not affected by clutter or occlusion)
* Efficient (fast to compute)

## Corner Detection

### Harris Corner Detector

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv
    import numpy as np

    # Load image
    img = cv.imread('image.jpg')
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    gray = np.float32(gray)

    # Apply Harris corner detection
    dst = cv.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)

    # Dilate to mark the corners
    dst = cv.dilate(dst, None)

    # Threshold for optimal value (adjust based on image)
    img[dst > 0.01 * dst.max()] = [0, 0, 255]

    cv.imshow('Harris Corners', img)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    using namespace cv;

    int main() {
        Mat img = imread("image.jpg");
        Mat gray, dst, dst_norm;
        
        cvtColor(img, gray, COLOR_BGR2GRAY);
        
        // Harris corner detection
        cornerHarris(gray, dst, 2, 3, 0.04);
        
        // Normalize
        normalize(dst, dst_norm, 0, 255, NORM_MINMAX);
        
        // Draw corners
        for(int i = 0; i < dst_norm.rows; i++) {
            for(int j = 0; j < dst_norm.cols; j++) {
                if((int)dst_norm.at<float>(i,j) > 200) {
                    circle(img, Point(j,i), 5, Scalar(0,0,255), 2);
                }
            }
        }
        
        imshow("Harris Corners", img);
        waitKey(0);
        return 0;
    }
    ```
  </Tab>
</Tabs>

### Shi-Tomasi Corner Detector (Good Features to Track)

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv
    import numpy as np

    img = cv.imread('image.jpg')
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

    # Parameters
    maxCorners = 100
    qualityLevel = 0.01
    minDistance = 10

    # Detect corners
    corners = cv.goodFeaturesToTrack(gray, maxCorners, qualityLevel, minDistance)
    corners = np.int0(corners)

    # Draw corners
    for corner in corners:
        x, y = corner.ravel()
        cv.circle(img, (x, y), 5, (0, 255, 0), -1)

    cv.imshow('Shi-Tomasi Corners', img)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    using namespace cv;

    int main() {
        Mat img = imread("image.jpg");
        Mat gray;
        cvtColor(img, gray, COLOR_BGR2GRAY);
        
        vector<Point2f> corners;
        int maxCorners = 100;
        double qualityLevel = 0.01;
        double minDistance = 10;
        
        goodFeaturesToTrack(gray, corners, maxCorners, 
                           qualityLevel, minDistance);
        
        for(size_t i = 0; i < corners.size(); i++) {
            circle(img, corners[i], 5, Scalar(0, 255, 0), -1);
        }
        
        imshow("Shi-Tomasi Corners", img);
        waitKey(0);
        return 0;
    }
    ```
  </Tab>
</Tabs>

## Edge Detection

### Canny Edge Detector

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv

    img = cv.imread('image.jpg')
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

    # Apply Gaussian blur to reduce noise
    blurred = cv.GaussianBlur(gray, (5, 5), 0)

    # Canny edge detection
    # threshold1: lower threshold
    # threshold2: upper threshold
    edges = cv.Canny(blurred, threshold1=50, threshold2=150)

    cv.imshow('Original', gray)
    cv.imshow('Edges', edges)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    using namespace cv;

    int main() {
        Mat img = imread("image.jpg");
        Mat gray, blurred, edges;
        
        cvtColor(img, gray, COLOR_BGR2GRAY);
        GaussianBlur(gray, blurred, Size(5, 5), 0);
        
        Canny(blurred, edges, 50, 150);
        
        imshow("Original", gray);
        imshow("Edges", edges);
        waitKey(0);
        return 0;
    }
    ```
  </Tab>
</Tabs>

<Note>
  For Canny edge detection:

  * Use a 2:1 or 3:1 ratio between upper and lower thresholds
  * Lower threshold: detects weak edges
  * Upper threshold: detects strong edges
  * Edges are connected if they're above the lower threshold and connected to an edge above the upper threshold
</Note>

## Feature Descriptors

### SIFT (Scale-Invariant Feature Transform)

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv

    img = cv.imread('image.jpg')
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

    # Create SIFT detector
    sift = cv.SIFT_create()

    # Detect keypoints and compute descriptors
    keypoints, descriptors = sift.detectAndCompute(gray, None)

    # Draw keypoints
    img_keypoints = cv.drawKeypoints(img, keypoints, None,
                                     flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

    print(f"Number of keypoints: {len(keypoints)}")
    print(f"Descriptor shape: {descriptors.shape}")

    cv.imshow('SIFT Keypoints', img_keypoints)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    #include <opencv2/features2d.hpp>
    #include <iostream>
    using namespace cv;
    using namespace std;

    int main() {
        Mat img = imread("image.jpg");
        Mat gray;
        cvtColor(img, gray, COLOR_BGR2GRAY);
        
        // Create SIFT detector
        Ptr<SIFT> sift = SIFT::create();
        
        vector<KeyPoint> keypoints;
        Mat descriptors;
        
        sift->detectAndCompute(gray, Mat(), keypoints, descriptors);
        
        Mat img_keypoints;
        drawKeypoints(img, keypoints, img_keypoints, Scalar::all(-1),
                     DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
        
        cout << "Keypoints: " << keypoints.size() << endl;
        imshow("SIFT Keypoints", img_keypoints);
        waitKey(0);
        return 0;
    }
    ```
  </Tab>
</Tabs>

### ORB (Oriented FAST and Rotated BRIEF)

ORB is a fast alternative to SIFT and SURF:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv

    img = cv.imread('image.jpg')
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

    # Create ORB detector
    orb = cv.ORB_create(nfeatures=400)

    # Detect and compute
    keypoints, descriptors = orb.detectAndCompute(gray, None)

    # Draw keypoints
    img_keypoints = cv.drawKeypoints(img, keypoints, None, 
                                     color=(0, 255, 0))

    print(f"Number of ORB keypoints: {len(keypoints)}")
    cv.imshow('ORB Keypoints', img_keypoints)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    #include <opencv2/features2d.hpp>
    using namespace cv;

    int main() {
        Mat img = imread("image.jpg");
        Mat gray;
        cvtColor(img, gray, COLOR_BGR2GRAY);
        
        Ptr<ORB> orb = ORB::create(400);
        
        vector<KeyPoint> keypoints;
        Mat descriptors;
        orb->detectAndCompute(gray, Mat(), keypoints, descriptors);
        
        Mat img_keypoints;
        drawKeypoints(img, keypoints, img_keypoints, 
                     Scalar(0, 255, 0));
        
        imshow("ORB Keypoints", img_keypoints);
        waitKey(0);
        return 0;
    }
    ```
  </Tab>
</Tabs>

### AKAZE

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv

    img = cv.imread('image.jpg')
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

    # Create AKAZE detector
    akaze = cv.AKAZE_create()

    # Detect and compute
    keypoints, descriptors = akaze.detectAndCompute(gray, None)

    # Draw keypoints
    img_keypoints = cv.drawKeypoints(img, keypoints, None, 
                                     flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

    cv.imshow('AKAZE Keypoints', img_keypoints)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    #include <opencv2/features2d.hpp>
    using namespace cv;

    int main() {
        Mat img = imread("image.jpg");
        Mat gray;
        cvtColor(img, gray, COLOR_BGR2GRAY);
        
        Ptr<AKAZE> akaze = AKAZE::create();
        
        vector<KeyPoint> keypoints;
        Mat descriptors;
        akaze->detectAndCompute(gray, Mat(), keypoints, descriptors);
        
        Mat img_keypoints;
        drawKeypoints(img, keypoints, img_keypoints);
        
        imshow("AKAZE Keypoints", img_keypoints);
        waitKey(0);
        return 0;
    }
    ```
  </Tab>
</Tabs>

## Feature Matching

Based on OpenCV's find\_obj.py sample:

### Brute-Force Matcher

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv
    import numpy as np

    # Load two images
    img1 = cv.imread('box.png', cv.IMREAD_GRAYSCALE)
    img2 = cv.imread('box_in_scene.png', cv.IMREAD_GRAYSCALE)

    # Create ORB detector
    orb = cv.ORB_create(400)

    # Detect and compute for both images
    kp1, desc1 = orb.detectAndCompute(img1, None)
    kp2, desc2 = orb.detectAndCompute(img2, None)

    # Create BFMatcher
    bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)

    # Match descriptors
    matches = bf.match(desc1, desc2)

    # Sort matches by distance (best matches first)
    matches = sorted(matches, key=lambda x: x.distance)

    # Draw top 20 matches
    img_matches = cv.drawMatches(img1, kp1, img2, kp2, matches[:20], None,
                                 flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

    print(f"Number of matches: {len(matches)}")
    cv.imshow('Matches', img_matches)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    #include <opencv2/features2d.hpp>
    using namespace cv;
    using namespace std;

    int main() {
        Mat img1 = imread("box.png", IMREAD_GRAYSCALE);
        Mat img2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);
        
        Ptr<ORB> orb = ORB::create(400);
        
        vector<KeyPoint> kp1, kp2;
        Mat desc1, desc2;
        
        orb->detectAndCompute(img1, Mat(), kp1, desc1);
        orb->detectAndCompute(img2, Mat(), kp2, desc2);
        
        BFMatcher bf(NORM_HAMMING, true);
        vector<DMatch> matches;
        bf.match(desc1, desc2, matches);
        
        Mat img_matches;
        drawMatches(img1, kp1, img2, kp2, matches, img_matches);
        
        imshow("Matches", img_matches);
        waitKey(0);
        return 0;
    }
    ```
  </Tab>
</Tabs>

### FLANN-Based Matcher

Faster for large datasets:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv
    import numpy as np

    img1 = cv.imread('box.png', cv.IMREAD_GRAYSCALE)
    img2 = cv.imread('box_in_scene.png', cv.IMREAD_GRAYSCALE)

    # Use SIFT for FLANN
    sift = cv.SIFT_create()
    kp1, desc1 = sift.detectAndCompute(img1, None)
    kp2, desc2 = sift.detectAndCompute(img2, None)

    # FLANN parameters
    FLANN_INDEX_KDTREE = 1
    index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
    search_params = dict(checks=50)

    # Create FLANN matcher
    flann = cv.FlannBasedMatcher(index_params, search_params)

    # Find k=2 best matches for each descriptor
    matches = flann.knnMatch(desc1, desc2, k=2)

    # Apply ratio test (Lowe's ratio test)
    good_matches = []
    for m, n in matches:
        if m.distance < 0.75 * n.distance:
            good_matches.append(m)

    print(f"Good matches: {len(good_matches)} / {len(matches)}")

    # Draw matches
    img_matches = cv.drawMatches(img1, kp1, img2, kp2, good_matches, None,
                                 flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

    cv.imshow('FLANN Matches', img_matches)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    #include <opencv2/features2d.hpp>
    using namespace cv;
    using namespace std;

    int main() {
        Mat img1 = imread("box.png", IMREAD_GRAYSCALE);
        Mat img2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);
        
        Ptr<SIFT> sift = SIFT::create();
        
        vector<KeyPoint> kp1, kp2;
        Mat desc1, desc2;
        sift->detectAndCompute(img1, Mat(), kp1, desc1);
        sift->detectAndCompute(img2, Mat(), kp2, desc2);
        
        FlannBasedMatcher flann;
        vector<vector<DMatch>> knn_matches;
        flann.knnMatch(desc1, desc2, knn_matches, 2);
        
        // Ratio test
        vector<DMatch> good_matches;
        for(size_t i = 0; i < knn_matches.size(); i++) {
            if(knn_matches[i][0].distance < 0.75 * knn_matches[i][1].distance) {
                good_matches.push_back(knn_matches[i][0]);
            }
        }
        
        Mat img_matches;
        drawMatches(img1, kp1, img2, kp2, good_matches, img_matches);
        
        imshow("FLANN Matches", img_matches);
        waitKey(0);
        return 0;
    }
    ```
  </Tab>
</Tabs>

## Finding Homography

Find the transformation between matched images:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv
    import numpy as np

    # After getting good matches (from previous example)
    # Extract location of good matches
    src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
    dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)

    # Find homography
    M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)

    # Get dimensions of first image
    h, w = img1.shape

    # Define corners of first image
    pts = np.float32([[0, 0], [w, 0], [w, h], [0, h]]).reshape(-1, 1, 2)

    # Transform corners to second image
    dst = cv.perspectiveTransform(pts, M)

    # Draw bounding box in second image
    img2_color = cv.cvtColor(img2, cv.COLOR_GRAY2BGR)
    cv.polylines(img2_color, [np.int32(dst)], True, (0, 255, 0), 3)

    cv.imshow('Object Detection', img2_color)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    #include <opencv2/features2d.hpp>
    #include <opencv2/calib3d.hpp>
    using namespace cv;
    using namespace std;

    // After getting good_matches
    vector<Point2f> src_pts, dst_pts;
    for(size_t i = 0; i < good_matches.size(); i++) {
        src_pts.push_back(kp1[good_matches[i].queryIdx].pt);
        dst_pts.push_back(kp2[good_matches[i].trainIdx].pt);
    }

    Mat H = findHomography(src_pts, dst_pts, RANSAC, 5.0);

    // Transform corners
    vector<Point2f> corners(4);
    corners[0] = Point2f(0, 0);
    corners[1] = Point2f(img1.cols, 0);
    corners[2] = Point2f(img1.cols, img1.rows);
    corners[3] = Point2f(0, img1.rows);

    vector<Point2f> scene_corners(4);
    perspectiveTransform(corners, scene_corners, H);

    // Draw box
    Mat img2_color;
    cvtColor(img2, img2_color, COLOR_GRAY2BGR);
    line(img2_color, scene_corners[0], scene_corners[1], Scalar(0, 255, 0), 3);
    line(img2_color, scene_corners[1], scene_corners[2], Scalar(0, 255, 0), 3);
    line(img2_color, scene_corners[2], scene_corners[3], Scalar(0, 255, 0), 3);
    line(img2_color, scene_corners[3], scene_corners[0], Scalar(0, 255, 0), 3);
    ```
  </Tab>
</Tabs>

## Blob Detection

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import cv2 as cv
    import numpy as np

    img = cv.imread('blobs.jpg', cv.IMREAD_GRAYSCALE)

    # Setup SimpleBlobDetector parameters
    params = cv.SimpleBlobDetector_Params()

    # Filter by area
    params.filterByArea = True
    params.minArea = 100

    # Filter by circularity
    params.filterByCircularity = True
    params.minCircularity = 0.1

    # Filter by convexity
    params.filterByConvexity = True
    params.minConvexity = 0.5

    # Filter by inertia
    params.filterByInertia = True
    params.minInertiaRatio = 0.01

    # Create detector
    detector = cv.SimpleBlobDetector_create(params)

    # Detect blobs
    keypoints = detector.detect(img)

    # Draw detected blobs
    img_with_keypoints = cv.drawKeypoints(img, keypoints, None, 
                                         (0, 0, 255),
                                         cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

    print(f"Number of blobs: {len(keypoints)}")
    cv.imshow('Blobs', img_with_keypoints)
    cv.waitKey(0)
    ```
  </Tab>

  <Tab title="C++">
    ```cpp theme={null}
    #include <opencv2/opencv.hpp>
    #include <opencv2/features2d.hpp>
    using namespace cv;

    int main() {
        Mat img = imread("blobs.jpg", IMREAD_GRAYSCALE);
        
        SimpleBlobDetector::Params params;
        params.filterByArea = true;
        params.minArea = 100;
        params.filterByCircularity = true;
        params.minCircularity = 0.1;
        params.filterByConvexity = true;
        params.minConvexity = 0.5;
        params.filterByInertia = true;
        params.minInertiaRatio = 0.01;
        
        Ptr<SimpleBlobDetector> detector = 
            SimpleBlobDetector::create(params);
        
        vector<KeyPoint> keypoints;
        detector->detect(img, keypoints);
        
        Mat img_with_keypoints;
        drawKeypoints(img, keypoints, img_with_keypoints, 
                     Scalar(0, 0, 255),
                     DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
        
        imshow("Blobs", img_with_keypoints);
        waitKey(0);
        return 0;
    }
    ```
  </Tab>
</Tabs>

<Note>
  Feature detector comparison:

  * **SIFT**: Most robust, patented (free since 2020), slower
  * **SURF**: Fast, patented, good for real-time
  * **ORB**: Free, fast, good alternative to SIFT/SURF
  * **AKAZE**: Free, fast, works well with planar scenes
  * **BRISK**: Free, very fast, binary descriptor
</Note>

<Warning>
  When matching features, always use the ratio test (Lowe's ratio test) to filter out ambiguous matches and reduce false positives.
</Warning>

## Next Steps

* Use features for [Object Detection](/tutorials/object-detection)
* Apply to [Camera Calibration](/tutorials/camera-calibration)
* Explore [Image Stitching and Panoramas](/tutorials/image-stitching)
