cv2.intensity_transform.gammaCorrection
The exposure knob that doesn't wash out your whites
- input
- result
When a render comes out flat, the reflex is to drag a brightness slider. Don't. Brightness in the literal sense is additive - it adds a constant to every pixel, which lifts black to grey, flattens contrast and clips your highlights on the way. Gamma is a power curve: out = 255 * (in/255)^γ. It lifts shadows, leaves white where it is, and never clips. Different operation, much better result.
This node is the intensity_transform module's gamma, wrapped in bmad4ever/comfyui_cv. One image in, one gradient of exposure out.
Mechanism, and the one number you set
gamma is the exponent, and the pack's tooltip spells out the direction: below 1 brightens midtones and lifts shadows, above 1 darkens them, and 1.0 is a no-op. So 0.7 is a gentle brightening, 1.3 is a darkening, 0.5 is heavy. Note the inverted feel - smaller number, brighter image - which trips people up on the first try.
Under the hood it's a lookup table per 8-bit level, which has one important consequence: float input is rejected. The author's tooltip says so explicitly and names the fix - CV Cast Array (or cv2.convertScaleAbs) first. If you're working in NPARRAY space with 0..1 floats, that's the step you're missing when the node refuses.
The input is input (IMAGE, MASK or NPARRAY) and the output is result, which echoes the input's format. IMAGE in, IMAGE out - it slots between a Load Image and a Save Image with nothing else in between. It's also batch-aware: give it a whole IMAGE batch and the pack loops frame by frame.
Mechanically these intensity_transform functions return their result through a pre-allocated buffer instead of shipping a Mat, which is why there's no dst input on the node: the wrapper sizes and allocates that buffer from the input for you.
Where it's genuinely the right tool
- Fixing a flat render before saving, instead of re-rolling the seed or burning a second sampler pass. Deterministic, instant, and it doesn't touch composition - which is the whole argument for reaching for a curve rather than img2img.
- Matching two sources roughly. When a composited element sits a stop darker than its background, a gamma nudge is the fast first move. If the colour is off rather than the level, you want colour matching instead.
- Linearizing / de-linearizing. sRGB images are gamma-encoded; a curve near 0.4545 approximates the sRGB-to-linear step and 2.2 (or the exact sRGB piecewise curve) approximates going back. Treat that as an approximation for experimentation - if you're doing real colour management, use a proper colour-managed path, not this node.
Installing the pack
Manager → search comfyui_cv, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Python ≥ 3.12 and a recent ComfyUI built on the V3 node API. OpenCV is pinned in spirit to 5.0.0.93.
Contrib matters here. intensity_transform is a contrib module, so this node only exists if your installed cv2 is a contrib build. Installing plain opencv-python on top of a contrib wheel overwrites the shared cv2 directory with a core-only build, and contrib nodes vanish with no error message - just an empty search. tools/repair_opencv_contrib.py --check diagnoses that; --apply repairs it.
Traps, briefly
Gamma 0. A fresh node's widget starts at 0 in the schema, and 0 is not "identity" - set it deliberately. 1.0 is the no-change value.
Applying it twice. Two gamma 0.8 passes is gamma 0.64, which is much stronger than it sounds. Power curves compose multiplicatively.
Float input. Already covered, but it's the number one error here. Cast to 8-bit first.
Expecting local adaptation. This is a global curve on every pixel. For "this dark region needs lifting, the rest is fine", you want a mask, not a different gamma.
For completeness: the pack's README is honest that it's a personal project with heavy LLM involvement, warns about possible overfitting to its own test cases, and says not to run it in production without your own review. This particular node is a thin wrapper around a stable OpenCV function - the risk is in the library around it, not the maths.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| input | COMFY_MATCHTYPE_V3 | 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. | |
| gamma | FLOAT | 0.0000-1e+38–1e+38 | - - - |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'input' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |