CV Feather Blend
Kill the seam where two warped images meet
- image_a
- image_b
- mask_a
- mask_b
- image
- weights
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_aandimage_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 sameCV Stitch Canvasdsize. 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.
featheris 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.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| image_a | COMFY_MATCHTYPE_V3 | First 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_b | COMFY_MATCHTYPE_V3 | Second 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. | |
| feather | FLOAT | 320–1024 | Transition width in pixels inside the overlap. 0 = hard 50/50 average where both images are valid. |
| mask_aopt | NPARRAY,MASK | Coverage 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_bopt | NPARRAY,MASK | Coverage 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)
| Name | Type | Description |
|---|---|---|
| image | COMFY_MATCHTYPE_V3 | Blended image in the inputs' format; black where neither input is covered. |
| weights | NPARRAY | HxW float32 share of image_a (1 = pure A, 0 = pure B) - debug it with 'Preview CV Array' in heatmap mode. |