CV White Balance (xphoto)
Fixing a colour cast so your reference photo is actually usable
- image
- balanced
A photo shot under tungsten comes out orange. Under shade, blue. If you're using that photo as a style reference, a colour source, an IP-Adapter input, or the first frame of an img2img pass, that cast isn't a stylistic choice - it's a measurement error you're feeding the model. White balance is the pre-processing step that removes it.
CV White Balance (xphoto) wraps the three cv2.xphoto balancers behind one dropdown, because they share an interface and a purpose and choosing between them is a judgement call about your photo, not a different operation.
The three methods
- simple (default) - clips a percentage of the extreme pixels per channel and stretches the rest. Fast, predictable, and the one to try first. Its knob is
clip_percent(default 2): the percentage of darkest and brightest pixels ignored per channel before stretching. At0the absolute extremes are used, so a single blown highlight can wreck the whole balance - 1 to 5 is the useful range. - grayworld - assumes the scene averages to gray and corrects until it does. Good for mixed scenes, and it goes spectacularly wrong on a legitimately one-coloured image: point it at a sunset or a forest and it dutifully neutralises the very thing that made the photo interesting. Its knob is
saturation_threshold(default 0.9, the same for the next method): pixels above that saturation are treated as already clipped and excluded from the estimate. Lower the threshold to ignore more of a strongly coloured subject. - learning-based - a small built-in regression model. The most robust on real photographs, and the one to reach for when the scene genuinely is dominated by one colour and grayworld washes it out.
Input is any 3-channel image, and the output echoes the input's format - an IMAGE in, an IMAGE out; an NPARRAY in, an NPARRAY out. That's the node being format-polymorphic, so it drops into a chain without a conversion step either way. One output: balanced.
Where it belongs in a pipeline
Early, and usually unnoticed. Colour-cast removal is a pre-processing step by design - the node's own description says as much - because everything downstream assumes your reference photo's colours mean something. Colour matching, palette extraction, style transfer, and any "make this look like that" workflow all key off the reference's actual colours, and a cast shifts all of them.
It's the deterministic, millisecond end of post-processing, in the sense the KB's post-processing layer describes: no model, no diffusion pass, a known operation applied to pixels. Which is exactly the point - this is not a job for img2img.
One clarification, because "auto white balance" means different things in different tools: grayworld and learning-based are estimation methods that guess the illuminant, while simple is closer to a histogram stretch and estimates nothing. That's why simple can look flat but almost never looks absurd. With a known neutral object in frame, an honest gray-point correction by hand beats all three.
Installing it
Manager → ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart. Python ≥ 3.12, a recent ComfyUI on the V3 node API, opencv-contrib-python-headless~=5.0.0.93.
The contrib wheel is not optional for this node. cv2.xphoto is a contrib module, and the four different opencv-*-python distributions all install into one site-packages/cv2 - so a plain opencv-python installed by some other pack after this one silently deletes xphoto, along with a fair chunk of the rest of the pack. If the node starts erroring with a missing module, that's what happened. tools/repair_opencv_contrib.py --check diagnoses it; --apply fixes it.
No models to download: the learning-based method's model is baked into the OpenCV build. GPL-3.0, forked from opencv-comfyui, LLM-assisted, and the author's "not production-ready without review" caveat sits over the whole pack.
Where people get burned
A 4-channel or 1-channel image. The node requires three channels. An RGBA PNG or a grayscale input fails; convert first.
Grayworld on a sunset. Covered above, but it's the failure people actually hit and then blame the node for. The scene being one colour isn't a bug in the scene.
clip_percent at 0. One blown highlight - a specular glint, a sky patch - sets the top of the range for the whole channel and the balance comes out wrong in a way that's hard to attribute back to this widget. Leave it at 2.
Using it to fix a wrong exposure. White balance moves channel gains to neutralise a cast. If the photo is simply too dark or too bright, that's a different operation, and running white balance on it will happily produce a correctly-tinted but still wrong image.
Expecting it to match two photos exactly. This balances each image independently, which is close to, but not the same as, matching one photo to another's colour. For that, colour transfer against a reference is the right tool - the KB's post-processing essay covers that class of node and where it lives.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| image | COMFY_MATCHTYPE_V3 | Image with an unwanted colour cast. Must be 3-channel; the output echoes this input's format. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| method | COMBO | simple | Which balancer to use. Try 'simple' first; switch to 'learning-based' when the scene is genuinely dominated by one colour and 'grayworld' washes it out. |
| clip_percentopt | FLOAT | 2.00–49 | 'simple' only: percentage of the darkest and brightest pixels ignored per channel before stretching. 0 uses the absolute extremes, so a single blown pixel can ruin the balance; 1-5 is the useful range. |
| saturation_thresholdopt | FLOAT | 0.900–1 | 'grayworld' / 'learning-based' only: pixels whose saturation exceeds this are treated as already clipped and excluded from the estimate. Lower it to ignore more of a strongly coloured subject. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| balanced | COMFY_MATCHTYPE_V3 | White-balanced image, in the same format as the input. |