CV Color Range
InRange with a hue that knows it wraps at 179
- image
- center
- tolerance
- mask
What this is for
Chroma-keying without a green screen budget. Select pixels within a tolerance of a target colour and get a mask. The usual way to do this in OpenCV is cv2.inRange with a lower and an upper bound, and the usual way to get those bounds wrong is to pick a red that lives at hue 2 and then have to write two separate bands because hue wraps at 179 back to 0. This node takes a centre and a tolerance instead, and - this is the actual feature - knows when a channel is circular.
Tell it via format that channel 0 is a hue with period 180, and a band centred on 0 keeps values near 179 too. One pass, one band, red across the seam. That single sentence is why this node exists.
How it works
For each channel it keeps a pixel when it's within center ± tolerance. On a circular channel it computes the difference modulo the period, so "within 4 of 0" means {178, 179, 0, 1, 2, 3, 4} rather than an unpopulated range. A tolerance of at least half the period on a circular channel accepts every hue, which is a neat way to say "ignore hue, threshold saturation and value only".
center and tolerance take one value per channel - a CV Scalar literal like (60, 200, 200), or the center/tolerance outputs of CV Color Range From Sample - and a single value broadcasts to every channel.
The format dropdown matters more than it looks. Numpy arrays carry no colour-space tag, so the node cannot know whether channel 0 is BGR blue or HSV hue. You tell it. linear - no circular channel is the default and correct for BGR/Lab/gray; pick an HSV/HLS entry when it isn't.
Inputs and outputs
image- the array to threshold, in whatever space you want it thresholded in. A ComfyUI IMAGE arrives as BGR uint8, so if you want intuitive hue/sat/val selection, put acv2_cvtColorin front of it and switch the image to HSV first. This is the step people skip and then wonder why "hue 60" selects a blue.center- the target colour, one value per channel.tolerance- the accepted half-width per channel.(15, 60, 60)is loose on saturation and value, tight on hue: usually what you want, because hue is the channel you trust and the other two wobble under shading.format- which channel is a circular hue, and its period.
One output: mask, uint8 0/255. Wire it into morphology (cv2_morphologyEx) to clean specks, cv2_bitwise_and to keep the pixels, or CV Array -> Mask if you're leaving the CV world and handing a mask to core nodes. From there, CV Connected Components (Split Mask) or CV Components Touching Border are the natural next steps if you want per-region masks.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
# restart ComfyUI
Manager users: ComfyUI CV (bmad4ever). Python ≥ 3.12, V3-node-API ComfyUI, and opencv-contrib-python-headless~=5.0.0.93 from the requirements file. No models needed - colour thresholding is all numpy and cv2.
Common issues
- The mask is empty or selects the wrong thing entirely. Nine times out of ten the image is still BGR while you configured HSV values. Convert first, then set
formatto match. - Red only partly selected. You used the linear format. Set the format with the circular 180-period channel and red works across the 179/0 seam in a single node.
- The mask is noisy. That's tolerance, not the node. Tighten hue, and remember that thresholds on a compressed video frame inherit the compression - a light blur before thresholding beats a morphological cleanup after it.
- Everything selected. A circular tolerance >= half the period accepts all hues; check your tolerance vector if you only meant to loosen saturation.
- Contrib nodes vanished pack-wide. A non-contrib wheel overwrote
site-packages/cv2;tools/repair_opencv_contrib.py --check, then--apply.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE | Colour array in whatever space you want to threshold (BGR, HSV, Lab, a single hue channel...). Set 'format' to match. A ComfyUI IMAGE arrives as BGR uint8. 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. | |
| center | NPARRAY | Target colour, one value per channel, e.g. a 'CV Scalar' (60, 200, 200) for green in 8-bit HSV. A single value broadcasts to every channel. | |
| tolerance | NPARRAY | Accepted half-width per channel, e.g. (15, 60, 60). A pixel is kept when every channel is within center +/- tolerance; for a circular channel a tolerance >= period/2 accepts every hue. | |
| format | COMBO | linear - no circular channel (BGR/RGB/Lab/Luv/YCrCb/gray) | Which channel (if any) is a circular hue, and its period - numpy arrays carry no colour-space tag. All other channels are treated linearly. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| mask | NPARRAY | uint8 0/255 mask of the in-range pixels. |