ComfyUI Node

cv2.dft

Into the frequency domain without leaving ComfyUI

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.dft
  • src
  • nparray
◄flagsnone (0)►
◄nonzeroRows0►

The discrete Fourier transform turns an image into its spectrum: every pixel becomes a complex number describing how much of a particular spatial frequency is present. It's the door to the frequency-domain tricks people love - killing periodic noise, matching templates by correlation, hunting compression fingerprints - and this pack exposes cv2.dft as a node, along with the smaller pieces you need to actually build a usable pipeline out of it.

The mechanism, and the four things that surprise people

cv2.dft takes a floating-point array (real, or complex if you say so) and returns the transform. That "floating-point" is non-negotiable: uint8 input raises. In this pack you get there with Image → CV Array using the float32 dtype option, or CV Cast Array.

The flag set is the part you have to think about. flags is a string widget rendered as toggles, pipe-joined - none (0) | DFT_COMPLEX_OUTPUT - and the pack even ships a CV DFT Flags builder node so you don't have to remember the spelling. The options are DFT_COMPLEX_OUTPUT, DFT_REAL_OUTPUT, DFT_SCALE, DFT_INVERSE, DFT_ROWS and DFT_COMPLEX_INPUT, and the traps are:

  1. Without DFT_COMPLEX_OUTPUT you get the compact CCS layout, not a nice two-channel (real, imaginary) spectrum. The pack's own tooltip calls the complex layout "the format the spectrum tools (magnitude, mulSpectrums, masking) expect". If you're planning to touch coefficients, ask for it explicitly.
  2. cv2 does not scale the inverse by default. The integer-divide-by-N you'd expect from a textbook inverse DFT is opt-in. The pack says it plainly: the usual inverse choice is DFT_SCALE | DFT_REAL_OUTPUT - "divide by N (cv2 skips this by default!) and return a real image instead of a 2-channel spectrum". Skip the DFT_SCALE and your round trip comes back N times too bright, which looks like a mystery brightness bug rather than a maths bug.
  3. DFT_ROWS transforms each row independently. That's the 1-D flavour, and if you use it anywhere in a chain the other spectrum functions (mulSpectrums, divSpectrums) have to match - the pack's tooltip warns about exactly this.
  4. Size matters, and not monotonically. Pad to a size the FFT likes - cv2.getOptimalDFTSize and cv2.copyMakeBorder are both in this pack. A power-of-two-ish array is dramatically faster than a prime-ish one, and the pad also moves wrap-around artefacts out of the way.

nonzeroRows (default 0) is the efficiency knob for DFT-based correlation: tell it only the first N rows are non-zero and OpenCV skips the rest.

The output is a single NPARRAY, the same size as the input - two interleaved channels for a complex spectrum, one for a real one.

Making the spectrum visible

The DC term is orders of magnitude bigger than everything else, so a naive abs → image gives you one white pixel and black everywhere else. The chain is: cv2.dft with DFT_COMPLEX_OUTPUT → cv2.magnitude (or cartToPolar for phase) → cv2.log to compress the range → CV Array → Image, which min-max normalises into viewable 8-bit.

What it's actually for

Periodic noise removal, the classic: a sensor or scanner artefact shows up as a handful of bright spikes in the spectrum. Mask them out, inverse-transform, and the striping is gone - surgery on the actual image rather than a re-generation. Correlation: matchTemplate works in the spatial domain, but the FFT path with mulSpectrums is the classic fast convolution, and it's the same trick cv2.phaseCorrelate uses to find sub-pixel shift. Comparison and forensics - spectra tell you if two images share a camera or a compression history in a way pixel comparison doesn't.

The KB's post-processing layer has a useful frame for this: it's the cheap deterministic primitive, so reach for it before you reach for a diffusion pass. Killing a scanline pattern or aligning two frames is not a generative problem. (For a decorative frequency-domain effect rather than analysis, this pack also wraps cv2.ximgproc.qdft, the quaternion DFT - that's where the weird colour-spectrum visuals live.)

Install

Manager → Install Custom Nodes → ComfyUI CV (publisher bmad4ever), or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"

Restart ComfyUI. Python ≥ 3.12 and a recent V3-API ComfyUI; the contrib headless OpenCV wheel is the only dependency and dft is core, so no models are needed. Keep the contrib build anyway - all four OpenCV distributions share one site-packages/cv2, so a plain opencv-python install over it silently empties the contrib submodules (python tools/repair_opencv_contrib.py --check, then --apply). The pack is GPL-3.0, a fork of geroldmeisinger/opencv-comfyui, largely LLM-authored, and not production-ready by its author's own account.

Common issues

  • "Unsupported depth" on execution. Integer input. Go through Image → CV Array (float32) or CV Cast Array.
  • Round trip comes back brighter or darker. Missing DFT_SCALE on the inverse, or DFT_SCALE without a matching forward transform.
  • The spectrum preview is a single white dot. You skipped the log step. Magnitudes span too many orders of magnitude for a linear display.
  • Spectrum math gives nonsense. mulSpectrums / divSpectrums flags have to match how the forward dft was run - whole 2-D spectrum or DFT_ROWS per-row. Mismatch there produces a result that looks like a plausible image and is wrong.
Categoryimage/CV/low-level/cv2 D

Inputs (3)

NameTypeDefaultDescription
srcNPARRAYinput array that could be real or complex. A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
flagsoptSTRINGnone (0)transformation flags, representing a combination of the #DftFlags cv2.dft flags: one of none (0) plus any of DFT_COMPLEX_OUTPUT, DFT_REAL_OUTPUT, DFT_SCALE, DFT_INVERSE, DFT_ROWS, DFT_COMPLEX_INPUT, pipe-joined (e.g. "none (0) | DFT_COMPLEX_OUTPUT"). In the UI this renders as a dropdown with one toggle per flag.
nonzeroRowsoptINT0-2147483648–2147483647when the parameter is not zero, the function assumes that only the first nonzeroRows rows of the input array (#DFT_INVERSE is not set) or only the first nonzeroRows of the output array (#DFT_INVERSE is set) contain non-zeros, thus, the function can handle the rest of the rows more efficiently and save some time; this technique is very useful for calculating array cross-correlation or convolution using DFT. Preset to the OpenCV default (0).

Outputs (1)

NameTypeDescription
nparrayNPARRAY—