Nodes/ComfyUI CV/cv2.xphoto.dctDenoising
ComfyUI Node

cv2.xphoto.dctDenoising

It wants to know how noisy your image is, and it means it

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
cv2.xphoto.dctDenoising
  • src
  • result
◄sigma0.0000►
◄psize16►

This is a denoiser built on the frequency-domain idea that has been the backbone of image restoration for thirty years: transform a block, look at the coefficients, and throw away the small ones - because real structure concentrates into a few big coefficients while noise is spread thin across all of them. cv2.xphoto.dctDenoising does that per block, and its one interesting input is not a strength slider but sigma, the noise level you expect.

Why you'd reach for it

Two situations, and neither is "my AI image is noisy".

The first is actual sensor noise: a photo, a scanned frame, a phone shot that got pushed, or a video frame whose grain reads as digital hash rather than film. There, the noise really is roughly Gaussian and roughly known, which is exactly the assumption this algorithm makes.

The second is pre-processing before a measurement. If you're about to threshold, compute an optical-flow field, or run a feature detector over a noisy frame, a denoise pass upstream changes what gets detected. That's the same "cheap deterministic primitive first" logic that runs through the whole post-processing layer - reach for the filter before you reach for a model.

What it is not good for: cleaning up diffusion noise or compression blocks. Both are structured, neither is Gaussian, and a DCT threshold will smear the first and ring around the second.

How it works

The image is cut into psize×psize blocks (16 by default) and each is transformed with a DCT. Coefficients below a threshold derived from sigma are zeroed - the noise falls away, the strong structural coefficients survive - then the inverse transform puts the block back. Larger blocks model smooth regions better but ring near hard edges, which is the classic block-transform trade-off and the reason psize exists as a knob at all.

The inputs that matter

  • src - the image. NPARRAY, IMAGE or MASK; batches go through frame by frame.
  • sigma - expected noise standard deviation, in 0–255 intensity units. The pack's own note says 10–20 suits typical camera noise. Default is 0, i.e. "there is no noise", i.e. nothing much happens. If the node looks like it's not working, this is why.
  • psize (optional) - the block size, preset to OpenCV's default of 16. It shows up as an advanced input; most people never touch it.

Output: result, echoing src's format. Low-level wrappers emit NPARRAY, so convert with CV Array → Image before it meets a normal image node.

Tuning sigma without guessing

You don't need a noise meter. Run the node on a patch of flat sky or wall and sweep sigma in a few steps: too low and the grain is still there, too high and the flat area goes waxy and the edges start showing halo rings. Middle of those two failure points is right. After that, verify the image at 200% rather than full-frame - DCT artifacts are invisible zoomed to fit and obvious up close, and up close is where a later crop or upscale will find them.

Install

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

Restart ComfyUI, or install through ComfyUI Manager by searching ComfyUI CV. Requires opencv-contrib-python-headless~=5.0.0.93 (xphoto is contrib-only - no contrib wheel, no node), plus Python 3.12+ and a ComfyUI recent enough to have the V3 node API. Two install-time footguns worth knowing: an opencv-python wheel installed over the contrib one empties site-packages/cv2's contrib submodules and the node vanishes, which tools/repair_opencv_contrib.py --check will confirm; and the pack ships no models, so nothing here is downloading anything for you.

Traps and gotchas

  • sigma = 0 is the "does nothing" bug. Give it a real number.
  • It's CPU and it's not fast. A few seconds on a 2K frame depending on block size and machine; batch of 30 video frames and you'll feel it.
  • It denoises in the colour the input is in. The wrapper feeds it three-channel BGR, so the colour channels get transformed together - on strongly coloured noise (chroma speckle) this is fine, but it means the result is only as good as the assumption that noise is uniform across channels. Channel-wise noise (typical of sensors at high ISO) can leave a colour cast behind.
  • Don't stack it. Two DCT passes don't give you a stronger denoise, they give you a softer image and more ringing. If one pass isn't enough, the honest move is a different tool or a different sigma, not a second node.
  • The pack is new and explicitly LLM-assisted; the author's own README says verify logic yourself before relying on it. For a denoise pass you'd notice a bug anyway - the output would be wrong, not subtly wrong.
Categoryimage/CV/low-level/xphoto

Inputs (3)

NameTypeDefaultDescription
srcCOMFY_MATCHTYPE_V3source image 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.
sigmaFLOAT0.0000-1e+38–1e+38expected noise standard deviation
psizeoptINT16-2147483648–2147483647size of block side where dct is computed Preset to the OpenCV default (16).

Outputs (1)

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