Uncrop Face
Pasting the new face back, and the tensor gotcha that bites
- image
- bbox
- face
- mask
- IMAGE
Crop Face cut the old face out. Generation made a new one. Now you have to put it back - in exactly the same spot, blended so nobody can tell. That's Uncrop Face, the last compositing node in Character Face Swap. You hand it the full image, the bounding box from the crop step, the newly generated face, and a mask, and it resizes the face to fit the box and blends it in using the mask as a soft alpha.
The mechanism is honest and easy to follow. The face and mask get resized to the bbox's dimensions with bilinear interpolation, then a weighted sum does the compositing: output = mask * face + image * (1 - mask) over the bbox region. Mask 1.0 means "show the new face," 0.0 means "keep the original pixel," and the feathered edge of the mask is what makes the seam disappear (or not, see below).
The inputs
- image (IMAGE) - the full frame you're pasting into.
- bbox (BBOX) - where to paste. Normally from Crop Face, or Image Full BBox when you've upscaled and the old coordinates no longer apply.
- face (IMAGE) - the new face, sized arbitrarily - it gets resized to the bbox.
- mask (MASK) - the blend region, from Segment Face. This is what determines how soft the paste looks.
Output is the composited IMAGE.
Where people get burned
The single most reported failure with this node is a permute dimension error, and it's a real, reproducible one (it showed up on r/comfyui with the node's own stack trace back in August 2024):
permute(sparse_coo): number of dimensions in the tensor input does not
match the length of the desired ordering of dimensions i.e. input.dim() = 4
is not equal to len(dims) = 3
It fires in scale_face at face.permute(2, 0, 1). The cause: the node expects a single image and a single 2D mask - [H, W, C] and [H, W]. If your mask carries a batch dimension (many ComfyUI mask outputs are [1, H, W], and any batched input will be [B, H, W]), the unsqueeze inside pushes it to 4D and permute explodes. The fix is to make sure both face and mask are unbatchted when they reach this node - feed the mask straight from Segment Face (which emits a plain 2D mask) rather than through a node that adds a batch axis, or add a squeeze/mask.squeeze(0) in between. There's no fix in the pack itself - the thread asking about it got zero replies - so you're on your own for this one.
Two more honest notes. The face is resized to the bbox, so a mismatched aspect ratio will visibly stretch the swap; keep the crop box and the face roughly square, which the pack's squaring in Crop Face is designed to ensure. And the mask quality decides the seam: a hard-edged mask pastes hard-edged. The pack's answer is the Mask Contour + inpainting seam-fix loop - the README is explicit that it expects you to fix the seam in A1111-style inpainting (it cites a historical ComfyUI inpainting issue), so don't expect a perfect, seamless composite in one pass.
Installing the pack
ComfyUI Manager, search Character Face Swap, install, restart. Or:
cd ComfyUI/custom_nodes/
git clone https://github.com/ArtBot2023/CharacterFaceSwap.git
cd CharacterFaceSwap
python install.py # on the bundled Windows build: ../../../python_embeded/python install.py
Runs on torch + numpy only - no model downloads, no CUDA requirement beyond whatever the rest of the graph needs.
In one line
Uncrop Face is where the swap becomes a composite: resize, blend, and pray the mask was soft enough. Get the batch dimension off the mask and it'll behave.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| bbox | BBOX | — | |
| face | IMAGE | — | |
| mask | MASK | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |