Nodes/ComfyUI-BCNodes/Frequency Merge
ComfyUI Node

Frequency Merge

The Face of One Upscale, the Skin Texture of Another

By 0xBeycan·Created about a month ago·Updated 13 days ago· 2
Frequency Merge
  • base
  • detail
  • image
◄split_sigma3.0►
◄detail_strength1.00►
◄devicecpu►

Here's the SeedVR2 problem in one sentence: at downscale_factor 1 it keeps a face exactly right but the skin comes out plastic, and at a lower factor you get pores and fine texture but a face that's been subtly rewritten. Frequency Merge is the node that lets you stop choosing. Take the structure from one and the detail from the other.

The mechanism

It's frequency separation, and the maths is short enough to hold in your head:

image = G(base) + detail_strength × (detail − G(detail))

G is a Gaussian blur with sigma split_sigma in pixels, reflect-padded at the borders. So G(base) is base's low frequencies - shapes, shading, colour - and detail − G(detail) is detail's high frequencies: texture, fine grain, edge detail. Add them, clamp to [0, 1], done.

Gaussian specifically, because it doesn't ring. A hard frequency cut-off puts halos around every edge; a Gaussian divides smoothly, the same in every direction, and it's exact - which is also why feeding the same image into both inputs gives you that image back, up to float rounding.

Inputs and outputs

  • base - supplies the low frequencies. In the SeedVR2 workflow the author describes, this is the downscale_factor 1 output: its face is right.
  • detail - supplies the high frequencies, at the same size and image count as base. The lower-factor output, full of skin texture.
  • split_sigma - default 3, range 0.5 to 64. The tooltip has the useful number in it: a pattern repeating every 5.3 × split_sigma pixels is split half and half. At 3, that's about a 16 px period. Finer than that comes from detail; coarser from base. Raise it to take more of detail; keep 5.3 × split_sigma well below the size of anything that must not change - eyes, mouth, the line of the jaw.
  • detail_strength - default 1, range 0 to 2. Gain on the high frequencies. At 0 you get the blurred base and nothing else; above 1 you get something sharper than detail itself.
  • device - cpu (default) or gpu.
  • Output: image, float16 only when both inputs are float16 (which is what SeedVR2 PostProcess hands you), float32 otherwise. Alpha is dropped.

The workflow it's built for

Run SeedVR2 twice from the same source, at two downscale factors, then merge. That's a two-upscale trick rather than a two-upscale cost - you do pay for both passes, so this is for hero images, not batches. The other honest caveat: it only works when the two images line up pixel for pixel. Two different upscalers, or one resize between them, and you get double edges, because you're adding one image's high frequencies onto another image's slightly shifted structure. Same size is enforced by the node; same content is on you.

Outside upscaling, the same node does texture transfer: the detail of a grain plate or a photoreal reference onto a clean render. Same rule applies - align them first.

Install

Manager, search ComfyUI-BCNodes, or:

cd ComfyUI/custom_nodes
git clone https://github.com/0xBeycan/ComfyUI-BCNodes
pip install -r ComfyUI-BCNodes/requirements.txt

Restart and search BCNodes (it's under BCNodes/image). The requirements are opencv-python, postfx and caption-audit - none of which this node uses. Delete any stray ComfyUI-AutoBypass folder from the pack's previous name.

Gotchas

Mismatched inputs stop the run with a message saying so - no silent truncation of the shorter batch. A split_sigma whose blur (3 × sigma) doesn't fit inside the image is refused with the largest value that does; that's the node protecting you from a blur that's meaningfully bigger than the picture.

Performance is fine on CPU for stills - the author's measurement is 0.3 s for a 3840×2160 image at sigma 3 on an M-series CPU, 2.5 s at sigma 32, with about half a gig of working memory beyond the inputs. Do it frame by frame on video and you'll feel it; flip device to gpu for that.

And the standard warning about any detail-transfer trick: overshoot is visible as crunch. detail_strength above 1 on already-sharp input looks like a bad sharpening pass, because that's exactly what it is.

CategoryBCNodes/image

Inputs (5)

NameTypeDefaultDescription
baseIMAGEGives the low frequencies: shapes, shading, colour (e.g. the SeedVR2 output made at downscale_factor 1, whose face is right).
detailIMAGEGives the high frequencies: skin texture, fine detail (e.g. the SeedVR2 output made at a lower downscale_factor). Same size and image count as base.
split_sigmaFLOAT3.00.5–64Sigma in pixels of the Gaussian that splits low from high: a pattern that repeats every 5.3 x sigma px is split half and half, finer comes from detail, coarser from base. Raise it to take more of detail; keep 5.3 x sigma well below the size of what must not change (eyes, mouth).
detail_strengthFLOAT1.000–2Gain on detail's high frequencies. 1 = as in detail; 0 = none (the blurred base); above 1 sharper than detail.
deviceoptCOMBOcpuDevice the images are filtered on.

Outputs (1)

NameTypeDescription
imageIMAGEbase's low frequencies + detail's high frequencies, clamped to [0, 1], RGB; float16 only when both inputs are float16.