Nodes/ComfyUI CV/cv2.batchDistance
ComfyUI Node

cv2.batchDistance

The matcher under BFMatcher, for when you need the matrix

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
cv2.batchDistance
  • src1
  • src2
  • mask
  • dist
  • nidx
◄dtypesame as input►
◄normTypeNORM_L2►
◄K0►
◄update0►
◄crosscheckfalse►

cv2.batchDistance computes distances between every row of one descriptor set and every row of another - the brute-force core that BFMatcher wraps. If you have keypoints with descriptors and want to know which ones correspond, this is the arithmetic.

Its reason to exist next to the pack's curated CV Match Features (which builds a BFMatcher or FLANN index and applies the ratio test) is control. CV Match Features gives you matches. This gives you the distance matrix, or the K nearest neighbours per row, or a cross-checked subset you define. That's what you want for kNN graphs, chunked matching on huge descriptor sets, and any place where "the matches" is a summary too early in the pipeline.

How it works

For each row of src1, compute the distance to each row of src2 under normType - NORM_L2 for float descriptors (SIFT, SURF, AKAZE), NORM_HAMMING for binary ones (ORB, BRISK, the *_HAMMING2 variant for the 2-bit case). The combo offers NORM_L2, NORM_L1, NORM_INF, NORM_L2SQR, NORM_HAMMING, NORM_HAMMING2 and NORM_MINMAX, and picking the right one is the whole job: L2 and L2SQR differ by a square root, and comparing one against the other will make your "matches" nonsense.

Then it decides how much to keep. K = 0 writes the full N1×N2 distance matrix into dist. K > 0 keeps only the K nearest neighbours per row, in dist, with their indices into src2 in nidx. crosscheck adds a mutual-nearest test: a pair survives only if each is the other's best. update is an offset added to every index written into nidx, which is how you match a chunk of src1 against the full src2 and keep the indices globally valid.

Both inputs are NPARRAY-only - descriptor matrices, not pixels. The wrapper's typing says a data array, and that's the correct mental model: this node has no opinion about images at all.

Inputs and outputs

  • src1, src2 (NPARRAY) - N×D descriptor matrices, same D.
  • dtype (COMBO, required) - output depth. Set it explicitly; same as input inherits the descriptor depth, and a dist array that wants to be 32-bit coming out 8-bit is a bad time. For binary descriptors, choose a 32-bit depth.
  • normType (optional, default NORM_L2).
  • K (optional INT, default 0 = the full matrix).
  • mask (optional), update (optional INT, default 0), crosscheck (optional BOOLEAN, default False).
  • dist, nidx - the distances and the src2 row indices.

Where it fits

  • Descriptor matching with your own policy. Ratio test, cross-check, mutual-K, distance threshold - all doable by reading dist and nidx rather than accepting a matcher's defaults.
  • kNN graphs. K nearest descriptors per row is exactly a K-nearest-neighbour structure; the pack has K-Means and point-cloud nodes downstream that expect one.
  • Chunked matching. Thousands of descriptors against thousands: loop over chunks of src1, keep K = 1, use update to keep indices global. This is the standard trick for staying inside RAM, and it's why the parameter exists at all.
  • Distance statistics. The full matrix is a measurement - how far apart two feature sets are is a legitimate similarity score for place recognition, image-hash style comparisons and retrieval.

Install

ComfyUI Manager → comfyui_cv (ComfyUI CV), or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"

Restart ComfyUI. Python ≥ 3.12, a recent V3-API ComfyUI, behaviour curated against OpenCV 5.0.0.93. The contrib wheel is required; a non-contrib opencv-python over it silently strips the contrib submodules.

Where people get burned

Wrong descriptor depth. Float64 descriptors raise. SIFT gives float32; if yours came out of some other path, CV Cast Array to float32. Binary descriptors are uint8, and Hamming distances on float data is meaningless.

K = 1 with crosscheck. Cross-check only inspects the first kept column, so it's a K=1 tool. Ask for K>1 and the extra columns are unverified.

Reading nidx as indices into the wrong set. They index src2 - the reference set - not src1. Swap the inputs and every index is silently wrong while the distances look plausible.

Mismatched descriptor width. Both sets must have the same D. Two different extractors produce different widths and the error is at least explicit, but mixing descriptors from two scales of the same extractor is a subtle way to get garbage.

Doing this where CV Match Features would do. For SIFT/ORB matching with a ratio test, the curated node is fewer wires and less rope. Come here when you actually need the matrix.

Categoryimage/CV/low-level/cv2 B

Inputs (8)

NameTypeDefaultDescription
src1NPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
src2NPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
dtypeCOMBOsame as input - - -
normTypeoptCOMBONORM_L2 - - -
KoptINT0-2147483648–2147483647 - - - Preset to the OpenCV default (0).
maskoptNPARRAY,IMAGE,MASK - - - 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.
updateoptINT0-2147483648–2147483647 - - - Preset to the OpenCV default (0).
crosscheckoptBOOLEANfalse - - - Preset to the OpenCV default (False).

Outputs (2)

NameTypeDescription
distNPARRAY—
nidxNPARRAY—