Color Match
Stop re-generating to fix a colour problem
- image
- reference
- mask
- ref_mask
- IMAGE
Three problems keep coming up and they're all the same problem. An inpainted region doesn't agree with the original. An upscaled tile is slightly off from its neighbours. A subject pasted into a new background is lit by a different sun than the room it's standing in.
The tempting fix is to re-roll seeds until the model cooperates. The correct fix is a statistics transfer: take the reference's colour statistics and push them onto the target. It's instant, deterministic, and it doesn't change a single pixel of composition or structure.
What it is
Color Match takes a target IMAGE and a reference IMAGE, and moves the reference's hue, contrast and overall cast onto the target. The target keeps its own composition and its own light-and-dark structure - this is grading, not generation.
The author implemented ten methods from the published algorithms rather than copying another pack. The default is reinhard - Reinhard 2001 LAB colour transfer, per-channel mean and standard deviation. It decouples luminance from chroma, so it changes the colour without rearranging how the image reads. If you only ever touch one entry in that dropdown, this is it.
Then, roughly in order of how hard they push:
wavelet- Haar, three levels, matching only the lowest-frequency subband. Detail subbands are left alone, so texture, edges, grain and fine text survive. The one for cloth, skin, hair, fabric weave.adain- per-channel instance normalisation on sRGB. Blunt and direct; best when source and reference are the same kind of material.mmd- kernel mean matching under an RBF kernel, 60 iterations solving for an affine map. Better than covariance matching when the reference's distribution isn't Gaussian, and an affine fit rather than per-pixel kernel regression because a regression's output variance can't exceed the reference's - per-pixel gives you grey mush.histogram- per-channel CDF matching. Distributions line up exactly, which is also why it's the easiest to overdo.mvgd- LAB 3×3 covariance matching (the core of Pitié 2007): whiten, then recolour. Corrects channel correlations, so it cleans a cast better than per-channel matching.histogram-mvgd-histogram,histogram-mmd-histogram- three-step chains, more accurate on the marginals, all still measured against the same reference.reinhard-lab-gpu- Reinhard's maths as a whole-batch tensor broadcast instead of a per-frame loop. Identical output, faster on big images.mean-only- average colour only. When you just want the green fluorescent cast gone.
The parameters that matter
strength runs 0 to 1 and defaults to 1.0, which is almost always too much. 0.5 is usually the natural-looking setting - you keep half the original's character. Full strength is for matching a reference exactly, like stitching footage from two cameras.
scope is the one nobody finds and everybody needs: all swaps luminance and chroma, chroma-only keeps the original's light-and-dark structure and swaps only the hue, luma-only keeps the hue and moves contrast and brightness. For compositing work where the exposure has to stay put, chroma-only is your friend.
ref_mode says how a reference batch is read - all frames pooled, first frame only, or frame by frame. src_mode is the one that matters the moment your target has more than one frame: per-frame computes a fresh set of statistics per frame (closest for a single still), while whole-batch uses one set for the entire batch, which keeps frame-to-frame differences intact. That's the only setting that doesn't make video flicker, so for video, sequences and AnimateDiff, use whole-batch.
mask (optional) limits where the match applies - white matched, black untouched. mask_use decides whether that mask also restricts where the statistics come from, which is Photoshop's "use selection to calculate colour". Pasting a subject into a new background is the case for the stricter option: otherwise the background dilutes the subject's own statistics. ref_mask (optional) does the same on the reference side, so a big irrelevant sky, watermark or subtitle bar can't drag your colour toward it.
detail (0–1) restores high frequencies back to the original's level - the difference only, so brightness doesn't move. Reach for 0.3–0.6 when histogram matching turns a gradient into bands or detonates the noise in a low-contrast source. feather is a Gaussian mask edge in pixels; a hard mask wants 8–32 or you'll see a seam.
Output is a single IMAGE, same size and batch count as the target. The reference doesn't have to match the target's size at all - only its statistics are used.
Installing it
Manager → Install Custom Nodes → ComfyUI-QG-tools, or:
cd ComfyUI/custom_nodes
git clone https://github.com/live1617/ComfyUI-QG-tools
Full restart, and you should see QG-tools v0.9.7 | 38 nodes loaded | 8 categories in the console. No pip installs, no model files - the pack's requirements.txt is comment-only on purpose and this node is pure torch.
Where people get burned
A near-flat target amplifies noise. Fog, a plain backdrop, an underexposed shadow - tiny standard deviation, huge correction factor. The author floored the source statistics at 2% of normalised L for exactly this, so a crunchy result on a near-solid colour is the floor doing its job, not a bug to file.
Masking is two different questions. "Where does the result apply" and "where do the numbers come from" are separate settings. Most people set the first and then wonder why their masked subject still picks up the background's cast.
Full strength on a composited subject looks fake. Grading a subject into a new plate usually wants chroma-only and strength around 0.5–0.7, because the light direction in the background is baked in and you can't transfer that.
wavelet ignores the statistics mask. It aligns subbands per image rather than pooling pixels, so the mask only changes where the result lands, never how the match is computed.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | The target image to recolor; a batch is processed frame by frame, and an alpha channel is passed through. | |
| reference | IMAGE | The reference the color is taken from. It need not match the target's size; only its statistics are used. | |
| method | COMBO | reinhard | Reinhard is the general-purpose one; wavelet changes low frequencies only and leaves texture alone; AdaIN is the per-channel sRGB version and is most direct on material from the same source; MMD follows non-Gaussian distributions; histogram matching aligns the distributions exactly and is the easiest to overdo; multivariate Gaussian also corrects channel correlations; the two entries built around a histogram match are three-step chains with more accurate marginals; the GPU method is the same math as Reinhard, faster on big pictures; mean-only moves nothing but the average color. |
| strength | FLOAT | 1.00–1 | How much of the result is mixed in: 0 is the original, 1 is a full match, and around 0.5 usually looks most natural while keeping half of the original's character. |
| ref_mode | COMBO | all-merged | How a reference batch is read: all frames pooled into one sample pool, the first frame only, or frame by frame against the target (the reference cycles when it runs short). |
| src_mode | COMBO | per-frame | How the source statistics are computed for a target batch: per frame - one set per frame, closest for a single still; uniform - one set for the whole batch, so differences between frames survive and the picture does not flicker (use this for video or sequences). With a single frame the two are identical. |
| scope | COMBO | all | What gets matched: all - both luminance and chroma; chroma only - keeps the original's light-and-dark structure and swaps only the hue (compositing, or when exposure must not move); luminance only - keeps the original's hue and swaps contrast and overall brightness. |
| feather | INT | 00–256 | Feather width of the mask, in pixels: 0 is a hard edge; with a mask, 8-32 makes the seam disappear. |
| mask_use | COMBO | result-only | How the mask is used: result only - it limits where the match applies while the statistics still come from the whole picture; target stats too - the target's mean, standard deviation and histogram also come from inside the mask, so pasting a subject onto another background stops the background from diluting its statistics. |
| detail | FLOAT | 0.000–1 | Detail restore: brings high frequencies (texture, grain, edge sharpness) back to the original's level - what the match smoothed away is put back, what it boosted is pulled down. Try 0.3-0.6 when histogram matching turns gradients into bands or blows up noise in a low-contrast picture; 0 leaves it alone. Only the difference is restored, so overall brightness does not move. |
| maskopt | MASK | Where the match applies: white areas are matched, black areas keep the original; leave it unwired for the whole picture. | |
| ref_maskopt | MASK | Statistics area on the reference: only white pixels take part. Wire one when the reference carries large irrelevant areas (sky, background, a watermark) so the color is not dragged toward them. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | The matched image, same size and batch count as the target. |