Introduction: Seeing the Good and the Bad

Welcome to the second unit of our course. In the previous unit, you learned how to align images by calculating a homography matrix. You practiced extracting coordinates and using the RANSAC algorithm to find an accurate alignment while ignoring incorrect matches.

RANSAC does a fantastic job of filtering out bad matches, but as developers, we need to inspect what it accepted and what it rejected to ensure our alignment is actually sound. A homography might compute mathematically but practically represent a bad alignment.

In this lesson, we will learn how to extract the inliers (the good matches RANSAC kept) and the outliers (the bad matches it threw away) into separate lists. We will write a diagnostic tool to evaluate the alignment quality automatically, and then we will draw the results on the screen to visually confirm our math.

Extracting Inliers and Outliers

As a quick reminder, when we estimate a homography using RANSAC, the algorithm returns a matrix and a mask. This mask is essentially a list of 1s and 0s telling us exactly which matches fit the geometry (1) and which do not (0).

To make use of this mask, we need to split our original list of matches into two separate lists. We can achieve this cleanly in Python using the zip() function, which lets us loop through the matches and the mask at the same time.

Let's look at how we can use list comprehensions to separate our data:

# Assuming 'matches' is our list of features and 'inliers' is our mask of 1s and 0s
inlier_matches = [m for m, keep in zip(matches, inliers) if keep]

In this code, zip(matches, inliers) pairs each match object m with its corresponding 1 or 0 value, which we call keep. If keep is 1 (which evaluates to True in Python), the match is added to our inlier_matches list.

We can apply the exact same logic to find the outliers by simply looking for the items we do not want to keep:

inlier_matches = [m for m, keep in zip(matches, inliers) if keep]
outlier_matches = [m for m, keep in zip(matches, inliers) if not keep]

print("Matches:", len(matches))
print("Inliers:", len(inlier_matches))
print("Outliers:", len(outlier_matches))

Output:

Matches: 450
Inliers: 120
Outliers: 330

By separating them, we now have precise control over our data for both analysis and visualization.

Diagnosing the Alignment

Just looking at the number of inliers and outliers is not always enough. We want our program to automatically interpret the match data and warn us if something looks wrong. To do this, we can build a diagnose_alignment function.

First, we need to calculate some basic statistics from our inliers mask. Because our mask is a NumPy array, we can easily get the total number of inliers using .sum() and the ratio of inliers to total matches using .mean().

def diagnose_alignment(match_count, inliers):
    inlier_count = int(inliers.sum())
    
    # Calculate the ratio, ensuring we don't divide by zero
    inlier_ratio = float(inliers.mean()) if len(inliers) else 0.0

Now that we have our metrics, we can add a series of checks. Each scenario tells us something different about what is happening in the real world:

def diagnose_alignment(match_count, inliers):
    inlier_count = int(inliers.sum())
    inlier_ratio = float(inliers.mean()) if len(inliers) else 0.0

    if match_count < 20:
        return "diagnosis: too few matches; revisit the matching report"

    if inlier_count < 10:
        return "diagnosis: too few inliers; the images may not have enough reliable overlap"

    if inlier_ratio < 0.25:
        return "diagnosis: many matches disagree geometrically; try a stricter ratio or a better image pair"

    return "diagnosis: alignment is plausible; inspect the inlier visualization"

Let's break down these checks:

  • Too few matches overall (< 20): This indicates an issue with the initial feature matching step before RANSAC even runs. You might need to adjust your matching threshold.
  • Too few inliers (< 10): Even if you have lots of matches, if fewer than 10 agree on the geometry, the images probably do not have enough reliable overlap to stitch together safely.
  • A low inlier ratio (< 0.25): If you have 1,000 matches but only 100 inliers (a 10% ratio), it often points to repeated patterns in the image (like a brick wall), moving objects, or a bad camera angle.

If we pass some mock data to our function, we get a clear, readable diagnosis:

# Assuming we had 450 total matches and our mask is stored in the 'inliers' variable
print("inlier ratio:", float(inliers.mean()))
print(diagnose_alignment(450, inliers))

Output:

inlier ratio: 0.266
diagnosis: alignment is plausible; inspect the inlier visualization
Visualizing the Results

Numbers and text are great, but seeing the results visually is the best way to understand how well RANSAC performed. We can use the OpenCV function cv2.drawMatches to draw lines connecting the matching features between our two images.

Let's start by drawing the "good" inlier matches in green.

import cv2

inlier_view = cv2.drawMatches(
    left_image,
    keypoints1,
    right_image,
    keypoints2,
    inlier_matches[:80],
    None,
    matchColor=(0, 255, 0),
    flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS,
)

There are two important practical tips to note here:

  1. Limiting the matches: Notice we sliced the list using inlier_matches[:80]. Drawing hundreds of matches creates a chaotic web of lines that is impossible to read. Limiting it to the first 80 or so keeps the visualization clean.
  2. Using flags: The cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS flag tells OpenCV to only draw the features that actually have a connecting line. This prevents the image from being cluttered with unused dots.

Next, we can do the exact same thing for our outlier matches, but this time we will use red (0, 0, 255) to highlight the rejected matches.

outlier_view = cv2.drawMatches(
    left_image,
    keypoints1,
    right_image,
    keypoints2,
    outlier_matches[:80],
    None,
    matchColor=(0, 0, 255),
    flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS,
)

By displaying these two images side-by-side using cv2.imshow(), you can instantly spot patterns. For example, you might notice all your red outlier lines are attaching to moving cars or the leaves of a tree blowing in the wind, which perfectly explains why RANSAC rejected them!

Summary and Practice

In this lesson, we explored the crucial step of evaluating our RANSAC output. Instead of blindly trusting the math, you learned how to:

  • Split your feature matches into separate inlier and outlier lists using a mask.
  • Interpret the geometric agreement of your images by building a diagnose_alignment function.
  • Render clear visual feedback using cv2.drawMatches to highlight successful and failed connections.

You now have a solid diagnostic layer for your image alignment pipeline. Coming up next, you will head into the CodeSignal IDE for hands-on practice. You will get to write the extraction logic, build the diagnostic function, and generate these visual reports yourself to see exactly how your images align.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal