Nodes/ComfyUI CV/cv2.blur
ComfyUI Node

cv2.blur

A mask feather that costs nothing

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
cv2.blur
  • src
  • ksize
  • anchor
  • result
◄borderTypeBORDER_DEFAULT►

cv2.blur is the mean of a rectangle of pixels. No Gaussian, no weighting, no cleverness - a sliding-window average, and OpenCV computes it with an integral image so the cost is the same whether your kernel is 3×3 or 301×301. That "O(1) in kernel size" property is the whole reason it's still useful, and it's the difference between it and the pack's cv2.GaussianBlur.

The node ships in comfyui_cv - a pack wrapping ~470 raw cv2.* functions plus curated nodes, forked from geroldmeisinger's opencv-comfyui and rewritten on ComfyUI's V3 node API. The author is upfront that the pack is heavily LLM-assisted, updates aren't scheduled, and the raw wrappers are uncurated: you get the OpenCV call with typed ComfyUI sockets and not much hand-holding. (It has effectively no Reddit footprint - searching the corpus for the pack name returns nothing, and the author's only other ComfyUI pack, a cartesian-product list node, is from 2024. Read the source, not the discourse.)

What people use it for in a graph

Masks, mostly. A hard-edged binary mask put into an inpainting or detail pass gives you a visible rectangle of "the model did something here"; blurring the mask first is how you get a feathered transition, and this is the cheapest way to do it. Same trick as the feathering step in detect → crop → re-render → paste-back, just implemented with a filter instead of a mask-blur node.

Second use: the low-pass half of a high-pass. Blur a copy, subtract it, add the difference back scaled - that's an unsharp mask, and post-processing makes the point that "sharpen" is never anything else. Third: pre-blur before an edge detector (feeding a slightly smoothed image to cv2.Canny cuts the noise-induced edge spaghetti), and as the bright-pass filter in a bloom recipe.

The trap in a box blur is that the kernel is a hard rectangle, so the result has a boxy, slightly ringing character that reads as a haze rather than a soft falloff. For a glow you'll want a Gaussian. For "stop the mask edge from being a knife", a box is invisible and free.

How it works and what to set

One input decides everything: src is a match type, so IMAGE in → IMAGE out, MASK in → MASK out, NPARRAY stays NPARRAY. It also accepts a LATENT, which the pack processes in latent space - frame 0 becomes a float32 [H,W,C] array with values untouched, so a latent blur is a real latent blur and not a decoded round trip through uint8.

  • src (required) - the image, mask, latent or array to smooth.
  • ksize (required, default (0,0)) - the kernel, as one composite CV_TUPLE value in (w, h) order. It travels as a whole, so you either type the two numbers into the widget or wire CV Tuple in. (0,0) is not a default to leave alone; OpenCV rejects a zero kernel.
  • anchor (optional, default (-1,-1)) - the kernel's anchor point; (-1,-1) means center, which is what you want 99% of the time.
  • borderType (optional, default BORDER_DEFAULT) - how pixels outside the frame are extrapolated. Note the author's tooltip: BORDER_WRAP is not supported here.

Output is a single socket, result, echoing src's format. Channels are filtered independently, so a 3-channel image is three separate 2-D blurs.

Install

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

Restart, or use ComfyUI Manager (search "comfyui_cv"). Requirements are Python ≥ 3.12, a recent ComfyUI on the V3 API, and:

pip install "opencv-contrib-python-headless~=5.0.0.93"

Nothing else downloads; there are no models behind this node.

Common issues

"The function/feature is not implemented" or an assertion on ksize. You left ksize at (0,0). Set it to something like (9,9).

Blurring an IMAGE costs you precision. Image inputs are converted to uint8 BGR and back, so a heavily blurred IMAGE is quantized at 8 bits per channel. On a MASK, a LATENT or an NPARRAY you skip that round trip entirely - which is another reason this node lives in mask work.

A huge kernel on a big frame is instant, and that's not a bug. The integral-image implementation doesn't care. It also means a 301×301 blur is not "expensive enough to be safe" - it will happily smear your whole image into a flat field.

The blur is lopsided. You gave the kernel (w, h) in the wrong order - a non-square box filter is asymmetric and it shows.

BORDER_WRAP raises. It's documented as unsupported for these filters; use the default or a reflect mode.

Categoryimage/CV/low-level/cv2 B

Inputs (4)

NameTypeDefaultDescription
srcCOMFY_MATCHTYPE_V3input image; it can have any number of channels, which are processed independently, but the depth should be 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.
ksizeCV_TUPLE0,0blurring kernel size. One value with 2 components (w, h) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place.
anchoroptCV_TUPLE-1,-1anchor point; default value Point(-1,-1) means that the anchor is at the kernel 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.
borderTypeoptCOMBOBORDER_DEFAULTborder mode used to extrapolate pixels outside of the image, see #BorderTypes. #BORDER_WRAP is not supported.

Outputs (1)

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