cv2.distanceTransform
Every pixel's distance to the nearest edge
- src
- nparray
Here's the thing nobody tells you about this node: it doesn't produce an image. It produces a field of numbers - for every white pixel in your binary mask, how many pixels away is the nearest black one. The output is a float array whose maximum is "how fat is this blob at its fattest". That's geometry, not pixels, and once you see it that way you'll find three or four uses for it in a week.
Where it earns its place
Feathered masks that aren't a blur. Blur a binary mask and you get a soft edge, but it's a soft edge that leaks in all directions and depends on how big your blur radius happens to be. A distance field gives you a graded ramp measured in pixels: divide it by some radius, clamp, and you have an alpha that reaches exactly one at the centre of a 20-pixel-thick region and zero at the boundary. That's the honest way to soften a mask.
Blob cores. Threshold the distance map and you keep the parts of each region that are at least N pixels from any edge - an erosion that doesn't chew weird shapes into thin structures. This is the classic "sure foreground" seed for watershed-style segmentation, and the distance-transform-then-threshold step is the reason it appears in every segmentation tutorial ever written.
Measurements. The peak value inside a region is the radius of the largest circle that fits in it. Stroke thickness, blob width, clearance between two shapes - all of that falls out of the map for free.
How it works, and the two knobs that change the numbers
src must be an 8-bit, single-channel, binary image - that's the tooltip's own wording and it's strict. A MASK wired in gets converted to uint8 for you, which is the path you want. An IMAGE gets reduced to gray (rarely what you want), and a float NPARRAY raises outright.
distanceType defaults to DIST_L2, the plain Euclidean distance. The others (DIST_L1, DIST_C, DIST_L12, DIST_FAIR, DIST_WELSCH, DIST_HUBER) swap in robust or Manhattan-style costs; for mask work, leave it on DIST_L2.
maskSize defaults to 0, which is OpenCV's DIST_MASK_PRECISE - the exact Euclidean algorithm. 3 and 5 are the fast chamfer approximations with a 3×3 or 5×5 neighbourhood; they're visibly blockier (you get distance values with a diamond-shaped bias). The tooltip notes that DIST_L1 and DIST_C force the mask to 3 regardless, because a bigger aperture buys nothing there.
dstType is the output depth, CV_32F by default. CV_8U is only legal with DIST_L1, per the same tooltip.
There's exactly one output, nparray. It's not an IMAGE and won't preview as one.
Getting the result somewhere visible
The distances are unbounded - a fat blob can peak at 80 - so a raw preview looks like a black rectangle with a faint smudge. Two ways out:
- Preview CV Array has a normalize mode and a heatmap mode; that's the viewer you want for a distance field.
- CV Array -> Mask min-max normalizes floats into a MASK for you, so
distanceTransform → CV Array -> Mask → Mask Bluris a complete soft-mask chain.
Typing a MASK out of it is usually the whole point, so those two nodes are the ones to learn next.
Installing the pack
This node ships inside bmad4ever/comfyui_cv, ~470 auto-generated raw cv2.* wrappers plus curated nodes - a GPL-3.0 fork of Gerold Meisinger's opencv-comfyui. In Manager, search ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart after. You need Python ≥ 3.12 and a recent ComfyUI (V3 node API). One dependency: opencv-contrib-python-headless~=5.0.0.93. Keep the contrib build - all four OpenCV wheels write into the same site-packages/cv2 and a plain opencv-python installed later quietly strips the contrib modules. python tools/repair_opencv_contrib.py --check diagnoses that; --apply fixes it.
Common issues
(-215:Assertion failed) src.type() == CV_8UC1. You fed it a float array or a 3-channel image. Convert first: the raw wrappers accept MASK directly, so go through a MASK or insert Image -> CV Array with the GRAY option.
Everything is zero. Your mask is inverted, or the mask has no white. Distance is measured from each non-zero pixel to the nearest zero pixel - the transform runs on the white stuff and reports distance to black.
Blocky, diamond-shaped distances. You're on a chamfer approximation or a DIST_L1-family type. Use DIST_L2 with maskSize 0.
Slow on a 4K mask. DIST_MASK_PRECISE is the exact algorithm and it costs more than 3 or 5; on big inputs the 5×5 approximation is usually indistinguishable for masking work.
One node, one output, and a surprising amount of downstream geometry. For the sibling that also tells you which blob each pixel belongs to, see cv2.distanceTransformWithLabels.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| src | NPARRAY,IMAGE,MASK | 8-bit, single-channel (binary) source image. 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. | |
| distanceType | COMBO | DIST_L2 | Type of distance, see #DistanceTypes |
| maskSize | INT | 0-2147483648–2147483647 | Size of the distance transform mask, see #DistanceTransformMasks. In case of the #DIST_L1 or #DIST_C distance type, the parameter is forced to 3 because a $3\times 3$ mask gives the same result as $5\times 5$ or any larger aperture. |
| dstTypeopt | COMBO | CV_32F | Type of output image. It can be CV_8U or CV_32F. Type CV_8U can be used only for the first variant of the function and distanceType == #DIST_L1. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| nparray | NPARRAY | — |