Pertineo Trimap from Matte
ViTMatte Won't Fix Your Whole Matte — Just the Band You Point It At
- matte
- matte_image
- reference_image
- core_mask
- garbage_mask
- trimap
- preview
Every "AI background remover" conversation in this ecosystem eventually hits the same wall. BiRefNet, InSPyReNet, rembg - they're all segmentation models. They decide, per pixel, foreground or background, and their notion of "confident" is a probability, not transparency. Feed a veil, a wine glass, smoke or a strand of hair to one and the failure is structural, not a tuning problem: you asked a labeling model for fractional alpha.
Matting works differently. A matting model like ViTMatte takes a trimap - white where you know it's foreground, black where you know it's background, grey where you're unsure - and solves for alpha only inside the grey. That's why the results are dramatically better, and why you can't just drop a matting node into a graph: something has to build that trimap.
Pertineo Trimap from Matte is that something. And since your coarse matte almost certainly came from a segmentation model or a video model's alpha channel, it's the bridge between the two methods.
How it works
Four operations, and it's worth knowing all of them because each one is a knob you'll actually turn.
- The matte is cut at
threshold(0.5 by default) into a hard foreground/background decision. - That hard region is eroded inwards by
core_erodepixels - the survivors become solid white (1). This is the "we're certain" zone. - The same hard region is dilated outwards by
outer_dilatepixels - the ring between that edge and the core becomes 0.5, the unknown band. - Everything else is 0.
Both are measured from the same boundary, in opposite directions: a core of 16 and a dilate of 16 gives a 32-pixel-wide unknown band straddling your coarse edge, and that band is the only thing ViTMatte computes.
The preview output is the part I'd install the node for on its own. With reference_image connected, it darkens the background to ~35%, leaves the core alone, and paints the unknown band orange over the actual plate. You can see at a glance whether the band covers the hair or stops in the middle of it.
The inputs
Required, and set by feel rather than by spec:
- threshold - where you cut the coarse matte. Raise it if the coarse matte is bloated.
- core_erode - the important one. Anything inside the white core stays hard-edged, forever. If hair or motion blur sits inside your coarse matte, the core is too fat - erode more.
- outer_dilate - how far outside the edge you're willing to let the matting model look. Bigger = more search room, more compute, more chance of grabbing background.
- matte_channel - only used with
matte_image: red/green/blue/alpha/luminance. - invert_matte - for inverted masks, e.g. ComfyUI's
LoadImagealpha, which comes out backwards. - invert_garbage - flips the garbage mask's meaning from "white is garbage" to "white is keep, outside is garbage". That second mode is a roto: draw a rough shape around your subject and everything outside it is thrown away.
Optional:
- matte (a
MASK) or matte_image (anIMAGE) - connect exactly one. Both or neither throws a clear error. - reference_image - the plate. Also resizes the matte to plate resolution before processing, which quietly saves you from a whole class of mismatch bugs.
- core_mask - force white areas to solid foreground, for holes inside the subject.
- garbage_mask - force areas to background. Garbage wins over core when they overlap.
Outputs are trimap (→ Pertineo ViTMatte Tiled Refiner) and preview (→ any preview node; it's just an IMAGE).
Install
ComfyUI Manager → Pertineo VFX, restart, or:
cd ComfyUI/custom_nodes
git clone https://github.com/pertineo/comfyui-pertineo-vfx
pip install -r comfyui-pertineo-vfx/requirements.txt
This node needs no models - it's morphology. The weights only arrive when you get to the refiner. OpenCV does the erode/dilate if it's available, with a numpy fallback otherwise.
Where people get burned
A trimap with no grey area does nothing. If core_erode plus outer_dilate collapse the band to nothing - or your threshold cuts the matte so hard there's no soft edge left - ViTMatte computes zero tiles and you get the trimap back as your "matte". The node prints the unknown percentage to the console every frame; if it says 0%, fix the trimap, not the refiner.
Writing the trimap out as an image in sRGB. If you go the folder route, 0.5 becomes about 0.73 through an sRGB transform and ViTMatte sees a nonsense trimap. Write it raw/linear, and turn dithering off. If you're generating trimaps inside ComfyUI like this, the data never leaves float and you're fine.
Eroding nothing. core_erode = 0 means the whole coarse matte is "certain" and only the outside ring gets refined - usually the reason someone reports that ViTMatte changed nothing.
Inputs (11)
| Name | Type | Default | Description |
|---|---|---|---|
| threshold | FLOAT | 0.500.01–0.99 | The matte is cut at this value before eroding / dilating (0.5 = half transparency). |
| core_erode | INT | 160–512 | Erode inwards in px. Everything inside stays solid white (1). Increase it where hair or motion blur lies inside the coarse matte. |
| outer_dilate | INT | 160–512 | Dilate outwards in px. The area between core and this edge becomes unknown (0.5), the only area ViTMatte refines. |
| matte_channel | COMBO | Only used with matte_image: which channel of the image holds the matte. For a matte rendered as grey RGB, red or luminance both work. | |
| invert_matte | BOOLEAN | false | Invert the matte first (e.g. for masks from LoadImage alpha, which are inverted). |
| invert_garbage | BOOLEAN | false | Off: white in garbage_mask = garbage (forced to background). On: white = keep, everything OUTSIDE is garbage – like a roto drawn around the subject. |
| matteopt | MASK | Coarse matte batch as MASK (e.g. from LTX), white = foreground. Connect either this or matte_image. | |
| matte_imageopt | IMAGE | Coarse matte batch as IMAGE (e.g. a matte rendered as RGB sequence). The channel is chosen with matte_channel. Connect either this or matte. | |
| reference_imageopt | IMAGE | Plate. If connected, the matte is resized to the plate resolution first and the preview shows an overlay. | |
| core_maskopt | MASK | Optional: white areas are forced to solid foreground (1), e.g. holes inside the body. | |
| garbage_maskopt | MASK | Optional: garbage areas are forced to background (0), e.g. objects the coarse matte caught by mistake. Wins over core_mask. See invert_garbage. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| trimap | MASK | — |
| preview | IMAGE | — |