Nodes/ComfyUI-JH-PixelPro/Color Matcher (LAB)
ComfyUI Node

Color Matcher (LAB)

When your AI pass changes the skin tone, this snaps it back

By jetthuangai·Created 5 months ago·Updated 4 months ago· 5
Color Matcher (LAB)
  • image_target
  • image_reference
  • mask
  • image_matched
channelsab
strength1.00

Every portrait pipeline hits this eventually: you run a face refine, an IPAdapter pass, or an inpaint, and the result is better in every way except color - the skin tone drifted, the white balance shifted, there's a faint green cast where there wasn't one before. ComfyUI is genuinely bad at this specific fix, because it needs you to rebuild the original color while keeping the new detail. JHPixelProColorMatcher is the pack's answer: a Reinhard color transfer in LAB space, tuned for exactly this "AI pass drifted the color" failure mode.

The setup is deceptively simple. image_target is the AI output that drifted; image_reference is the pre-AI source you want the color pulled back toward. The node computes the mean and standard deviation of the two images' color statistics and re-anchors the target's stats onto the reference's - the classic Reinhard et al. transfer. What makes it retouching-grade is the channels choice:

  • ab (default): match chroma only, preserve the target's luminance. This is the pro default. The AI output keeps its lighting and contrast, and you fix the color drift - which is what you want 90% of the time.
  • lab: match L + a + b, a full tone transfer including brightness. Use it when the pass also wrecked the exposure.

strength (default 1.0) blends between identity and full match - 0.6–0.8 is the natural dose for skin-tone correction, and honestly if you run full strength on skin you'll often overcorrect. The optional mask input is a statistics gate, not an output mask: it restricts which pixels are used to estimate the mean/std, so you can match only the skin region while the background statistics don't pollute the transfer. The correction is always applied to the full target.

Where people get burned

The reference must have the same H×W as the target - the node raises a ValueError otherwise, with no auto-resize. Pre-scale the reference upstream with an ImageScale node. And the transfer assumes both images share a similar tonal regime: a daylight portrait matched against a tungsten-lit reference will look wrong no matter what you do. Pre-grade closer first, then use a small strength to finish. There's also a sibling node, JHPixelProColorMatcherRegion, if you need the output region-restricted too - this one's mask only gates the statistics.

Output is image_matched at the target's shape. It's a one-in-one-out color node, so it slots anywhere: after the face-detail block, before compositing, or as part of a batch skin-tone unification chain (pick one anchor portrait, run every other frame through at channels=ab, strength 0.7 - that's a legitimate batch-grading trick).

Install

ComfyUI Manager → search ComfyUI-JH-PixelPro, or:

cd ComfyUI/custom_nodes
git clone https://github.com/jetthuangai/ComfyUI-JH-PixelPro.git
cd ComfyUI-JH-PixelPro
pip install -r requirements.txt

Restart ComfyUI; it's under ComfyUI-JH-PixelPro/color. Pack needs ComfyUI ≥ 0.43.x, Python ≥ 3.10, and kornia (the LAB round-trip is Kornia's), plus mediapipe, opencv-python-headless, scipy. On CPU a 2K match runs in a few hundred milliseconds - not realtime, but fine for a still. On GPU it's a non-event. If skin-tone drift after an AI pass is your recurring headache, this is the node that turns a manual eyeball-fix into a one-wire step.

CategoryComfyUI-JH-PixelPro/color

Inputs (5)

NameTypeDefaultDescription
image_targetIMAGE
image_referenceIMAGE
channelsCOMBOabab = match chroma only, preserve target luminance (pro retouch default — avoids washing out the AI output's lighting). lab = match L + a + b (full tone transfer including brightness).
strengthFLOAT1.000–1Blend factor. 0 = identity target (bypass), 1 = full match. Typical pro dose 0.6–0.8 for natural skin-tone correction.
maskoptMASK

Outputs (1)

NameTypeDescription
image_matchedIMAGE