Classical vs learned local features, measured on the same images with the same keypoint budget: SIFT · ORB · AKAZE against XFeat · DISK · ALIKED, on the Oxford-VGG affine sequences and HPatches.
Companion code for a three-part series on condados.ai.
image-matching-lab/
├── pyproject.toml # uv package + [project.scripts] + ruff
├── src/image_matching_lab/
│ ├── config.py # paths (project-root-aware) + dataset + RANSAC constants
│ ├── core/
│ │ ├── datasets.py # Oxford-VGG + HPatches download, Pair enumeration
│ │ ├── backends.py # Features dataclass + OpenCV backends + registry
│ │ ├── deep_backends.py # kornia backends (XFeat, DISK, ALIKED)
│ │ ├── matching.py # NN / mutual-NN / Lowe ratio + MAGSAC++ homography
│ │ ├── metrics.py # corner error, AUC, precision against ground truth
│ │ └── viz.py # keypoint + match drawing, GIF writing
│ └── cli/ # one click command per file
│ ├── download.py extract.py match.py
└── output/ # generated artifacts (dir kept, contents gitignored)
uv sync# 1. Datasets. Oxford-VGG is ~40 MB total; HPatches is 1.19 GiB, so it is opt-in.
uv run iml-download # Oxford-VGG, all 8 sequences
uv run iml-download --sequences graf,bark # just the ones you need
uv run iml-download --hpatches # adds HPatches (slow)
# 2. Detect and describe. Writes a keypoint overlay to output/.
uv run iml-extract --backend sift --image data/oxford-affine/graf/img1.ppm
# 3. Match a ground-truth pair and score it against the known homography.
uv run iml-match --backend sift --sequence graf --difficulty 4
uv run iml-match --backend disk --sequence graf --difficulty 4 # auto-picks LightGlue
uv run iml-match --backend disk --sequence graf --difficulty 4 --matcher nn # the wrong protocolThe ratio threshold is not transferable. Learned descriptors are L2-normalised, so their
first/second-nearest ratios cluster near 1.0; SIFT's are unnormalised with a wide spread.
Applying SIFT's classic 0.8 to DISK keeps zero matches on graf 1-4. Defaults are
per-descriptor-family (config.DEFAULT_RATIOS) and the threshold used is always logged.
DISK and ALIKED need LightGlue. Same keypoints, only the matcher changes:
| method | NN + ratio 0.95 | + LightGlue |
|---|---|---|
| DISK | precision 0.08, corner error 366 px | precision 0.67, corner error 4.16 px |
| ALIKED | precision 0.10, corner error 6.59 px | precision 0.70, corner error 3.11 px |
--matcher auto (the default) gives each backend the matcher it was designed for.
Exception: XFeat is 64-D and kornia 0.8.3's LightGlue configs expect 128-D/256-D, so
XFeat runs on nearest neighbour — upstream pairs it with LighterGlue, which kornia does not
expose. Any XFeat number here was produced without its own matcher.
This project runs on OpenCV 5 and depends on opencv-contrib-python, not
opencv-python. OpenCV 5 folded features2d into a new Features module and moved several
classical detectors out to opencv_contrib:
main module cv2.* |
contrib cv2.xfeatures2d.* |
|
|---|---|---|
| kept / moved | SIFT, ORB, FAST, GFTT, MSER, AffineFeature | AKAZE, KAZE, BRISK, AGAST, SURF, DAISY, FREAK, BRIEF, LATCH, VGG, BEBLID, TEBLID, … |
| new in 5.0 | ALIKED_create, DISK_create, LightGlueMatcher_create, ANNIndex_create (Annoy) |
— |
So pip install opencv-python on 5.x gives you no AKAZE at all. backends._create_akaze
checks the main module first and falls back to contrib, so OpenCV 4 keeps working; on 4.14.0.94
and 5.0.0.93 AKAZE returns identical results here (273 matches, 62 inliers, 3.29 px corner
error on graf 1-4).
Use the _create factories. cv2.xfeatures2d.AKAZE() — the bare constructor of the abstract
class — segfaults the interpreter on 5.0.0.
Every command writes its artifacts to output/ and logs what it did with
loguru. Nothing is cached silently: re-running overwrites.
| Dataset | Content | Ground truth | Source |
|---|---|---|---|
| Oxford-VGG affine | 8 scenes × 6 images (blur, viewpoint, zoom+rotation, lighting, JPEG) | homographies 1→2..6 | robots.ox.ac.uk |
| HPatches sequences | 116 scenes × 6 images (illumination / viewpoint) | homographies 1→2..6 | HF mirror |
Note: the HPatches URL printed in the paper and in most downstream repos
(icvl.ee.ic.ac.uk) is dead — the hostname has no DNS record. The dataset lives
on the Hugging Face mirror linked from the official repo, which is what
iml-download --hpatches uses.
Neither page states an explicit licence. Images are downloaded at run time and are not redistributed in this repo.
- K. Mikolajczyk et al. A comparison of affine region detectors. IJCV 65(1/2):43–72, 2005.
- V. Balntas, K. Lenc, A. Vedaldi, K. Mikolajczyk. HPatches: A benchmark and evaluation of handcrafted and learned local descriptors. CVPR 2017. https://arxiv.org/abs/1704.05939
- G. Potje, F. Cadar, A. Araujo, R. Martins, E. R. Nascimento. XFeat: Accelerated Features for Lightweight Image Matching. CVPR 2024. https://arxiv.org/abs/2404.19174
The code in this repository is licensed under the Apache License 2.0.
That covers this code and nothing else. Everything the pipeline downloads at run time carries its own terms, and some of them are more restrictive:
| what | where it comes from | terms |
|---|---|---|
| XFeat weights | verlab/accelerated_features |
Apache-2.0 |
| DISK weights | cvlab-epfl/disk |
Apache-2.0 |
| ALIKED weights | Shiaoming/ALIKED |
BSD-3-Clause |
| LightGlue weights | fetched by kornia | see kornia and upstream |
| Oxford VGG affine sequences | robots.ox.ac.uk | no licence stated on the source page |
| HPatches sequences | Hugging Face mirror | no licence stated on the source page |
Neither dataset page declares an explicit licence, so treat both as academic-use images: this repository links to them and downloads them at run time rather than redistributing them. Check the terms yourself before any commercial use, and cite the papers listed in the README.