cv2.seamlessClone
Kill the Seam on Your Composite Without Touching a Model
- src
- dst
- p
- mask
- result
Paste an object into a scene and the join shows. Not because the mask is sloppy - because the patch was lit differently, white-balanced differently, or generated in a different pass. The usual ComfyUI answer is to send the whole frame back through a model and hope. cv2.seamlessClone is the other answer: a deterministic Poisson solve that matches the gradients of the inserted patch to the destination's boundary colour, so the seam disappears and nothing else in the frame moves by a single pixel.
That last property is the reason this node matters. Masked compositing is the only tool in the stack that guarantees the unmasked region is untouched - edit models regenerate everything and drift, and drift compounds across a chain of edits. cv2.seamlessClone is a composite, not a generation: no model, no seed, no drift. Run it twice, get the same frame.
How it works
For the masked region, OpenCV solves a Poisson equation whose boundary condition is the destination image and whose internal gradient field comes from the source patch (or the source structure, depending on the flag). Practically: the patch takes on the illumination of wherever it lands while keeping its own texture. That's why it fixes exposure and white-balance mismatches that an alpha paste can't.
It's also why it's slow. The solve covers the whole canvas, so a large dst costs real time - enough that this wrapper runs the call in an interruptible subprocess, always, so you can cancel a run mid-solve instead of watching ComfyUI freeze. Budget for a beat or two of latency per call; that's the pack being careful, not the node being broken.
Inputs, outputs, and the p coordinate
src- the patch you're inserting. Must be 8-bit, 3-channel.dst- the image you're inserting into. This is the primary input and the one whose type you get back on the output socket: IMAGE in, IMAGE out.p- where the centre ofsrclands indst, as(x, y). It's a compositeCV_TUPLEinput, so either wire it from CV Tuple or type the two components in place. Default is(0, 0), which puts the patch centre in the corner - set this first, always.flags- the clone mode, and this is the interesting dial:NORMAL_CLONEpastes the patch and matches its colour to the destination;MIXED_CLONEkeeps only the source's strong gradients, so underlying destination texture survives where the patch is flat;MONOCHROME_TRANSFERtakes the source's structure and paints it with the destination's colour - the one people reach for to relight a shape rather than move an object.mask(optional) - the region ofsrcto clone, white = include. It must be the size ofsrc, not ofdst, which is the mistake almost everyone makes once. Leave it unconnected and the whole patch is cloned.
Output is a single result socket echoing dst's format - no manual conversion needed.
The pack ships a demo workflow, 17_seamless_clone.json, that clones the same birds-and-sky patch into the same landscape in all three flag modes with an ImageCompare node on the end. If you're going to spend five minutes learning one thing about this node, learn what those three modes look like on your own image.
Install
Manager → search ComfyUI CV → install, 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. The example workflow's input images live in the repo's example_inputs/ - run workflows/01_install_example_inputs.json once to copy them into ComfyUI/input, then reload the page, because the Load Image dropdown only refreshes with the node definitions.
Where people get burned
The patch has to fit. p places the patch's centre; if the resulting rectangle isn't entirely inside dst, cv2 errors out rather than clipping. Compute your placement from the image size, or nudge it until the whole patch is in bounds.
8-bit, 3-channel, both images. Grayscale or float input gets rejected. If your mask came from an alpha channel, that's fine - the mask input accepts a MASK - but src and dst need to be colour.
Mask size. A mask sized to dst is the classic failure. The tooltip says it in plain text: it's the region in the source.
It's not an inpaint. seamlessClone blends a healthy patch into a healthy image. For "there's nothing here, invent something," you want cv2.inpaint or the inpainting workflow proper. And the pack's README is blunt that its DNN-based examples are showcases, not production pipelines - the classic cv2 functions are the solid half of it.
The contrib wheel. Installing plain opencv-python over the pinned contrib build empties the shared site-packages/cv2 and makes contrib nodes vanish with no error. tools/repair_opencv_contrib.py --check diagnoses it.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| src | NPARRAY,IMAGE,MASK | The source image (8-bit 3-channel), from which a region will be blended into the destination. 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. | |
| dst | COMFY_MATCHTYPE_V3 | The destination image (8-bit 3-channel), where the src image will be blended. The image output(s) echo this input's format. The low-level cv2 function writes its result into this array in place, but this wrapper passes cv2 a private copy, so your input array is never modified. 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. | |
| p | CV_TUPLE | 0,0 | The point where the center of the src image is placed in the dst image. One value with 2 components (x, y) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place. |
| flags | COMBO | NORMAL_CLONE | Flags that control the type of cloning method, can take values of `cv::SeamlessCloneFlags`. |
| maskopt | NPARRAY,IMAGE,MASK | A binary mask (8-bit, 1, 3, or 4-channel) specifying the region in the source image to blend. Non-zero pixels indicate the region to be blended. If an empty Mat is provided, a mask with all non-zero pixels is created internally. Optional - leave unconnected for the OpenCV default (None). 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)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'dst' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |