Nodes/comfyui_cv/CV Match Image Moments
ComfyUI Node

CV Match Image Moments

Scores every frame of a batch against one reference by Hu moments of the IMAGE INTENSITY (cv2.matchShapes on raster moments), with the same scale/rotation/mirror policy as 'CV Filter Contours By Shape'. Use it when the thing that distinguishes two candidates is INSIDE the outline: two disks shaded from opposite sides are identical to a contour matcher (exactly 0.000000) and separate here. Emits a keep-mask rather than a filtered batch, so a zero-match result stays a valid batch - combine 'matched' with 'CV Index Batch' to select. Failure-tolerant: an empty batch yields found=false and empty arrays, and a frame cv2 refuses to compare (one side carrying no mass at all) comes back as an infinite distance rather than an error.

By bmad4ever·Created 3 months ago·Updated 2 days ago· 0
CV Match Image Moments
  • reference
  • images
  • distances
  • matched
  • best_index
  • found
  • scale_ratios
  • rotations
  • mirrored
  • transforms
methodCONTOURS_MATCH_I1
max_distance0.30
weightingintensity, peak-normalized
scale_modeany scale (invariant)
scale_tolerance0.10
rotation_modeany rotation (invariant)
rotation_tolerance_deg10
mirror_modeeither handedness (invariant)
min_chirality0.0000
Categoryimage/CV/moments

Inputs (11)

NameTypeDefaultDescription
referenceNPARRAY,IMAGE,MASKThe template to match against (frame 0 if a batch). It does NOT have to be the same size as the candidates - moments are scale invariant. 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.
imagesNPARRAY,IMAGE,MASKCandidate frames, one score per frame. 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.
methodCOMBOCONTOURS_MATCH_I1How the Hu signatures are compared (I1 is the usual choice; I2/I3 are alternative norms).
max_distanceFLOAT0.300–1000000Largest accepted dissimilarity (0 = identical). Raise it to 1e6 and read the distances output to calibrate - intensity distances do not have the same scale as contour ones.
weightingCOMBOintensity, peak-normalizedWhich density function the moments integrate. 'intensity' is Hu's original formulation and cv2's default (mass = pixel value), so a shaded region is a different shape from a flat one. Hu moments are NOT invariant to a brightness gain, so 'peak-normalized' (divide by the frame's own maximum) is the default: it more than halved the error of a x0.5 exposure change in testing (I1 0.0394 raw -> 0.0146). It does NOT fully remove the effect - dimming also shrinks the effective support of a soft-edged region, and no normalization can fix that. 'binary' ignores the values and uses the silhouette, which is the closest thing to what the contour nodes measure - but beware, it treats EVERY non-zero pixel as part of the shape, so any background haze swallows the whole frame.
scale_modeoptCOMBOany scale (invariant)Hu moments ignore size. 'same scale as reference' additionally requires the candidate to be about as big as the reference - the ratio of sqrt(m00), so 0.5 means half the linear size. NOTE: on this path 'size' is sqrt(total intensity), so a brighter copy reads as a bigger one unless weighting is 'binary'.
scale_toleranceoptFLOAT0.100–100Accepted size band as a fraction: 0.10 accepts 0.91x to 1.10x of the reference (symmetric in the ratio, so growing and shrinking are treated alike). 0 demands an exact match. Ignored unless scale is 'same scale as reference'.
rotation_modeoptCOMBOany rotation (invariant)'same orientation as reference' keeps only candidates standing the same way up. 'same orientation or 180 deg flipped' also accepts the upside-down copy - USE IT for 2-fold symmetric shapes (ellipse, rectangle), whose 0-360 orientation flips arbitrarily. Both are meaningless for a shape with no well-defined orientation: check 'pose_confidence' first (a rotating square reads 0, 140, 145, 155, 135 degrees - pure noise).
rotation_tolerance_degoptFLOAT100–180Half-width of the accepted rotation window in degrees. Compared CIRCULARLY, so 359 degrees counts as 1 degree away from 0.
mirror_modeoptCOMBOeither handedness (invariant)The matchShapes distance is a poor handedness test, so decide it here instead. Only the 7th Hu invariant's SIGN changes under reflection (OpenCV: "invariants to the image scale, rotation, and reflection except the seventh one, whose sign is changed by reflection"), and matchShapes skips any term below its internal eps = 1e-5 - which |h7| usually is. Measured: an 'F' and its mirror are indistinguishable (~0), while a hook with a bigger |h7| scores 0.50. 'same handedness only' drops reflected copies; 'mirrored only' keeps just the reflected ones. A mirror-symmetric shape (square, circle, isoceles triangle) is its own mirror, so it counts as UNDECIDED: kept by 'same handedness only', dropped by 'mirrored only'.
min_chiralityoptFLOAT0.00000–1How chiral a shape must be before mirroring can be decided at all - the magnitude of the 'chirality' value. This knob exists because the 7th Hu invariant is the one routinely discarded in practice: it is the smallest and the most fragile, and OpenCV's own docs note the invariance is proved "with the assumption of infinite image resolution", so "in case of raster images, the computed Hu invariants for the original and transformed images are a bit different". Rasterizing a symmetric shape therefore produces a small NON-zero h7 whose sign is meaningless. Measured magnitudes: an 'F' glyph 0.020-0.048, a scalene triangle 0.0069, a rasterized symmetric shape up to ~0.005 of pure noise, a square exactly 0. Below this on EITHER side the match is reported as mirrored = 0 (undecidable) rather than guessed. Set 0 to trust the sign always. Those numbers are for hard-edged silhouettes; a smooth intensity field measures ~1e-5, which is why the default here is far lower than on the contour matcher. Read the 'chirality' output of 'CV Image Moments' on your own data before raising it.

Outputs (8)

NameTypeDescription
distancesNPARRAY(N,) float32 matchShapes distance per INPUT frame, in batch order (not sorted - the batch order is what 'CV Index Batch' needs). Rejected frames keep their real distance; use 'matched' to tell them apart.
matchedNPARRAY(N,) float32 keep-mask, 1 where the frame passed BOTH the distance threshold and the invariance policy, else 0.
best_indexINTIndex of the closest ACCEPTED frame, or -1 when nothing matched. Feed it to 'CV Index Batch'.
foundBOOLEANFalse when no frame passed - wire it into an 'if/else' rather than testing best_index by hand.
scale_ratiosNPARRAY(N,) float32 sqrt(m00) of each frame over the reference's. 0.0 where no pose exists.
rotationsNPARRAY(N,) float32 rotation in degrees, 0-360, taking the reference onto that frame (Y down, clockwise on screen). For a MIRRORED match it is the rotation applied AFTER flipping the reference about its vertical axis.
mirroredNPARRAY(N,) float32 handedness verdict: +1 reflected, -1 same handedness, 0 undecidable (symmetric, or below 'min_chirality').
transformsNPARRAY(N,2,3) float32 similarity matrix per frame mapping REFERENCE coordinates onto that frame (mirror, scale, rotate, centroid to centroid). Feed a row to cv2_warpAffine to overlay the template on its match.