Panorama stitching
Two photos of the same view, joined into one by finding the points they share and solving for the turn between them.
- 0.03 px
- Off the true homography
- 25,000
- RANSAC rounds
- 4 s
- Run time
What it does
A panorama pipeline from CSC 340: SIFT keypoints, matches filtered by a ratio test, and a homography found by RANSAC written by hand rather than called from OpenCV. The RANSAC was later revised to score by inliers and refit on them, which took it from 10 px off the true answer to 0.03 px, closer than OpenCV's own. Blending was specified and not finished, and this page says which is which.
- Finds the same points in both photos
- SIFT keypoints and descriptors on each image, matched with a brute-force matcher. A match is kept only when its best candidate is clearly better than the second best, at a ratio of 0.7, which throws out most of the matches that only look alike.
- Solves for the camera's turn by hand
- RANSAC written out in full: 25,000 rounds, each taking four random matches, normalising them, and taking the null vector of the 8 by 9 system by SVD as a candidate homography. The candidate that the most matches agree with wins and is refitted on all of them, then printed beside OpenCV's findHomography on the same matches.
- Maps the second photo into the first
- Every pixel of the panorama outside the first photo is looked up in the second through the inverse homography, so the warped image has no holes. Five stitched results are in the final report.
Demo
Stitch it
Two photos of one scene, the second taken with the camera turned. Step through corners, matches, 25,000 rounds of RANSAC and the stitch, with the first version of the RANSAC beside the revised one. Then mix in bad matches and see which holds.
- Match the revised fit agrees with
- Match it rejects
- Where each fit puts photo A: solid revised, dashed first version
Drawn, not photographed. The pipeline runs live on these two images.
| First version least total distance over every match | Revised most matches within 3 px, then refit on them | |
|---|---|---|
| Matches it agrees with | – | – |
| Off from the true overlap | – | – |
| Found in round | – | – |
The seams are left hard: blending them away was specified and not finished. Two substitutions, because a browser has no OpenCV: corners come from a Harris detector and are described by normalised 8 × 8 patches instead of SIFT, and each system is solved with the last entry fixed at 1 instead of by SVD. The ratio test, the round count and both scoring rules are the repo's.
How it works
The revision
The first version scored each round by adding up the distance between where every match lands and where it should, outliers included, and kept the smallest total. Under a total, a single wild match can outweigh a dozen good ones, so the winning round was the one that compromised with the bad matches rather than ignoring them, and a ratio test always lets some through.
The revision, on 18 October 2026, changed three things:
- Score by agreement. A round counts the matches that land within 3 px, and the round that explains the most wins.
- Refit on every inlier. The winner is solved again from all of its inliers at once, rather than kept as the best four-point guess.
- Normalise first. Points are centred and scaled before the system is built, which keeps the SVD well conditioned when coordinates run into the hundreds.
On a drawn pair with a known answer and 417 real SIFT matches, the first version landed 10.06 px
from the truth and the revision lands 0.03 px from it; OpenCV’s findHomography lands 0.05 px from
it. Scoring all matches at once with NumPy also took a run from 60 seconds to 4. The demo above
runs both versions side by side on the same samples.
What is not finished
The repository is named for multi-band blending, and the blending is not there.
blending_assignment.py is the course’s starter file with every step still marked ..., and
the handout beside it describes the method: split each image into three frequency bands with
Gaussian blurs, blend the fine detail over a narrow seam and the broad colour over a wide one, and
add the bands back together. That is why the stitches in the demo keep a hard seam where the two
exposures meet. The warping code behind the report’s five panoramas is also not in the repository.
Testing
3 tests, under 2 s
The tests draw their own image pair, cut the second view through a homography they choose, and check that the RANSAC finds it again, so the answer is known exactly and no image files are needed. The script also prints OpenCV's homography beside its own on every run.
What the tests defend
- A third of the matches replaced with random pairings must not move the result more than half a pixel. That is the case the first version failed: under a total, those bad matches dragged the fit off.
- Four exact points must give back the exact homography, which pins the 8 by 9 system itself and the normalisation around it.
Built with
- Language
- PythonNumPy
- Vision
- OpenCVSIFTRANSAChomography
- Testing
- pytestdrawn pairs with a known answer