Nodes/ComfyUI CV/cv2.ximgproc.weightedMedianFilter
ComfyUI Node

cv2.ximgproc.weightedMedianFilter

The denoiser that won't eat your edges — if you feed it a guide

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
cv2.ximgproc.weightedMedianFilter
  • joint
  • src
  • mask
  • result
◄r0►
◄sigma25.5000►
◄weightTypeWMF_EXP►

A plain median filter removes noise by replacing every pixel with the median of its neighbours, which is why it also erases eyelashes, brick mortar and anything else thinner than the window. This one is weighted, and the weights come from a second image - the joint (guidance) input. Where the guide says "these two pixels look alike", the median leans that way. That is the whole trick: structure is preserved where the guide has structure, and smoothed where it doesn't.

Why you'd reach for it

This is a cv2.ximgproc function, so it's only in the contrib build of OpenCV, and it's the classic tool for edge-preserving smoothing without a model. Two honest uses in a ComfyUI graph:

  • Noise reduction on a photo or a scanned frame where a Gaussian blur would turn it to mush - especially on skin, where bilateral gets plasticky.
  • Smoothing a render while keeping the guide's structure. Point joint at a clean reference (another frame, a depth map, a line drawing) and src at the noisy one, and edges land where the guide says they are, not where the noise says they are.

Like the rest of the deterministic post-processing layer, it costs no VRAM and no seed lottery. It is also pure CPU and roughly O(r²) per pixel, so it's not free in wall-clock time.

How it works

Each output pixel becomes a weighted median of the (2r+1)×(2r+1) neighbourhood in src. The weight of a neighbour is its similarity to the centre pixel measured in the joint image: with the default WMF_EXP, exp(-|J(neighbour) − J(centre)|² / sigma²). Pixels whose guide value is close get a big say; pixels across an edge get almost none. So the filter can't smear across a boundary the guide can see. WMF_JAC/WMF_IV1/WMF_IV2/WMF_COS swap that weighting function, and WMF_OFF makes it an ordinary median - handy as a control to see what the weights were actually buying you.

The inputs that matter

  • joint - the guidance image, and the format-echoing input: it also decides what type result comes back as. Feed it your clean reference, or wire the same image in twice for self-guided smoothing.
  • src - the image to filter. Takes NPARRAY, IMAGE or MASK; the wrapper uses frame 0 of a batch (or the whole batch for arithmetic-style ops).
  • r - the filter radius. It defaults to 0, which does nothing at all. Set it: 3–5 for light cleanup, 8–12 when you actually want the painterly smoothing. Cost climbs fast.
  • sigma (optional, advanced) - the falloff of the similarity weight, preset to OpenCV's own default of 25.5. Larger means "more colours count as similar", so more of the neighbourhood gets a vote; lower makes the filter hug the guide's edges harder.
  • weightType - the weighting function, WMF_EXP by default. Leave it alone unless you're comparing.
  • mask (optional) - restrict filtering to where it's non-zero. This is how you smooth only the sky, or only the skin.

Output: one socket, result, echoing joint's format. Because this is a low-level wrapper it hands back a raw NPARRAY, not an IMAGE - pipe it through CV Array → Image to get back into the normal graph, or into Preview CV Array if you just want to see it.

Install

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

then restart ComfyUI - or search ComfyUI CV in ComfyUI Manager. The one hard dependency is opencv-contrib-python-headless~=5.0.0.93, and the contrib part is not optional here: ximgproc lives there and nowhere else. If you install a plain opencv-python wheel over the contrib one, all four distributions share a single site-packages/cv2 and the contrib submodules silently empty out - this node just disappears. tools/repair_opencv_contrib.py --check tells you if that happened; --apply fixes it. Python 3.12+ and a recent ComfyUI (V3 node API) are required.

Traps

  • r = 0 is the reported "node does nothing" bug, every time. It's a required input with a useless default; the pack's other nodes preset OpenCV's defaults, this one can't (upstream's default really is 0).
  • Slow at big radii. r=10 on a 4K frame is a coffee break. Crop or downscale first.
  • src as a MASK arrives single-channel, which is fine, but the result comes back as NPARRAY - remember to convert before wiring it into a mask-consuming node.
  • The pack itself is new, LLM-authored and open about it: the author says don't ship it to production without reading the code and that updates aren't planned. For a filter wrapper that's a low-stakes disclaimer; just don't expect a maintainer to answer your issue.
Categoryimage/CV/low-level/ximgproc

Inputs (6)

NameTypeDefaultDescription
jointCOMFY_MATCHTYPE_V3 The image output(s) echo this input's format. 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.
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.
rINT0-2147483648–2147483647 - - -
sigmaoptFLOAT25.5000-1e+38–1e+38 - - - Preset to the OpenCV default (25.5).
weightTypeoptCOMBOWMF_EXP - - -
maskoptNPARRAY,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.

Outputs (1)

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