CV Draw Matches
The RANSAC inlier picture that tells you if your homography is real
- image_a
- keypoints_a
- image_b
- keypoints_b
- matches
- inlier_mask
- image
Why you'd reach for this
Feature matching either works or produces confident nonsense, and the numbers don't tell you which. Thirty inliers out of two hundred sounds fine until you look and see they're all on the same railing.
The standard way to look is the side-by-side canvas: image A on the left, image B on the right, a line drawn between each matched pair. If the lines are roughly parallel, you have a rigid-ish relationship between the views. If they cross each other in a starburst, your matcher paired unrelated texture. Twenty seconds of looking beats a page of reprojection error.
How it works
cv2.drawMatches, with the pack's polymorphic image inputs so both sides can be IMAGE, MASK or NPARRAY (output comes back in the same format). Both keypoint sets and the matches are typed sockets - CV_KEYPOINTS from CV Detect Features, CV_MATCHES from CV Match Features - so the whole thing wires up without any array conversion.
Two controls do the real work:
- max_draw truncates the list best first. That only means anything because the upstream matcher already sorted by quality - with a ratio test, the surviving matches come out ordered - so
max_draw = 50gives you the fifty strongest pairs, not an arbitrary fifty. On a busy pair of photos this is the difference between a readable render and a grey rectangle. - inlier_mask - the
Nx1uint8 fromCV Find Homography (RANSAC),CV Find Fundamental MatrixorCV Recover Pose (Essential Matrix). Only rows flagged 1 are drawn. This is the node's actual diagnostic value: show all matches once, then show inliers only, and the gap between the two pictures is your outlier rate.
The node raises if the mask length doesn't match the match count, which matters because a mask from a different filtering stage is a silent-misalignment bug otherwise. It also passes cv2's "don't draw single points" flag, so unmatched keypoints don't clutter the canvas as grey dots.
color is worth a second. The default green is the classic, and the author's own tooltip calls out the trap: green vanishes over foliage, grass or any green subject. Pick a colour the pair of photos doesn't contain - magenta is the usual cheat.
Inputs and outputs that matter
- image_a / image_b - the two views, same format both sides.
- keypoints_a / keypoints_b - from whichever detector ran on each image. They needn't be the same algorithm, but scale/rotation behaviour should match.
- matches -
CV Match Featuresoutput. - max_draw - 0 = all. Start at 0 to see the damage, then cap it.
- inlier_mask (optional) - the RANSAC mask. This is what makes the picture an argument rather than an illustration.
- image - the only output, a side-by-side canvas the same format as the inputs. No count; match counts come from upstream.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Python ≥ 3.12, ComfyUI with the V3 node API, or install ComfyUI CV from ComfyUI Manager. Everything here is classic cv2 on the CPU.
Common issues
- A starburst of crossing lines. Bad matches. Tighten the ratio test in
CV Match Features, drop to a more robust detector setting, or filter keypoints by response first. - Good inliers clustered in one region. The homography is being fitted on scene structure too thin to constrain it - a planar assumption that only holds on the poster, not the rest of the photo. That's knowledge, not a node failure.
- Mask length error. The
inlier_maskcame from a differently filtered match set. Filter both in the same place. - Green lines invisible. The tooltip says it; the subject is green. Change the colour.
- Side-by-side canvas huge. That's both images concatenated. Cap
max_draw, and if it's still unreadable, downscale before matching rather than after drawing. - Contrib wheel. The pack depends on the contrib OpenCV build; a plain
opencv-pythoninstalled over it empties the contrib submodules and contrib nodes disappear.tools/repair_opencv_contrib.py --check/--apply.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| image_a | COMFY_MATCHTYPE_V3 | Left image of the pair. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| keypoints_a | CV_KEYPOINTS | Keypoints of image A. | |
| image_b | COMFY_MATCHTYPE_V3 | Right image of the pair (same format as image_a). Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| keypoints_b | CV_KEYPOINTS | Keypoints of image B. | |
| matches | CV_MATCHES | Matches from 'CV Match Features' to draw. | |
| max_draw | INT | 00–10000 | Draw at most this many matches (best first). 0 = all. |
| inlier_maskopt | NPARRAY | Nx1 uint8 mask from findHomography & co: only matches flagged 1 are drawn. | |
| coloropt | STRING | (0, 255, 0) | Color of the match lines and their keypoints, as a single value or a BGR tuple. The green default vanishes over foliage, grass or any green subject - set something the pair of photos does not contain. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | COMFY_MATCHTYPE_V3 | Side-by-side canvas, same format as the image inputs. |