Nodes/ComfyUI CV/CV Find Fundamental Matrix
ComfyUI Node

CV Find Fundamental Matrix

Two views, one matrix, and a found flag you must read

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Find Fundamental Matrix
  • points_a
  • points_b
  • fundamental
  • inlier_mask
  • inlier_count
  • found
◄methodFM_RANSAC►
◄reproj_threshold3.0►
◄confidence0.99►
◄min_inliers15►

The fundamental matrix is the piece of epipolar geometry that links two views of the same static scene: it tells you, for a point in one image, the line in the other image where its match must lie. That's the thing you need before uncalibrated stereo rectification, before triangulating a point cloud from two photos, and before any epipolar-overlay sanity check. This node estimates it from matched point sets with cv2.findFundamentalMat.

How it works and how it fails

points_a and points_b are Nx1x2 arrays of matched coordinates - the outputs of the pack's feature matching nodes land here directly. From those, the node estimates F and, because you'll be using a robust method, gives you back the inlier mask as well.

The failure contract is the important part. Same as the pack's homography node, and stated explicitly in the description: with fewer than eight points, or a degenerate configuration, you get found = false, a zero matrix and inlier_count = 0 instead of the workflow stopping. You branch on found with a control-flow node. That's the difference between a pipeline that survives its bad frames and one that dies on frame 300 of a batch.

The degeneracy warning is specific and worth repeating because it's the number one way people get a nonsense F: points from a single plane are degenerate for F. A poster, a wall, a book cover, a screenshot - all planar, all give you an unstable matrix that looks fine numerically and produces garbage epipolar lines. If your scene has no depth, you want a homography instead, which is what the planar case is properly described by. The other classic failure is a purely rotational camera move: F encodes the translation between views, and if you didn't translate, there's nothing for it to encode.

Inputs

  • points_a / points_b - matched points, Nx1x2. Filter them to RANSAC inliers from a previous stage if you have them, but note the estimator here is itself robust, so this is optional.
  • method - FM_RANSAC (the standard choice for feature matches), FM_8POINT (uses all points, only for outlier-free input), or FM_7POINT (needs exactly seven points, and can return multiple solutions).
  • reproj_threshold (default 3, RANSAC only) - maximum distance in pixels from a point to its epipolar line to count as an inlier. Raise it if you're fighting noise, lower it if you're getting epipolar lines that don't line up with anything.
  • confidence (default 0.99, RANSAC/LMedS) - how sure you want the estimate to be.
  • min_inliers (default 15, minimum 8) - the node's own gate on found. Raise it to reject accidental fits - on a scene with a dominant plane, accidental fits are common and this is your defence.

Outputs

fundamental is the 3×3 float64 matrix, all zeros when not found (the pack's CV Draw Epipolar Lines knows to skip the degenerate lines that would produce). inlier_mask is an Nx1 uint8 mask you feed into CV Draw Matches or the epipolar overlay - and, usefully, into CV Filter Points By Mask, run twice with the same mask to clean point sets A and B in lockstep. inlier_count and found are your control signals.

Where it fits

Matches → F here → inliers → CV Stereo Rectify (Uncalibrated) for a rectified pair, or triangulation for a sparse cloud. The mask-based filtering step in between is what keeps the outliers out of the reconstruction, and skipping it is the difference between a cloud that looks like your scene and one that looks like your scene with fireworks in it.

If you're working with a calibrated rig instead - same camera, known intrinsics - you'd want the essential matrix path, which this pack reaches through its camera-calibration and stereo nodes. F is the uncalibrated route, and uncalibrated means "two photos of the same thing from different places", which is by far the more common case in a ComfyUI graph.

Install

# ComfyUI Manager → search "ComfyUI CV" → install → restart
# or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
cd comfyui_cv && pip install -r requirements.txt

Python ≥ 3.12 and a recent ComfyUI on the V3 node API. Single dependency: opencv-contrib-python-headless~=5.0.0.93. This is a core cv2 function, so it works regardless - but keep the contrib wheel, because installing plain opencv-python over it silently empties the shared cv2 contrib submodules and the pack's Contrib-category nodes disappear (tools/repair_opencv_contrib.py --check / --apply).

When it gives you nothing useful

  • found: false - under eight points, or degenerate. Check your match count first; this is usually a matcher problem, not a geometry one.
  • found: true and the epipolar lines are nonsense - you're looking at a plane, or the camera only rotated. Both are degenerate, both look plausible in the numbers.
  • inlier_count is tiny - reproj_threshold too tight, or the matches are bad. Raise the threshold and look at the overlay before you trust anything downstream.
  • A fit that passes min_inliers by accident - that's what min_inliers is for. Raise it.

bmad4ever's pack is a fork of geroldmeisinger's opencv-comfyui rewritten on the V3 API, and the author's standing disclaimer is that it's LLM-assisted and not production-grade without independent review. The failure contract here - never crash, always answer with found - is the sort of thing that makes a graph survivable, and it's worth copying into whatever you build with it.

Categoryimage/CV/features

Inputs (6)

NameTypeDefaultDescription
points_aNPARRAYPoints in image 1, Nx1x2 (from 'CV Match Features').
points_bNPARRAYCorresponding points in image 2, Nx1x2.
methodCOMBOFM_RANSACFM_RANSAC is the standard choice for feature matches; FM_8POINT uses all points (only for outlier-free input); FM_7POINT needs exactly 7.
reproj_thresholdFLOAT3.00.1–100Maximum distance in pixels from a point to its epipolar line for it to count as an inlier (RANSAC only).
confidenceFLOAT0.990.5–1Desired probability that the estimate is correct (RANSAC/LMedS only).
min_inliersINT158–10000Minimum inliers for 'found' to be true. Raise it to reject accidental fits.

Outputs (4)

NameTypeDescription
fundamentalNPARRAY3x3 float64 fundamental matrix (all zeros if not found - 'CV Draw Epipolar Lines' skips the degenerate lines it produces).
inlier_maskNPARRAYNx1 uint8: 1 = inlier. Feed into 'CV Draw Matches' / 'CV Draw Epipolar Lines'.
inlier_countINT—
foundBOOLEAN—