Nodes/ComfyUI CV/cv2.connectedComponents
ComfyUI Node

cv2.connectedComponents

Count and label the blobs in a mask

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.connectedComponents
  • image
  • int
  • nparray
◄connectivity8►
◄ltypeCV_32S►

Why this one matters

You have a binary mask with, say, five separate blobs in it, and you need them separately: five independent masks, or a count, or "keep only the biggest one". That's connected-component labelling, and ComfyUI core has no node for it. This is the one you want.

It shows up constantly in the mask layer that masking-detection-detailing.md describes - after a threshold sweep, a colour-range selection, a background-removal pass (background-removal.md) or a segmentation model, you often get a mask that is technically one mask and practically several objects. cv2.connectedComponents is the thing that splits it up.

It's a raw wrapper from ComfyUI CV (bmad4ever/comfyui_cv), category image/CV/low-level/cv2 C - but the pack also ships curated, friendlier versions of the same idea, and for most people those are the better starting point (more below).

How it works

The input must be 8-bit, single-channel. Every non-zero pixel is foreground; the algorithm then flood-labels connected foreground regions and writes a label map: same shape as the input, each pixel holding the integer id of the blob it belongs to. Blob 0 is the background.

That "every non-zero pixel is foreground" detail is the whole reason this node disappoints people. Feed it a greyscale photo rather than a binary mask and almost the entire image is non-zero, so you get one giant component. Threshold first - cv2.threshold, CV Color Range, or any of the mask-producing nodes - then label.

Connectivity is the one meaningful choice: 8-way (the default) counts diagonal neighbours as connected, 4-way doesn't. Antialiased edges and thin diagonal strokes usually want 8; if two objects are joined only at a corner and you're seeing them as one blob, that's why.

The pack does quietly convert a 3-channel IMAGE to grey for you on the way in (it's on the pack's grey-required list), which saves you a node but doesn't save you from the thresholding.

Inputs and outputs that matter

  • image - required. The 8-bit single-channel mask. IMAGE/MASK/NPARRAY all accepted.
  • connectivity - optional INT, default 8, advanced input. Set 4 if diagonal contact is not contact.
  • ltype - optional COMBO, default CV_32S. The author's tooltip says only CV_32S and CV_16U are supported. Stay on CV_32S.

Outputs, and note the naming - this is a raw wrapper, not a curated UI:

  • int - the label count. It includes the background label, so five blobs report as 6. That off-by-one has eaten a lot of afternoons.
  • nparray - the label map. It's an NPARRAY, not an echo of your input, because a label map is data rather than a picture. Preview it with Preview CV Array in heatmap mode (forty labels are indistinguishable in greyscale), and consume it with CV Labels to Masks (full size) to get one MASK per region.

Honestly, if your goal is "give me the blobs", the curated nodes in this same pack are the answer: CV Connected Components (Split Mask) does the split directly, CV Keep Largest Component keeps the big one, CV Select Component At Point keeps whatever covers a coordinate, CV Components Touching Border drops blobs cut off by the frame edge (the sky-mask and clipped-detection case), and CV Fill Holes closes interior gaps. Reach for the raw node when you want the label map itself, or when you're feeding a downstream function that wants it.

Installing the pack

Manager → search ComfyUI CV, or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv

Restart ComfyUI. Needs Python ≥ 3.12, a ComfyUI recent enough for the V3 node API, and opencv-contrib-python-headless~=5.0.0.93.

Where people get burned

  • A grey photo instead of a mask. One giant blob, no error. Threshold first.
  • The off-by-one on the count. int counts background. Subtract one, or count blobs some other way (the WithStats variant's stats output has one row per label including background, so the same caution applies there).
  • Reading labels as brightness. Label 200 is not "brighter than" label 3 - the ids are arbitrary. Anything visual should go through applyColorMap or the pack's preview heatmap.
  • CV_8U is not an option. Only CV_32S / CV_16U, so "cap at 255 labels" isn't how you'd limit it anyway.
  • Pack-level caveats, worth reading once. The README states the code was written with heavy LLM assistance, may contain overfitted corners, will not receive planned updates, and shouldn't be used in production without independent review. The community footprint is nil - searching Reddit for this pack returns nothing - so you're on your own for oddities. And the failure you're most likely to hit first isn't in this node at all: it's cv2 itself. Windows portable builds with DLL load failed while importing cv2, or a non-contrib wheel overwriting the contrib one, take out every OpenCV node pack at once. The repo's tools/repair_opencv_contrib.py --check handles the second case.
Categoryimage/CV/low-level/cv2 C

Inputs (3)

NameTypeDefaultDescription
imageNPARRAY,IMAGE,MASKthe 8-bit single-channel image to be labeled 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.
connectivityoptINT8-2147483648–21474836478 or 4 for 8-way or 4-way connectivity respectively Preset to the OpenCV default (8).
ltypeoptCOMBOCV_32Soutput image label type. Currently CV_32S and CV_16U are supported.

Outputs (2)

NameTypeDescription
intINT—
nparrayNPARRAY—