cv2.dft
Into the frequency domain without leaving ComfyUI
- src
- nparray
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:
- Without
DFT_COMPLEX_OUTPUTyou 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. - 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 theDFT_SCALEand your round trip comes back N times too bright, which looks like a mystery brightness bug rather than a maths bug. DFT_ROWStransforms 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.- Size matters, and not monotonically. Pad to a size the FFT likes -
cv2.getOptimalDFTSizeandcv2.copyMakeBorderare 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) orCV Cast Array. - Round trip comes back brighter or darker. Missing
DFT_SCALEon the inverse, orDFT_SCALEwithout 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/divSpectrumsflags have to match how the forwarddftwas run - whole 2-D spectrum orDFT_ROWSper-row. Mismatch there produces a result that looks like a plausible image and is wrong.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| src | NPARRAY | input array that could be real or complex. A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here. | |
| flagsopt | STRING | none (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. |
| nonzeroRowsopt | INT | 0-2147483648–2147483647 | when 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)
| Name | Type | Description |
|---|---|---|
| nparray | NPARRAY | — |