ComfyUI Node

CPA Refine

The ViTMatte stage that catches everything the generator won't matte

By rik-python·Created about a month ago·Updated 9 days ago· 50
CPA Refine
  • plate
  • target
  • adds
  • cuts
  • others
  • selection
  • refined
  • others
◄vitmatteauto-download►
◄erode10►
◄dilate15►
◄radius2►

Everything else in this pack is about edges. CPA Refine is about ownership - it takes the raw SAM3 selections and turns them into three clean masks: your subject, your subject with a matte edge, and everyone else. It's the second stage of the graph the generator expands into, and it's the one that produces the "others" mask the rest of the pipeline uses to paint other people out of the plate.

If you're here because your console mentioned it, you're looking at the discriminative half of the pipeline: segmentation plus ViTMatte, which is good at which pixels and bad at hair.

What the three output masks mean

selection is the hard one, and it's smarter than "target". The node builds it as target union adds, minus cuts grown and feathered - so when you alt-click someone's hand out of your subject, that hole appears here, and the cut region also gets folded into the others mask. Your subject's silhouette and "everyone else in the frame" are two views of the same decision, and this node keeps them consistent.

refined is the ViTMatte edge. Per frame, the node builds a trimap from the selection (erode px inside, dilate px outside as the unknown band), crops to the subject with a 64 px margin - the crop is what makes it fast, since ViTMatte sees a small region instead of your whole frame - runs the model, then forces the trimap's known regions to hard 0 and 1.

others is everyone and everything excluded, cuts included.

Then all three get temporally stabilised, which is the part people underestimate.

The stabiliser is the interesting bit

SAM3 and ViTMatte decide each frame independently, so the edge they produce chatters - a couple of pixels of wobble frame to frame that is invisible in a still and awful in motion. The fix is optical flow: for each frame, the node warps the masks from radius frames before and after into the current frame using DIS optical flow computed on the grey plate, and averages them with a Gaussian time weight.

Two details make it work rather than smear. First, remapping by flow means neighbouring frames' edges land where the subject actually moved to. Second, each neighbour's weight is multiplied by a photometric term - if the warped plate doesn't match the current plate (an occlusion, something moving in front), that frame contributes less. So a hand passing in front doesn't ghost into your matte. radius=2, the default, is a five-frame window; the README's advice to raise it to 3–4 only for chattery props and drop it to 0 for fast motion is sound, because flow-based averaging plus fast action is how you get lag.

Note the deliberate precision split, which tells you where the author got burned: selection and others come out as float32 because they shape the isolated plate, and a single 8-bit rounding error there changes what LTX generates. refined comes out float16, because it only ever feeds the final blend.

The inputs you'd actually set

plate and target are required; adds, cuts and others are the optional mask inputs the picker's clicks turn into.

vitmatte defaults to auto-download, which pulls hustvl/vitmatte-base-composition-1k into models/vitmatte/ on first run. Point it at a folder name if you keep ViTMatte weights somewhere else.

erode (10) and dilate (15) are the trimap band. Raise dilate to 25–30 if frizzy edges get cut short; drop both to something like 5/8 if ViTMatte bleeds into the background on a hard-edged object. radius (2) is the stabiliser window described above.

How it fits the rest

Ownership here is what makes the generative pass possible at all. LTX mattes all main subjects and can't be told which one you want, so the pack paints others out of a copy of the plate and lets LTX matte your subject alone. Refine also hands the pipeline the ViTMatte edge it falls back on in two places: within a small band of another person, where LTX returns solid alpha on both sides and there's no generative edge to steal, and wherever a coverage map says LTX left your selection empty - a parked car, a table, a prop it didn't read as a main subject.

Which is also its limit. Our background removal notes make the point that a segmentation mask is structurally the wrong output type for hair: the strands were never in the outline. ViTMatte narrows the gap and doesn't close it - 45% hair recovery against LTX's 89% in the author's measurements. This node is not where you win hair. It's where you win the right subject.

Install

Ships with the pack:

cd ComfyUI/custom_nodes
git clone https://github.com/rik-python/ComfyUI-Comfy-Pro-Alpha-Generator.git
pip install -r ComfyUI-Comfy-Pro-Alpha-Generator/requirements.txt

transformers in that requirements file is what loads ViTMatte, and the model downloads itself on first use (~400 MB) into models/vitmatte/. It's hidden from the node menu - every internal stage in this pack carries the deprecated flag so it stays out of search while remaining executable - so don't hunt for it. ComfyUI 0.38+, NVIDIA GPU, and the pack is non-commercial without a licence.

One gotcha with a familiar shape: it clears the ViTMatte model from memory when it's done, so the first run of a session pays a load cost and the second doesn't. If you're re-running a preview over and over to compare settings, that cost shows up as inconsistent timing rather than slower work - you're not imagining it, and it isn't a problem to fix.

CategoryComfy Compositing/internal

Inputs (9)

NameTypeDefaultDescription
plateIMAGE—
targetMASK—
vitmatteSTRINGauto-download—
erodeINT10—
dilateINT15—
radiusINT2—
addsoptMASK—
cutsoptMASK—
othersoptMASK—

Outputs (3)

NameTypeDescription
selectionMASK—
refinedMASK—
othersMASK—