Smart Color Match (Ultimate VTON)
Smart Color Match (Ultimate VTON)
- image_ref
- image_gen
- ignore_mask
- IMAGE
The problem it actually solves
If you've done virtual try-on (VTON), you know the tell: the garment is in the right place but it's lit by a different room. The skin and background come out with a blue or green cast, or the whole frame goes flat gray, because the composited clothing dragged the color statistics somewhere they don't belong. This node - displayed as Smart Color Match (Ultimate VTON) - is the fix for exactly that. It's not a model and runs no inference; it's a deterministic color-transfer pass that pushes a generated image's tones toward a reference image while leaving a masked area (your garment) alone.
This is the "color match" step from the post-processing playbook: a statistics transfer is instant and deterministic, which beats re-rolling the generation because "the colors are off." One light aside - the "Ultimate VTON" name is aspirational. This is a color-matching post node, not a try-on model. There's no checkpoint to download, no API key, nothing to load. Two images and a mask in, one image out.
How it works
Under the hood it's a Reinhard-style color transfer in CIELAB space, with three guards bolted on so it doesn't wreck your composite:
- Frequency separation. The generated image is split into a blurred low-frequency layer (color) and a high-frequency layer (detail). Only the low layer gets color-corrected, then the detail is added back - so you shift the lighting without smearing fabric texture or skin pores.
- Mask-weighted statistics. It computes weighted mean/std of the reference and the generated image over the pixels you care about. The optional
ignore_maskmarks what to exclude: 1 = clothing (not counted), 0 = background/skin (counted). Soft, feathered masks work - it doesn't hard-binarize. - Luminance protection. In LAB, the A/B channels (color) get the full correction but the L channel (lightness) only gets a weak 0.3× pull.
luma_protectionthen rolls that off toward highlights and shadows, so you fix the cast without flattening the image into a gray pancake.
The decontaminate_radius input handles the nasty bit: light bounces off a garment onto the skin beside it, and those "polluted" pixels near the clothing edge skew your stats. Setting it erodes the background mask (morphological dilation of the clothing) so the statistics sample away from the boundary.
Inputs and outputs
Two inputs are obvious: image_ref (the reference whose lighting you want) and image_gen (the generated composite with the cast). Everything else is a knob:
blend_factor(0–1, default 1) - the master intensity. 1 = full correction, 0 = nothing changes.freq_separation_radius(0–255, default 31) - how "low" the low-frequency layer is. Higher = broader, smoother color shifts.luma_protection(0–1, default 0.5) - how hard to protect highlights and shadows from the lightness shift.decontaminate_radius(0–100, default 15) - the erosion size for the bounce-light isolation.ignore_mask(optional MASK) - the garment mask; 1 = excluded from the stats.
Output is a single IMAGE (not a list), so it wires straight into a Preview/Save node or the next stage of your VTON workflow.
Install
Here's the one genuine trap: the README's clone command literally contains YOUR_USERNAME/ComfyUI-Smart-Color-Match.git - a placeholder never updated after the author forked a template. Ignore it and clone the real repo:
cd ComfyUI/custom_nodes
git clone https://github.com/kayorlian/ComfyuiSmartColorMatch
Or search "Smart Color Match" in ComfyUI Manager, then restart. requirements.txt lists opencv-python, numpy, and mediapipe - no model files. Worth knowing: mediapipe is only imported by a cloth_texture_replace.py that isn't even registered in __init__.py; this node itself just needs OpenCV and NumPy. If mediapipe refuses to install on your Python version, you can likely run this node without it.
Troubleshooting
- Everything turns gray / colors wash out. Your mask is excluding everything (weighted sum ≈ 0), so the node falls back to neutral reference stats and pushes the image toward gray. Check mask orientation.
- Nothing changes at all.
blend_factorat 0 will do exactly that - start from the defaults and dial it down. - Clothing color bleeds into the match. Your mask is inverted. Remember: 1 = the area to exclude. If your mask came from a background-removal model as "person = white", the person is being excluded and you're matching only what's left.
- Only the first frame is processed. It grabs
image_ref[0]andimage_gen[0]; batch inputs won't behave the way you'd hope. - An even
freq_separation_radiusgets silently bumped to odd (OpenCV Gaussian kernels need odd sizes) - don't be confused by the +1.
This is a niche pack with essentially zero community footprint - no threads mention it or the author - so treat the defaults as a starting point. A full-strength match is often more than a real composite wants; 0.6–0.8 blend_factor tends to read as "lit by the same room" instead of "recolored".
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| image_ref | IMAGE | — | |
| image_gen | IMAGE | — | |
| blend_factor | FLOAT | 1.000–1 | — |
| freq_separation_radius | INT | 310–255 | — |
| decontaminate_radius | INT | 150–100 | — |
| luma_protection | FLOAT | 0.500–1 | — |
| ignore_maskopt | MASK | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |