Nodes/ComfyUI CV/cv2.distanceTransformWithLabels
ComfyUI Node

cv2.distanceTransformWithLabels

Which blob does this pixel belong to?

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
cv2.distanceTransformWithLabels
  • src
  • dist
  • labels
◄distanceTypeDIST_L2►
◄maskSize0►
◄labelTypeDIST_LABEL_CCOMP►

The plain distance transform tells you how far the nearest edge is. This one tells you whose edge it is. Two outputs, same cost: the distance map you already know, plus a label map where every foreground pixel carries the id of the zero-pixel region it's closest to.

Why that second output is the interesting one

Nearest-region assignment. Every pixel in the frame gets sorted into the territory of the nearest black region - a Voronoi partition, computed in one pass, no per-pixel loop. Practical shapes of that:

  • Per-blob seeds. Give each blob a distinct id, and you can grow regions outward from the blobs until they collide, which is the seed-and-flood half of watershed-style segmentation.
  • "Who owns this gap?" Two shapes with a channel between them: the label map tells you where the boundary actually falls, which is the honest version of drawing a line down the middle.
  • Component bookkeeping without a second pass. You already know blob ids for every interior pixel; you don't need a connected-components call on top.

If what you want is one mask per region, the pack has CV Labels to Masks (full size), which turns a label map into a batch of masks. That pairing is the reason to use this node rather than the label-free transform.

How the two knobs work

src is 8-bit, single-channel, binary - same strictness as the plain transform. A MASK wired in is converted for you; a float NPARRAY raises.

distanceType defaults to DIST_L2 (leave it there for mask work) and maskSize defaults to 0, OpenCV's DIST_MASK_PRECISE. See the plain cv2.distanceTransform page for what 3 and 5 do - they're the chamfer approximations, and they matter more here than usual, because approximate distances near a boundary can push a pixel into the neighbouring label.

labelType is the one that changes the meaning of the output:

  • DIST_LABEL_CCOMP (the default) gives every connected component of the zero set a single label, so all pixels of one blob share an id. This is what you want for blob territory.
  • DIST_LABEL_PIXEL gives every zero pixel its own label. Exact Voronoi cells, thousands of ids, and the label numbering becomes meaningless as a blob identity.

Inputs and outputs

Two required inputs (src, distanceType), one optional (labelType). Two outputs: dist and labels, both NPARRAY.

Neither output is an IMAGE - there's no format echo here, unlike the drawing and arithmetic wrappers - so preview them with Preview CV Array or dump them with CV Array To Text. There's a catch worth knowing before you look: the zero set (your black background) is label 0, so a raw label map previews as a mostly-black rectangle with bright blobs where the background used to be. Labels are arbitrary ids, so any colour you see is coincidence, not data. Label ids follow scan order, not size - if you want the biggest blob, reach for CV Keep Largest Component instead of hunting for the highest label.

Installing comfyui_cv

This node lives in bmad4ever/comfyui_cv: roughly 470 auto-generated raw cv2.* wrappers (built at import from OpenCV's own type stubs) plus a curated layer, forked from Gerold Meisinger's opencv-comfyui under GPL-3.0. In ComfyUI Manager, search ComfyUI CV, or:

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

Restart when it's done. Requires Python ≥ 3.12 and a recent ComfyUI on the V3 node API; the single dependency is opencv-contrib-python-headless~=5.0.0.93. Install the contrib wheel, not plain opencv-python - all four OpenCV distributions share one site-packages/cv2 and whichever was installed last wins, so a non-contrib wheel makes contrib modules disappear. The pack ships tools/repair_opencv_contrib.py --check / --apply for exactly that.

Common issues

(-215:Assertion failed) src.type() == CV_8UC1. Wrong input depth or channel count. MASK is the clean path; a float NPARRAY will not work no matter how binary it looks.

The label map looks like static. It isn't. Label 0 is the background and everything else is an arbitrary id; view it as numbers with CV Array To Text, or convert it with CV Labels to Masks.

One giant label instead of one per blob. Your blobs are touching, so they're one connected component of the background. Run a threshold or an erode to separate them first.

Labels are wrong right at the boundary. That's the chamfer approximation; switch maskSize to 0 for the exact algorithm.

Categoryimage/CV/low-level/cv2 D

Inputs (4)

NameTypeDefaultDescription
srcNPARRAY,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.
distanceTypeCOMBODIST_L2 - - -
maskSizeINT0-2147483648–2147483647 - - -
labelTypeoptCOMBODIST_LABEL_CCOMP - - -

Outputs (2)

NameTypeDescription
distNPARRAY—
labelsNPARRAY—