cv2.moments
24 numbers that describe a blob (and why the output isn't a list)
- array
- moments
cv2.moments is the shape-description primitive every contour or blob feature you've ever used is built on. It reduces a raster (or a point set) to 24 named numbers, m00 through nu03, from which you get area, centroid, principal axis, and the seven Hu moments that stay roughly constant when a shape rotates or scales. If you're filtering detected regions, matching shapes, or scoring object poses, this is the arithmetic under the hood.
In a ComfyUI graph you'd usually see it via the pack's curated nodes - CV Shape Moments, which renders a per-contour moments/Hu table with a canonical 0–360 orientation, or CV Image Moments, which does the same per frame. Both wrap this function. You reach for the raw node when you want the moments dict itself, or when you're feeding cv2_HuMoments.
The intensity thing nobody mentions
This is the detail that makes the node worth understanding. cv2.moments accepts either a point set or a raster, and for a raster the default binaryImage = false means a pixel's mass is its value. That's Hu's original density function. So two blobs with the same outline but different internal shading are the same shape to a contour-based node and different shapes here. A bright spot inside a dark region pulls the centroid toward itself, because as far as the moments are concerned, the light is the substance.
Flip binaryImage to true and every non-zero pixel counts as 1, which is what you want on a mask: with it off, m00 of a 255-valued mask is 255 × the pixel area, which is not the area.
Inputs, output, and the type oddity
array is the single required input - an IMAGE, MASK or NPARRAY, single-channel raster or an N×2 point set. binaryImage is the one switch, a boolean defaulting to false.
The output is one socket, moments, of type CV_MOMENTS. That type exists for a reason worth quoting, because it's a silent-wrong-answer trap: the Python binding reads the moments mapping by key. Hand the same 24 numbers to cv2.HuMoments as a list and you get seven zeros back - no exception, no warning. So CV_MOMENTS stays a named dict rather than flattening to a float array, and it's what cv2_HuMoments wants wired into m. (The type also accepts a pasted literal, if you already have a moments dict you trust.)
Install
ComfyUI Manager → search ComfyUI CV → install → restart. Or:
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, recent ComfyUI on the V3 node API. This node is core cv2, no contrib submodule, no models.
Gotchas
m00can be zero, and it can be negative. A zero-area contour givesm00 = 0; a reversed-winding contour gives a negativem00. Both break the centroid divide and the normalizednu*moments silently. The pack's curated nodes gate on this and report "no pose available"; the raw wrapper does not - check before you divide.- Composites and images are different inputs. Feed a binary mask if you mean a shape. Feed a grayscale image if you mean a density. Same node, very different answers, and the difference is the whole point of that
binaryImageflag. - No preview.
CV_MOMENTSis not an image and not a float list, so it won't render in a preview node. Wire it tocv2_HuMoments, or use the curatedCV Image Moments/CV Shape Momentsnodes when you want to read the numbers. - If
cv2_momentsis missing from your node menu entirely, your installedcv2doesn't expose it - the low-level wrappers are generated from the installed build.CV Build Informationtells you what that build actually is.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| array | NPARRAY,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. | |
| binaryImageopt | BOOLEAN | false | - - - Preset to the OpenCV default (False). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| moments | CV_MOMENTS | - - - The 24 NAMED image moments (m00 ... nu03) - wire straight into cv2.HuMoments. cv2 reads them by name, so this stays a moments value rather than a list of numbers. |