cv2.xphoto.applyChannelGains
ApplyChannelGains ships with the gains at 0 — i.e. it hands you a black image
- src
- result
Three numbers, one multiply. xphoto.applyChannelGains scales each colour channel of an image independently - which is manual white balance, a tint, and exposure trim all at once, and it's as deterministic as an arithmetic op gets. The thing to know before you wire it: all three gains default to 0, so an untouched node multiplies your image by zero and returns black. Set them to 1.0 for the identity.
Why you'd reach for it
Because colour fixes do not need a diffusion pass. The case for deterministic colour work makes itself - the failure mode it exists to prevent is re-rolling an image because "the colours are off" when a per-channel multiply would have settled it in a millisecond. Concretely:
- Match a composited subject to its new background. Cool background, warm subject: drop
gainBa touch, raisegainR. - Neutralise a cast. A tungsten-lit frame is the classic: blue channel starved, so
gainBgoes up. - Dial in a stylised grade you then reuse as a fixed recipe - because the gains are numbers in a widget, they're reproducible across a whole batch in a way a prompt never is.
OpenCV's own xphoto module pairs this with white-balance estimators; those are class APIs (createSimpleWB, createGrayworldWB) that the pack's node generator doesn't reach, so here you supply the gains yourself. That's not a limitation so much as a division of labour: measure with something else, apply with this.
How it works
For each channel: dst = src * gain, saturating at 255 for 8-bit input. It's applied per frame. The upstream contract says the input is a three-channel BGR image (CV_8UC3 or CV_16UC3), and the pack respects that - feed it a mask or a single-channel array and it's promoted to three channels first, since a per-channel gain is meaningless on a grayscale canvas.
The inputs that matter
src- the image.NPARRAY,IMAGEorMASK; a batch is handled frame by frame.gainB,gainG,gainR- the multipliers, in that order. 1.0 = no change. Note the order: blue first. If you're coming from an RGB slider mental model you will get a colour-shifted result and blame the node.- Green is the reference channel in practice. The pack's own tooltip says as much - leave
gainGat 1.0 and balance the other two against it, rather than nudging all three and chasing your tail.
Output: result, a single socket echoing src's format. It's a low-level wrapper, so an IMAGE link still comes back as NPARRAY - convert with CV Array → Image (or the wildcard bridge, * → CV Array, on the way in).
Install
ComfyUI Manager → search ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart afterwards. The pack needs opencv-contrib-python-headless~=5.0.0.93 - xphoto is a contrib submodule, so this node exists only on a contrib install. All four OpenCV wheel flavours share one site-packages/cv2, and installing a non-contrib wheel over a contrib one quietly wipes the submodules; tools/repair_opencv_contrib.py --check diagnoses it. You also need Python 3.12+ and a ComfyUI new enough for the V3 node API - on an older build the pack won't import at all.
Traps and gotchas
- Gains of 0 → black image. Not a crash, not a warning. Just a black frame, and a confused ten minutes. Start at 1/1/1.
- Gains above 1 clip. There's no headroom in an 8-bit tensor: raising
gainBto fix a dark blue channel will also flatten every blue highlight in the frame. If you need range, convert the image to float first rather than pushing harder here. - It's linear, so it is not a colour correction. A gain is one number per channel; it can't fix a hue rotation, and it can't fix a photometric response that isn't a multiply. If you want a proper fit against a known reference, that's what the pack's colour-correction nodes (Create CCM Model → Apply Color Correction) are for, and
Color Match Imagein the ecosystem still does the mean/std transfer trick. This node is the dumb, instant, always-works version. - Gains are not measured for you. Eyeball them, then write them down - a saved workflow with the numbers in it is the deliverable.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| src | COMFY_MATCHTYPE_V3 | Input three-channel image in the BGR color space (either CV_8UC3 or CV_16UC3) The image output(s) echo 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. | |
| gainB | FLOAT | 0.0000-1e+38–1e+38 | gain for the B channel |
| gainG | FLOAT | 0.0000-1e+38–1e+38 | gain for the G channel |
| gainR | FLOAT | 0.0000-1e+38–1e+38 | gain for the R channel |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |