cv2.intensity_transform.contrastStretching
A tone curve you draw with two points
- input
- result
Autoscaling (cv2.integral-style min/max stretch) hands the whole decision to the image. This node hands it back to you: you name two input levels and where you want them to land, and the curve is drawn between them. Two control points, four numbers, one linear remap.
It's the most controllable of the intensity_transform wrappers in bmad4ever/comfyui_cv, and - fair warning - the one where the default widget values will give you a black rectangle if you don't set anything.
The mechanism, in the author's own terms
The tooltip on r1 says it all: "INPUT level of the first control point (0-255): the black end of the range you want to stretch." Map that:
r1- the input level you want to become your new black.s1- the output levelr1gets mapped to. Belowr1, the curve is linear from(0, 0), so darks below your chosen point stay dark and keep their ordering.r2- the input level for the white end (must be greater thanr1).s2- the output levelr2gets mapped to.s1 < s2raises contrast between the two points;s1 > s2inverts that band.
So it's a piecewise-linear tone curve: a toe, a straight middle, a shoulder. Which is the same shape Instagram filters and Photoshop's Levels use, minus the sliders.
Concrete mappings to have on hand:
identity r1=0 s1=0 r2=255 s2=255
classic contrast stretch r1=40 s1=0 r2=210 s2=255
gentle lift r1=20 s1=0 r2=235 s2=240
inverted band r1=60 s1=200 r2=180 s2=40
Inputs and outputs
input takes an IMAGE, MASK or NPARRAY; four INT widgets follow, then nothing else. The output result echoes the input's format - an IMAGE comes back as an IMAGE, so it sits inline in a photo chain - and the node is batch-aware when you hand it an IMAGE batch, looping frame by frame.
Like the rest of the module it's carried out through a lookup table, which the author flags as the reason 8-bit input is what it wants. Float data should go through CV Cast Array first.
What it's good for
Deliberate, reproducible exposure decisions on a batch. Because it's four fixed numbers and a linear map, the same stretch across fifty frames is exactly the same operation, which is what you want when you're grading a clip rather than fixing a single still. Also handy for pushing a mask's useful band into a hard 0/255 before thresholding: set r1/r2 to the interval you care about and the rest of the range flattens to the ends.
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, recent ComfyUI (V3 node API), OpenCV curated at 5.0.0.93. contrastStretching lives in the contrib intensity_transform module, so a contrib wheel is required. If the node is missing entirely, that's the first thing to check: installing plain opencv-python over a contrib build strips the contrib submodules from the shared site-packages/cv2 silently, and tools/repair_opencv_contrib.py --check / --apply exists precisely for that.
Traps
The defaults are not identity. r1, s1, r2, s2 all start at 0. With s1 = s2 = 0 everything maps to black. The node is not broken - it's waiting for you to say what you meant. Type 0 / 0 / 255 / 255 to get a no-op, then work from there.
r2 must be greater than r1, and s1 < s2 if you want ordinary contrast. If your highlights came back darker than your midtones, you've flipped the band on purpose or by accident - that's the s1 > s2 case.
It's global. Four numbers for the whole frame. For "this region is flat, that one is fine", build a mask and composite: stretch a copy, then blend through the mask.
One outlier, again. If you set r1/r2 from a min/max you measured, a hot pixel sets the top of your range. Percentiles from CV Array Statistic are the better source.
The usual disclosure applies: the pack's README describes a personal, LLM-assisted project that isn't recommended for production without your own review. This node is a thin wrapper around a documented OpenCV function, so the sanity check is cheap: feed it a gradient ramp and verify the curve does what your four numbers say.
Inputs (5)
| 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. | |
| r1 | INT | 0-2147483648–2147483647 | - - - |
| s1 | INT | 0-2147483648–2147483647 | - - - |
| r2 | INT | 0-2147483648–2147483647 | - - - |
| s2 | INT | 0-2147483648–2147483647 | - - - |
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. |