Nodes/ComfyUI CV/cv2.blendLinear
ComfyUI Node

cv2.blendLinear

A per-pixel weighted average, for when a hard composite won't do

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.blendLinear
  • src1
  • src2
  • weights1
  • weights2
  • result

Every "merge these two passes" job in ComfyUI eventually hits the same wall: which one wins where they overlap. A hard split leaves a line. A feathered mask gets you most of the way, and cv2.blendLinear is the mathematically clean version of exactly that - a weighted average, per pixel, with a real weight map on each side instead of one alpha.

It's a raw wrapper out of comfyui_cv, the pack that exposes ~470 cv2.* functions as nodes. OpenCV itself uses blendLinear inside its stitching pipeline for exposure compensation, which tells you the intended shape of the problem: two images that overlap, and per-pixel confidence in each.

The math, because the math is the whole node

dst = (src1 * weights1 + src2 * weights2) / (weights1 + weights2)

That's it. Weights are per-pixel, so you can hand it a soft ramp and get a gradient blend; you can hand it separate weight maps for each side, which is what distinguishes this from plain alpha compositing.

The consequence to internalise: weights1 + weights2 == 0 gives you 0 (black). Feather a mask to true zero at the edges of a composited region and both weights reach 0 there, so you paint black along the seam. Add a tiny floor to both weight maps.

Inputs and outputs

All four inputs are required - there's no optional here, which trips people up because they expect "weights" to be the thing you can skip.

  • src1 - first image. This is the format-deciding input: the output result echoes whatever format you linked in, so IMAGE in → IMAGE out, MASK in → MASK out, NPARRAY stays NPARRAY.
  • src2 - second image, same size and channel count.
  • weights1, weights2 - the per-pixel weight arrays. Single-channel float images in OpenCV's own usage; they must match the source dimensions.

One output, result.

Because the weights are a ratio, the scale doesn't matter: a mask that arrives as 0/255 uint8 blends identically to a float 0/1 mask, at 256 steps of resolution instead of continuous. So Mask → CV Array into both weight sockets is a perfectly fine feathered crossfade, and it's the path you'll take most often.

The float path is where this gets interesting. Latent → CV Array hands you the latent as float32 with values untouched, and CV Array → Latent goes back - so you can blend two latents per pixel with a ramped mask and only decode once. That's a cleaner meld than blending finished RGB, because nothing is quantized to uint8 in the middle.

Where you'd actually reach for it

  • Merging a tiled upscale. Tiles are generated separately and don't agree at the edges; a ramp of weights across the overlap is the standard fix (tiled diffusion and Tile ControlNet exist to stop the tiles diverging, this is the step that stops the seam showing).
  • Two-pass merges. A clean render and a stylized one, crossfaded by a soft mask you painted elsewhere.
  • Outpainting seams. Blend the outpainted frame against the original across the boundary with a gradient instead of a straight line.
  • Paste-back in a detail loop. Same problem, smaller region: the re-rendered crop has to sit into the original without a visible rectangle (the paste-back step covered here).

Where it isn't the right tool: exposure or colour disagreement. blendLinear assumes the two images agree once weighted; if one is warmer, averaging them gives you a warmer average, not a match. That's a colour-match job (post-processing covers the mean/std transfer approach), and it's worth doing before the blend.

Install

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

…or ComfyUI Manager → search "comfyui_cv". Restart after. You need Python ≥ 3.12, a recent ComfyUI on the V3 node API, and OpenCV 5 contrib:

pip install "opencv-contrib-python-headless~=5.0.0.93"

That version is the one behaviour is curated against. No models needed for this node - it's pure array math.

Common issues

Black bars or a black seam. Your weights sum to zero there. Rectangles of weight that stop short of the edge are the usual cause.

"[OpenCV] Sizes of input arguments do not match." Sources differ in size, or a weight map came out 1-channel when a 3-channel one was expected, or you fed a batched array where a single frame was needed.

The blend looks banded. You're blending in 8-bit. Weights at 0/255 give you 256 discrete mixes, and a smooth gradient across a large area will show steps. Convert both sides to float (or better, do the whole thing in latent space) and the banding goes away.

It runs but the output is grey mush. Almost always a weight map that's blurrier than you meant - a big box blur on a mask makes a very wide ramp, and a wide ramp averages away detail across the whole frame instead of just the seam.

Categoryimage/CV/low-level/cv2 B

Inputs (4)

NameTypeDefaultDescription
src1COMFY_MATCHTYPE_V3 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.
src2NPARRAY,IMAGE,MASK - - - 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.
weights1NPARRAY,IMAGE,MASK - - - 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.
weights2NPARRAY,IMAGE,MASK - - - 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.

Outputs (1)

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