Nodes/ComfyUI CV/CV Feather Blend
ComfyUI Node

CV Feather Blend

Kill the seam where two warped images meet

By bmad4ever·Created 4 months ago·Updated 14 days ago· 1
CV Feather Blend
  • image_a
  • image_b
  • mask_a
  • mask_b
  • image
  • weights
◄feather32►

Panorama stitching has one unavoidable step: after you warp both photos onto a shared canvas, the overlap has to become one image. Averaging the overlap 50/50 gives you a visible ghost double-edge. CV Feather Blend does the thing the good stitchers do: it weights each image by how far inside its own valid region a pixel sits, so the transition happens gradually in the overlap and nowhere else.

How it works

Coverage first. Each input's validity is either a mask you supply, or - by default - the non-black pixels of that image. Then for each image the node computes a distance transform of the valid region and turns it into a weight that ramps linearly to 1 over feather pixels of travel from the border, capped at 1 anywhere deeper inside. The weight gets divided by the sum of both weights, and the output is the pixel-wise weighted average.

Two details worth knowing, both visible in the code path: the validity mask is padded by a pixel before the distance transform so that a mask covering the entire frame still gets finite distances rather than zeros, and where neither image is covered the output is black. Where only one image is covered, its weight is 1 and it's copied through untouched - there's no subtle darkening at the edges of your warped photo.

The second output, weights, is the share of image_a per pixel (1 = pure A, 0 = pure B). Preview it with Preview CV Array in heatmap mode. It is the single best way to debug a stitch: if the ramp is in the wrong place, or the share map is a hard rectangle rather than a gradient, you'll see it immediately instead of squinting at a seam.

Inputs

  • image_a and image_b - both already on the shared output canvas. They must be the same size; if they're not, the node raises an error naming both sizes and telling you to warp them with the same CV Stitch Canvas dsize. That's a pleasant failure for a class of bug that usually manifests as a crashed run or a silently misaligned stitch.
  • feather (default 32) - the transition width in pixels inside the overlap. Set it to 0 and you get a hard 50/50 average exactly where both images are valid, i.e. the ghosting you were trying to avoid. Don't set it to 0.
  • mask_a / mask_b (optional) - coverage masks. Leave them off and the default is "non-black pixel = covered".

The masks are where the honest caveats live. If your image contains genuine, intentional black - a dark night sky, a letterboxed frame, a black graphic - the non-black default reads it as uncovered and it gets overwritten. And when the inputs are warped images, the black fill outside the warped region is indistinguishable from content. The node description says what to do: connect the warped CV Constant Like masks, i.e. warp a white-filled constant instance with the same transform and feed that as the coverage. The tooltip states the reason plainly - that also keeps true-black image content from being treated as uncovered.

Where it fits

The pack's stitching path runs: matches → homography or CV Stereo Rectify (Uncalibrated) → warp both onto one canvas → this node. It's the last step, and it's also usable well outside stitching: two views of the same subject you want to combine, a generated fill and a photographic plate, a clean-plate composite, anything where you have coverage you can describe. The weights output being a real per-pixel array means you can also use it to merge at a different gamma or to hand a mask downstream.

Install

# ComfyUI Manager → search "ComfyUI CV" → install → restart
# or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
cd comfyui_cv && pip install -r requirements.txt

Python ≥ 3.12 and a recent ComfyUI (this pack needs the V3 node API - on an old build, no nodes appear). The only real dependency is opencv-contrib-python-headless~=5.0.0.93, and this node is a plain cv2 function call, so it survives a clobbered contrib install. Keep the contrib wheel anyway: a plain opencv-python install over it silently empties the contrib submodules and makes the pack's Contrib-category nodes disappear (tools/repair_opencv_contrib.py --check / --apply).

When it looks wrong

  • Ghosting in the overlap. feather is too small relative to the misalignment. Feathering hides sub-pixel differences, not a bad homography. Fix the alignment first.
  • A black hole where content should be. Neither coverage says "covered" there. Usually a default non-black mask on an intentionally dark image, or two warped images whose coverage isn't overlapping where you think it is.
  • A hard edge instead of a ramp in weights. Your coverage is shaped differently than you assumed. Look at the masks.
  • Size error on run. Warp both images with the same target size. The error message names the two sizes, so read it - it's the diagnosis.

bmad4ever's pack is a fork of geroldmeisinger's opencv-comfyui, rewritten on the V3 API, and the author - a small-utility ComfyUI maintainer - states that it's LLM-assisted and not production-grade. Feathering maths is one place where correctness is visible at a glance, so verify with the heatmap output rather than trusting the description.

Categoryimage/CV/features

Inputs (5)

NameTypeDefaultDescription
image_aCOMFY_MATCHTYPE_V3First image, already on the shared output canvas. 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.
image_bCOMFY_MATCHTYPE_V3Second image, same size and format as image_a. 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.
featherFLOAT320–1024Transition width in pixels inside the overlap. 0 = hard 50/50 average where both images are valid.
mask_aoptNPARRAY,MASKCoverage of image_a (>0 = valid). Default: non-black pixels of image_a. 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.
mask_boptNPARRAY,MASKCoverage of image_b. Default: non-black pixels of image_b. 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 (2)

NameTypeDescription
imageCOMFY_MATCHTYPE_V3Blended image in the inputs' format; black where neither input is covered.
weightsNPARRAYHxW float32 share of image_a (1 = pure A, 0 = pure B) - debug it with 'Preview CV Array' in heatmap mode.