Nodes/ComfyUI CV/cv2.dilate
ComfyUI Node

cv2.dilate

The mask-grower that eats hairline seams

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.dilate
  • src
  • kernel
  • anchor
  • result
◄iterations1►
◄borderTypeBORDER_DEFAULT►
◄borderValue►

Dilation is the oldest trick in the mask toolbox: make the white part bigger. That's it. And that one sentence covers 90% of why anyone opens this node - the remaining 10% is people who pointed it at a photograph, saw the image get blotchy, and assumed it was broken.

What you actually use it for

Your inpaint boundary leaves a rim of the original edge showing. Your paste has a one-pixel hairline where the source and the target don't quite meet. Your matte clipped the hair. The fix in all three cases is the same: grow the mask four to eight pixels so the new content overlaps the old edge instead of butting against it. The KB's docs/knowledge/inpainting.md lists mask blur and feathering as the standard blend step; dilation is the other half of that job - blur softens the boundary, dilate moves it.

You can also use it on a grayscale image, where dilation becomes "brighten locally": every pixel takes the maximum value in its neighbourhood, so highlights spread. That's genuinely useful (soft glow, bloom-ish passes) and also why the node on a colour photo looks like it smeared the bright bits around. That's not a bug, that's a per-channel max filter.

How it works

Each output pixel is the maximum of the input pixels covered by the structuring element - the kernel - centred on it, with the anchor deciding where "centred" is. iterations repeats the operation: one pass with a 3×3 kernel grows a circle only about a pixel, three passes roughly three pixels. It's cheap, but it is linear in iterations, so 40 iterations is 40 full passes.

The kernel is not an image. The schema types it NPARRAY and the tooltip says so plainly - a data array of points. Wire it from cv2.getStructuringElement in the same pack, whose ksize is split into width/height widgets and whose shape dropdown picks MORPH_RECT, MORPH_ELLIPSE or MORPH_CROSS. A cross grows in a plus, an ellipse grows round, a rectangle grows square-cornered - and yes, that difference is visible after five iterations.

The inputs that matter

src takes an IMAGE, a MASK or an NPARRAY, and the result echoes whatever you fed it - MASK in, MASK out. A batch of IMAGE frames is processed frame by frame and restacked, so you can grow whole clips in one go. LATENT works too, on frame 0, values untouched.

iterations is the one you'll reach for constantly, and it's the honest cost knob.

anchor is a CV_TUPLE, two components that travel together, default (-1, -1) meaning "middle of the kernel". You can type it or wire it from CV Tuple. Leave it alone unless you're deliberately growing in one direction.

borderType picks how pixels outside the frame are extrapolated; BORDER_WRAP isn't supported and the tooltip says so. borderValue is a cv2 Scalar written as a literal - "(0, 255, 0)" for green BGR, "255" to broadcast to every component - and blank means OpenCV's default. Unless your mask touches the frame edge you'll never touch either.

The only output is result.

Installing comfyui_cv

This is one node from bmad4ever/comfyui_cv - roughly 470 auto-generated raw cv2.* wrappers plus a layer of curated nodes, forked from Gerold Meisinger's opencv-comfyui (whose "I converted all of OpenCV to ComfyUI custom nodes" announcement still stands as the one time this idea hit the front page). Install it like anything else: in ComfyUI Manager, search ComfyUI CV and install, or:

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

Restart ComfyUI. The pack needs Python ≥ 3.12 and a recent ComfyUI on the V3 node API, and its one real dependency is opencv-contrib-python-headless~=5.0.0.93. Contrib matters. All four OpenCV wheels share one site-packages/cv2, last install wins, and a non-contrib wheel silently empties the contrib submodules. If nodes vanish, the pack ships a fixer:

python tools/repair_opencv_contrib.py --check     # diagnose
python tools/repair_opencv_contrib.py --apply     # repair, then restart

Common issues

It ran and nothing changed. You dilated a black mask, or the wrong polarity mask. Check the source is white-on-black where you want growth.

"error: (-215) ... in function 'normalizeAnchor'" and similar. Your structuring element is degenerate - a ksize of 0 in either component gives you an empty kernel. Give it real dimensions.

Rounded corners. You used MORPH_RECT and got a square grow. Switch to MORPH_ELLIPSE.

Blotchy colour. Dilation on a photo is a max filter per channel; it bleeds the bright pixels' colour sideways. Use CV Gaussian Blur or cv2.dilate with a one-pixel element if you only wanted to thicken lines.

When you don't need this node at all: if all you want is "+4 pixels on a MASK", core's GrowMask does exactly that in one node. Reach for cv2.dilate when you want a specific element shape, per-frame behaviour on an IMAGE batch, or to dilate something that isn't a mask.

Failures come back as a RuntimeError naming the function and the input shapes, so you usually get a real clue instead of a red node with no text.

Categoryimage/CV/low-level/cv2 D

Inputs (6)

NameTypeDefaultDescription
srcCOMFY_MATCHTYPE_V3input image; the number of channels can be arbitrary, but the depth should be one of CV_8U, CV_16U, CV_16S, CV_32F or CV_64F. The image output(s) echo this input's format. A LATENT link is processed in latent space: frame 0 becomes a float32 [H,W,C] array (any channel count), values untouched. Arithmetic ops (add, multiply, etc.) also accept a full LATENT batch ({samples: [B,C,H,W]}) — the whole batch flows through when both inputs have the same batch size. 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.
kernelNPARRAYstructuring element used for dilation; if `kernel=Mat()`, a `3 x 3` rectangular structuring element is used. Kernel can be created using #getStructuringElement A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
anchoroptCV_TUPLE-1,-1position of the anchor within the element; default value (-1, -1) means that the anchor is at the element center. One value with 2 components (x, y) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place.
iterationsoptINT1-2147483648–2147483647number of times dilation is applied. Preset to the OpenCV default (1).
borderTypeoptCOMBOBORDER_DEFAULTpixel extrapolation method, see #BorderTypes. #BORDER_WRAP is not supported.
borderValueoptSTRINGborder value in case of a constant border cv2 Scalar as a literal, e.g. "(0, 255, 0)" (BGR) or "(0, 255, 0, 64)" (BGRA). A bare number broadcasts to every component, so "255" means (255, 255, 255, 255). Components past the target's channel count are ignored by OpenCV. Leave blank for the OpenCV default.

Outputs (1)

NameTypeDescription
resultCOMFY_MATCHTYPE_V3Echoes the 'src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY.