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.
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)
| Name | Type | Default | Description |
|---|---|---|---|
| reference | NPARRAY,IMAGE,MASK | The 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. | |
| images | NPARRAY,IMAGE,MASK | Candidate 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. | |
| method | COMBO | CONTOURS_MATCH_I1 | How the Hu signatures are compared (I1 is the usual choice; I2/I3 are alternative norms). |
| max_distance | FLOAT | 0.300–1000000 | Largest 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. |
| weighting | COMBO | intensity, peak-normalized | Which 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_modeopt | COMBO | any 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_toleranceopt | FLOAT | 0.100–100 | Accepted 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_modeopt | COMBO | any 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_degopt | FLOAT | 100–180 | Half-width of the accepted rotation window in degrees. Compared CIRCULARLY, so 359 degrees counts as 1 degree away from 0. |
| mirror_modeopt | COMBO | either 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_chiralityopt | FLOAT | 0.00000–1 | How 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)
| Name | Type | Description |
|---|---|---|
| distances | NPARRAY | (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. |
| matched | NPARRAY | (N,) float32 keep-mask, 1 where the frame passed BOTH the distance threshold and the invariance policy, else 0. |
| best_index | INT | Index of the closest ACCEPTED frame, or -1 when nothing matched. Feed it to 'CV Index Batch'. |
| found | BOOLEAN | False when no frame passed - wire it into an 'if/else' rather than testing best_index by hand. |
| scale_ratios | NPARRAY | (N,) float32 sqrt(m00) of each frame over the reference's. 0.0 where no pose exists. |
| rotations | NPARRAY | (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. |
| mirrored | NPARRAY | (N,) float32 handedness verdict: +1 reflected, -1 same handedness, 0 undecidable (symmetric, or below 'min_chirality'). |
| transforms | NPARRAY | (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. |