CV Match Features
Pairing two images' keypoints, and the ratio test that makes it work
- keypoints_a
- descriptors_a
- keypoints_b
- descriptors_b
- matches
- points_a
- points_b
- count
Feature matching is the quiet step in a lot of things people care about: panorama stitching, uncalibrated stereo rectification, homography-based alignment, "which of these two shots is the same scene", and the whole visual-odometry chain the pack ships as an example. You detect keypoints in two images, then you need to know which point in A corresponds to which in B. That second half is this node.
It's a wrapper over cv2.BFMatcher and cv2.FlannBasedMatcher with the knobs that actually get used exposed, and it hands you back freshly baked cv2.DMatch objects plus the matched coordinates as plain arrays - because the coordinate arrays are what cv2.findHomography / the pack's CV Find Homography (RANSAC) and cv2.estimateAffine2D actually consume.
How it works
Five required inputs, and the pairing is the part you can get wrong: keypoints_a, descriptors_a, keypoints_b, descriptors_b. Both sides must come from the same detector - descriptors from ORB are binary 32-byte strings and descriptors from SIFT are 128 floats, and a matcher can't compare across that.
Then three decisions:
matcher: BFMatcher (brute force, exact, fine up to mid-sized sets) or FLANN (approximate, built for 10k+ descriptors).norm_type: leave it on auto. Auto reads the descriptor dtype and picks HAMMING for uint8 (ORB, BRISK, AKAZE) or L2 for float32 (SIFT). Overriding this is how you get zero matches and no explanation.match_mode: ratio test (Lowe) is the default and the right one. Keep matches whose best distance is clearly better than the runner-up (ratio, default 0.75 - lower is stricter). Cross-check keeps only mutual best matches and is BFMatcher-only. Radius keeps everything withinradiusand is mostly a debugging mode.
k (default 2) is the kNN count feeding the ratio test; raise it and you consider more candidates for more time. The flann_* group - algorithm, trees, checks, table_number, key_size, multi_probe_level - only matters if you picked FLANN, and you can leave all of it alone until you're chasing speed.
Outputs: matches (the cv2.DMatch list, for CV Draw Matches), points_a and points_b as Nx1x2 float32 in matching order (straight into a homography or affine fit), and count.
Zero matches is a result, not a failure
This is the mindset the node wants from you. Two images of unrelated things produce zero matches, correctly, and nothing raises. Check count, and check your homography node's own found flag downstream rather than assuming a number means a good fit. The classic beginner trap is a hardcoded index or a crop that assumes at least one match exists; wire it through an if/else on count instead.
Two practical gotchas beyond that. Ratio at 0.75 on a scene with lots of repeated texture (windows, tiles) will still produce confident-looking wrong matches - the fix is RANSAC downstream, not a stricter ratio. And points_a/points_b come out Nx1x2 specifically so they drop into cv2's findHomography without reshaping; if you need to do math on them first, CV Reshape Array is in the same pack for exactly that reason.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Manager → ComfyUI CV does the same thing. Restart. The declared dependency is opencv-contrib-python-headless~=5.0.0.93, on Python ≥ 3.12 with a V3-API-era ComfyUI. No model files - matching here is pure classical CV, CPU, no downloads. The ONNX-based version lives next door as CV Match Features (Model).
One install gotcha worth repeating because it bites people in every ComfyUI install that has a few CV-adjacent packs: the four OpenCV wheels all share a single site-packages/cv2. Install opencv-python (non-contrib) over the contrib one and contrib functionality silently disappears. The pack has a checker:
python tools/repair_opencv_contrib.py --check
If a custom node is throwing libxcb.so.1: cannot open shared object file on a minimal Linux box (NixOS users report this), that's a system library problem, not a this-pack problem - install the headless wheel or the missing system lib.
Inputs (16)
| Name | Type | Default | Description |
|---|---|---|---|
| keypoints_a | CV_KEYPOINTS | Keypoints of image A (from 'CV Detect Features'). | |
| descriptors_a | NPARRAY | Descriptors of image A, paired with keypoints_a. Must use the same detector as image B. | |
| keypoints_b | CV_KEYPOINTS | Keypoints of image B (from 'CV Detect Features'). | |
| descriptors_b | NPARRAY | Descriptors of image B, paired with keypoints_b. | |
| matcher | COMBO | BFMatcher | BFMatcher: brute-force, exact, fast for small/mid descriptor sets. FLANN: approximate nearest neighbors, faster for large sets (10k+ descriptors). |
| norm_type | COMBO | auto | Distance norm. 'auto' = HAMMING for uint8 (ORB/BRISK/AKAZE), L2 for float32 (SIFT). Override only if you know what you are doing. |
| match_mode | COMBO | ratio test (Lowe) | Ratio test: keep matches clearly better than the runner-up (robust default). Cross-check: keep only mutual best matches (BFMatcher only). Radius: keep all matches within max distance (all matchers). |
| ratio | FLOAT | 0.750.1–1 | Lowe ratio threshold; lower = stricter. Ignored for cross-check and radius modes. |
| kopt | INT | 22–32 | k for kNN matching. 2 = Lowe's ratio test (standard). Higher k considers more candidates but is slower. Ignored for cross-check mode. |
| radiusopt | FLOAT | 0.0 | Max distance for radius matching. 0 = no limit (returns all matches). Only used when mode = radius. |
| flann_algorithmopt | COMBO | auto | FLANN index algorithm. 'auto' = KDTREE for float32 descriptors, LSH for binary (uint8). Override to force a specific algorithm. |
| flann_treesopt | INT | 51–16 | FLANN KDTREE: number of randomized trees. More = better accuracy, slower build. |
| flann_checksopt | INT | 501–1000 | FLANN search: number of leaves to check. Higher = more accurate but slower. |
| flann_table_numberopt | INT | 61–30 | FLANN LSH: number of hash tables. Only used for binary descriptors. |
| flann_key_sizeopt | INT | 124–32 | FLANN LSH: key bits per table. Only used for binary descriptors. |
| flann_multi_probe_levelopt | INT | 10–5 | FLANN LSH: extra search probes per table. 0 = standard LSH, higher = more accurate. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| matches | CV_MATCHES | — |
| points_a | NPARRAY | Matched keypoint coordinates in image A, Nx1x2 float32. |
| points_b | NPARRAY | Matched keypoint coordinates in image B, Nx1x2 float32. |
| count | INT | — |