VS Difference Matte
Real partial transparency, out of two plates and a subtraction
- white_plate
- black_plate
- color
- alpha
- premultiplied
Here's the thing no segmentation model will ever give you: honest alpha in the middle of a bottle of gin. BiRefNet will hand you a silhouette, and even the matting weights are guessing at fractional transparency on a surface that's genuinely semi-transparent by design. Difference matting doesn't guess. Shoot the same object on white and on black, subtract, and the per-pixel difference is the alpha - because a fully transparent pixel took on the backdrop completely, an opaque pixel took none of it, and a half-transparent pixel took exactly half. VS Difference Matte is the arithmetic, done in float and delivered as straight-alpha colour.
The formula
Per channel: alpha_c = 1 - (W - B) / (white_level - black_level). On a clean pair of plates with white_level 1.0 and black_level 0.0, that reduces to 1 - (W - B) - the gap between the plates, which is exactly how much of the backdrop got through. Then channel_combine collapses the three per-channel alphas into one: mean (default, calmest), max (keeps coloured fringes of glass - handier on tinted bottles), or luma.
Then two things happen in order, and the order matters:
- Renormalise between
clip_lowandclip_high- default 0.004 and 0.996. This is a pedestal killer. Plate-based alphas come back with a haze of 0.002–0.01 across the background because the two plates were never exactly 1.0 and 0.0. Left in, that's a grey veil in your PNG composited anywhere. Clipping it off is standard practice in the technique. - Solve for straight colour:
colour = (B - (1 - alpha) · black_level) / alpha, zeroed where alpha is 0, clamped to 0–1.
Outputs are color (straight, unpremultiplied - the thing you save), alpha (the mask), and premultiplied (colour × alpha). Most of the world wants straight alpha in a PNG; the premultiplied output exists for compositors and for previewing, because that's the form a compositing app wants when it merges.
The two levels
white_level and black_level are how the node knows the actual levels in your plates. If you ran VS Normalize White Plate, the backdrop really is 1.0 and the black plate really is 0, so leave these at default and forget them. If you skipped normalisation and your "white" backdrop is sitting at 0.94, setting white_level to 0.94 is the honest way to tell the node - it's better than clipping the difference later, which just throws away low-alpha glass.
clip_high below 1.0 is a soft ceiling: pulling it to 0.98 makes the last two percent of alpha read as fully opaque. Cheap trick for bottles with a blown highlight right on the silhouette.
Install
ComfyUI Manager → VS Glass Matte, or:
cd ComfyUI/custom_nodes
git clone https://github.com/andrewclass1/comfyui-vs-glassmatte
# restart ComfyUI
No dependencies. There's no requirements.txt, and pyproject.toml declares an empty dependency list - it's a pure torch/numpy pack, which is rare enough to be worth stating.
Where people get burned
Size mismatch is a hard error. ValueError("White and black plates must be the same size.") - because the node subtracts tensors directly. If you regenerated the neck through a crop, this is the error you get when the stitch landed on a different-sized base, and no amount of tuning will fix it.
The label ghost. If the plates disagree anywhere in the opaque region, that disagreement shows up as alpha. The classic is a black plate whose body was regenerated instead of copied, so the label's edges differ by a couple of levels and you get an embossed ghost of the label in the alpha channel - invisible on white, obvious on red. That's what VS Plate Drift Check is for; run it before you tune this node.
clip_low too high. People raise it to kill a dirty backdrop, and it eats the faint edge of the glass along with it - you get a hard-edged bottle where the neck used to fade. If you need a high clip_low, fix the backdrop instead. That's an ordering problem, not a parameter problem.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| white_plate | IMAGE | — | |
| black_plate | IMAGE | — | |
| white_level | FLOAT | 1.0000.5–1 | — |
| black_level | FLOAT | 0.0000–0.5 | — |
| clip_low | FLOAT | 0.0040–0.2 | — |
| clip_high | FLOAT | 0.9960.8–1 | — |
| channel_combine | COMBO | mean | 3 options: mean, max, luma |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| color | IMAGE | — |
| alpha | MASK | — |
| premultiplied | IMAGE | — |