cv2.boxFilter
The same blur as cv2.blur, plus the option to *sum* instead of average
- src
- ksize
- anchor
- result
With normalize left on, cv2.boxFilter is literally the same operation as cv2.blur: the mean of a rectangular window. So why does the node exist in your menu? Because of the two switches cv2.blur doesn't have - turn normalization off and you get the windowed sum, and the ddepth dropdown lets that sum live in a wider type than the input. That's not a blur any more. That's a local-accumulation primitive, and it's cheap.
It ships in comfyui_cv, the pack that wraps ~470 raw cv2.* functions (forked from geroldmeisinger's opencv-comfyui, rewritten on ComfyUI's V3 node API). One caveat the author states himself: the pack is heavily LLM-assisted and the raw wrappers are uncurated - you're getting the OpenCV call with ComfyUI sockets, so the OpenCV docs are your second reference.
How it works
A box filter is a sliding-window sum over a (w, h) rectangle, separable and implemented with an integral image, so it's constant-time in kernel size. One pass over the image precomputes prefix sums; every window after that is four adds. A 101×101 box filter on a 4K frame costs about the same as a 3×3.
Normalized (default), each window is divided by its area - a mean, i.e. a box blur, and the reason it's O(1) is also the reason it looks slightly hazy and boxy compared to a Gaussian. Unnormalized, you get the raw sum, and a 5×5 window over 8-bit pixels can reach 25×255 = 6375 - which overflows uint8 and wraps. That's the entire story of this node's ddepth dropdown.
Inputs and outputs
src decides the output format: it's a match type, so IMAGE in → IMAGE out, MASK in → MASK out, NPARRAY stays NPARRAY (and a LATENT is processed in latent space, frame 0 as a float32 [H,W,C] array with values untouched).
src(required) - the input.ddepth(required, default"same as input") - the output depth. Options runsame as input,CV_8U,CV_8S,CV_16U,CV_16S,CV_32S,CV_32F,CV_64F; "same as input" maps to OpenCV's-1. This is the field you must change when you switch normalization off - a wide type means your sums don't wrap.CV_32Fis the usual pick.ksize(required, default(0,0)) - one compositeCV_TUPLE,(w, h). Type it in the widget or drive it fromCV Tuple. Zero is not a valid kernel.anchor(optional, default(-1,-1)) - kernel center. Leave it.normalize(optional, defaulttrue) - divide by the window area, or don't.borderType(optional, defaultBORDER_DEFAULT) - how pixels outside the frame are extrapolated; the author's tooltip notesBORDER_WRAPisn't supported.
One output, result, echoing src's format.
What each mode is good for
Normalized is mask feathering and cheap softening - see post-processing for why a blur is also the base layer of an unsharp mask (blur a copy, subtract, add the difference back scaled; "sharpen" is never anything more than that). It's also the fastest big-radius smear you have, which is what makes it good enough for a glow pass if you don't want to pay for a Gaussian.
Unnormalized with a float depth is the interesting one: a local density map. Box a bright mask and you get "how much bright stuff is in this neighbourhood", which is a one-node background estimate or a poor man's adaptive threshold. Big windows are free, so this is a genuinely fast way to get a coarse heat map of where things are in a frame - then send it to Preview CV Array in heatmap mode.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart, or Manager → search "comfyui_cv". Python ≥ 3.12, recent ComfyUI on the V3 node API, and the pack's single real dependency:
pip install "opencv-contrib-python-headless~=5.0.0.93"
Installing a non-contrib opencv-python wheel on top of that silently empties the shared cv2 and the contrib nodes disappear; tools/repair_opencv_contrib.py in the pack diagnoses and repairs it. No models needed.
Common issues
The image looks like a psychedelic mess. You turned normalize off and left ddepth on "same as input", so your windowed sums are wrapping around inside uint8. Set ddepth to CV_32F.
Everything is black. Either ksize is (0,0) (which OpenCV rejects with an assertion rather than a black image, so this is less likely than it sounds) or you set a wide ddepth and something downstream is treating the float array as an image. A CV_32F array with values in the thousands is not displayable - normalize it, or threshold it.
The blur is lopsided. ksize is (w, h); swap the numbers.
It's identical to cv2.blur and you feel cheated. It is, default for default. The difference is the two options above, and if you don't need them, cv2.blur (or the pack's cv2.GaussianBlur) is the friendlier-looking node.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| src | COMFY_MATCHTYPE_V3 | input image. 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. | |
| ddepth | COMBO | same as input | the output image depth (-1 to use src.depth()). |
| ksize | CV_TUPLE | 0,0 | blurring 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. |
| anchoropt | CV_TUPLE | -1,-1 | anchor 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. |
| normalizeopt | BOOLEAN | true | flag, specifying whether the kernel is normalized by its area or not. Preset to the OpenCV default (True). |
| borderTypeopt | COMBO | BORDER_DEFAULT | border mode used to extrapolate pixels outside of the image, see #BorderTypes. #BORDER_WRAP is not supported. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |