cv2.ximgproc.dtFilter
Edge-preserving smoothing that doesn't care how big sigma is
- guide
- src
- result
The bilateral filter's dirty secret is cost. Its quality knob - the spatial sigma - directly multiplies the work, so the settings that look best are the ones that take longest. The domain transform (Gastal & Oliveira, 2011) sidesteps this by transforming the image into a space where distances along an edge-aware curve become 1-D, then filtering that. The result: cost essentially independent of sigma, and a filter that looks a lot like a bilateral.
That's why dtFilter is the one to reach for when you need edge-preserving smoothing inside a loop - per-frame over a video, per-tile over a big upscale, in a batch. Where bilateralFilter gets expensive, this one stays flat.
Inputs
guide- "guided image (also called as joint image) with unsigned 8-bit or floating-point 32-bit depth and up to 4 channels". A match-type socket:IMAGE/MASK/NPARRAYin, same format out. Self-guided (guide = src) is plain smoothing; guide = a cleaner or structural image is joint filtering.src- "filtering image with unsigned 8-bit or floating-point 32-bit depth and up to 4 channels".sigmaSpatial- the spatial sigma, in pixels. This is the one that doesn't blow up your runtime. Bigger = broader smoothing.sigmaColor- "similar to the sigma in the color space into bilateralFilter". It's in your data's units, which for a ComfyUIIMAGEmeans 8-bit 0–255 - think tens, not 0.1.mode- one ofDTF_NC(default, normalised convolution),DTF_RF(recursive filtering) orDTF_IC(interpolated convolution). The tooltip says they "correspond to three modes for filtering 2D signals in the article". Start on the default;DTF_RFis the cheap recursive one if you're pushing speed.numIters(default 3) - the tooltip's own verdict is "3 is quite enough", and it's right. Each iteration sharpens the edge-awareness; past three you're paying for nothing.
Output is a single value echoing the guide's format.
The batch story, which is unusually good here
This node is one of the contrib filters the pack treats as per-frame batch safe, and for a specific reason worth knowing: when you feed a batch to both guide and src, frame i of the guide is paired with frame i of the source. So a 12-frame noise-reduction pass filters frame by frame in one node execution and comes back as a 12-frame batch - no unstack/restack dance. Pass the same batch to both sockets for self-guided smoothing, or two paired batches for genuine joint filtering.
Where it fits
Two jobs it's good at:
Denoise before upscaling. The single biggest quality win in any upscale is not amplifying noise. A domain-transform pass over the frames first keeps edges crisp while removing the grain an upscaler would happily turn into plastic.
Structure/detail separation. Subtract the smoothed version from the original (cv2_subtract) to get a detail layer, then recombine with cv2_addWeighted (the pack's filter sheet uses amount 1, offset −1, bias 128 to show the detail layer). Once separated you can sharpen the detail layer rather than the image, which is what most "professional sharpening" actually is.
And if you can't tell whether your settings helped: CV Quality Compare scores two images with SSIM and returns a per-pixel map of where they differ. Use it. Eyes prefer smoother; SSIM tells you which one kept the structure.
Sibling filters in the same module
guidedFilter (local linear fit; watch eps, it's a variance in squared units of your pixel scale), bilateralTextureFilter (texture-vs-structure), l0Smooth (flat regions, hard steps), fastGlobalSmootherFilter (global WLS), weightedMedianFilter (specular noise), rollingGuidanceFilter (scale-space), amFilter (adaptive manifold). The pack's 10_ximgproc_edge_aware_filters example runs six of them on the same photo with scores next to each - the fastest way to develop an intuition for which is which.
Installing it
Contrib module, inside ComfyUI CV. Manager → search ComfyUI CV, 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.
What goes wrong
- Nothing happens.
sigmaColorset for a 0–1 scale while the data is 0–255 (or the reverse). Confirm the scale withInspect CV Data. - A halo along high-contrast edges. Too many iterations at a big spatial sigma. Back off
numItersto 1 and see what changes. - Guide/source size mismatch. They have to match; if your guide came from a different branch of the graph, resize or crop one of them.
ximgprocempty in your menu. Contrib-only submodule and one sharedsite-packages/cv2for all fouropencv-python*wheels - a non-contrib install silently empties it.tools/repair_opencv_contrib.py --checkin the pack repo diagnoses it.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| guide | COMFY_MATCHTYPE_V3 | guided image (also called as joint image) with unsigned 8-bit or floating-point 32-bit depth and up to 4 channels. 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. | |
| src | NPARRAY,IMAGE,MASK | filtering image with unsigned 8-bit or floating-point 32-bit depth and up to 4 channels. 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. | |
| sigmaSpatial | FLOAT | 0.0000-1e+38–1e+38 | ${\sigma}_H$ parameter in the original article, it's similar to the sigma in the coordinate space into bilateralFilter. |
| sigmaColor | FLOAT | 0.0000-1e+38–1e+38 | ${\sigma}_r$ parameter in the original article, it's similar to the sigma in the color space into bilateralFilter. |
| modeopt | COMBO | DTF_NC | one form three modes DTF_NC, DTF_RF and DTF_IC which corresponds to three modes for filtering 2D signals in the article. |
| numItersopt | INT | 3-2147483648–2147483647 | optional number of iterations used for filtering, 3 is quite enough. Preset to the OpenCV default (3). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'guide' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |