cv2.findHomography in Python: Homography Matrix Guide

cv2.findHomography() estimates a 3×3 perspective transformation matrix from matching points in two planes. In OpenCV projects, it is commonly used for image alignment, object localization, panorama stitching, document rectification, and feature-matching workflows.

A homography works when the matching points lie on the same physical plane, or when the camera motion can be modeled as a planar perspective transform. If the scene contains multiple depth planes, one homography will not describe the whole scene accurately.

cv2.findHomography Syntax

H, mask = cv2.findHomography(
    srcPoints,
    dstPoints,
    method=0,
    ransacReprojThreshold=3.0,
    maxIters=2000,
    confidence=0.995,
)
Argument Meaning
srcPoints Point coordinates in the source image or plane.
dstPoints Matching point coordinates in the destination image or plane.
method Estimator. Common choices are 0, cv2.RANSAC, cv2.LMEDS, and cv2.RHO.
ransacReprojThreshold Maximum reprojection error, in pixels, for a pair to count as an inlier when using RANSAC or RHO.
mask Returned inlier/outlier mask for robust methods.

The function returns H, the homography matrix, and mask, which marks inlier matches. OpenCV’s documentation notes that a threshold from about 1 to 10 pixels usually makes sense when the input points are measured in pixels.

Simple Point-Pair Example

You need at least four matching point pairs. The points should be shaped as (N, 1, 2) or as a compatible two-column floating-point array.

import cv2
import numpy as np

src_pts = np.float32([
    [40, 40],
    [220, 35],
    [230, 180],
    [35, 190],
]).reshape(-1, 1, 2)

dst_pts = np.float32([
    [60, 30],
    [260, 70],
    [220, 220],
    [50, 170],
]).reshape(-1, 1, 2)

H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)

if H is None:
    raise ValueError("Homography could not be estimated")

print(H)
print(mask.ravel().tolist())

With four clean point pairs, the mask should contain inliers for the points used to estimate the transform. With many noisy feature matches, the mask becomes more useful because it separates likely-correct matches from outliers.

Using Homography for Object Detection

The common OpenCV workflow is:

  1. Detect keypoints and descriptors in the query image and scene image.
  2. Match descriptors with a matcher such as BFMatcher or FLANN.
  3. Keep good matches, often with a ratio test.
  4. Build source and destination point arrays from the matched keypoints.
  5. Call cv2.findHomography() with cv2.RANSAC.
  6. Use cv2.perspectiveTransform() to project the query image corners into the scene.
import cv2
import numpy as np

MIN_MATCH_COUNT = 10

# kp1, des1: keypoints and descriptors from the query image
# kp2, des2: keypoints and descriptors from the scene image
# good_matches: filtered matches from your matcher

if len(good_matches) >= MIN_MATCH_COUNT:
    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)

    H, inlier_mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)

    if H is not None:
        h, w = query_image.shape[:2]
        corners = np.float32([
            [0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]
        ]).reshape(-1, 1, 2)

        projected = cv2.perspectiveTransform(corners, H)
else:
    print(f"Not enough matches: {len(good_matches)}/{MIN_MATCH_COUNT}")

The official OpenCV tutorial uses the same idea: after enough good feature matches are found, matched keypoint coordinates are passed into findHomography(), and perspectiveTransform() maps object corners into the target image.

Choosing RANSAC, LMEDS, RHO, or 0

  • method=0: Uses all point pairs. Use it only when the points are clean and you do not expect outliers.
  • cv2.RANSAC: The usual choice for feature matching because it can reject bad point correspondences.
  • cv2.LMEDS: Does not need a reprojection threshold, but OpenCV notes that it works correctly only when more than half of the matches are inliers.
  • cv2.RHO: A PROSAC-based robust method that can be useful for large match sets.

Understanding the Mask

The returned mask is a compact report of which point pairs were treated as inliers by the robust method. For drawing feature matches, convert it to a list:

matches_mask = inlier_mask.ravel().tolist()

Use the mask when you want to draw only reliable matches or count how many correspondences supported the final homography.

Common cv2.findHomography Errors

  • Not enough points: A homography needs at least four point pairs. In practice, use more than four when points come from feature matching.
  • Wrong point shape: Convert points to np.float32 and reshape to (N, 1, 2) when examples or downstream calls expect that shape.
  • H is None: The matrix could not be estimated. Check point quality, match count, and whether the points are close to collinear.
  • Bad threshold: A threshold that is too strict can reject useful matches; one that is too loose can keep outliers.
  • Non-planar scenes: One homography cannot align objects that live on different depth planes.

Related Python Pool Guides

After estimating a homography, you often need to display or inspect images, so the cv2.imshow guide is useful. For adjacent OpenCV work, see cv2.normalize in Python. If you are loading image arrays for plotting, read Matplotlib imread. For broader image conversion workflows, see ImageMagick in Python.

Official References

Conclusion

cv2.findHomography() is the OpenCV function to estimate a perspective transform between matching planar points. For real feature-matching tasks, use more than four matches, prefer cv2.RANSAC, inspect the returned inlier mask, and always handle the case where no reliable homography can be estimated.

Subscribe
Notify of
guest
4 Comments
Oldest
Newest Most Voted
Zhengzhi Liu
Zhengzhi Liu
4 years ago

it seems that part of the code is missing, how did you calcuate dst in homography = cv2.polylines(frame, [np.int32(dst)], True, (255, 0, 0), 3) I was not able to reproduce the same result, could you please help?

Pratik Kinage
Admin
4 years ago
Reply to  Zhengzhi Liu

Sorry, for the incomplete code. I’ve updated the article.

xar
xar
4 years ago

Dont understand where/how you got queryIdx and trainIdx when initializing query_pts and train_pts.

Pratik Kinage
Admin
4 years ago
Reply to  xar

Hi,

queryldx and trainldx are DMatch object attributes. These can be accessed by referencing the Dmatch instance, in this case, it was ‘m’.

Regards,
Pratik