Nodes/ComfyUI CV/cv2.inRange
ComfyUI Node

cv2.inRange

Turn 'keep pixels in this range' into a mask — and the hue-wrap trap that comes with it

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
cv2.inRange
  • src
  • lowerb
  • upperb
  • nparray

Selecting by colour is one of the oldest tricks in computer vision, and cv2.inRange is its one-liner: give it a lower bound and an upper bound, get back a binary mask of the pixels that fall inside. Green screens, skin tones, a specific shade of teal in a sky, the red pixels of a QR-ish marker - all of it is this function.

This is the raw wrapper from bmad4ever/comfyui_cv. It works, and for plenty of jobs it's exactly the right node. But the pack ships two curated alternatives that solve a problem this raw version can't, so read the trap section before you commit.

Mechanism

Per channel, inclusive bounds: a pixel survives if lowerb[c] <= src[c] <= upperb[c] on every channel. Output is a two-valued mask, 0 or 255 - OpenCV's binary image convention, not a 0..1 float mask.

src, lowerb and upperb all take an NPARRAY, IMAGE or MASK. That's a deliberate widening by the pack: instead of typing three literals, you can feed bounds from a CV Scalar node, or take them from CV Color Range From Sample, which measures the centre and spread of a scribbled region and hands you back the numbers. Bounds can also be a single value that broadcasts, which is what you want for a grayscale threshold band.

The output is nparray - NPARRAY, and it stays NPARRAY on purpose. A 3-channel image in gives a 1-channel binary out, which a socket type can't express; the pack's comment on this explains that handing a three-channel white picture to a mask consumer would be worse than making you convert deliberately. So you bridge it: CV Array → Mask turns it into a real ComfyUI MASK you can feed to anything mask-shaped, or keep it as an array and chain more cv2 ops on it (bitwise ops, morphology, cv2_findContours).

The trap: hue is a circle

Convert to HSV first - cv2_cvtColor with COLOR_BGR2HSV - because "the greens" as BGR triplets is nobody's idea of fun. But OpenCV's HSV hue runs 0..179, and it wraps. Red sits at both ends: 0-ish and 175-ish. A single inRange band centred on red either misses half of it or you build two bands and OR them with cv2_bitwise_or.

That's exactly what CV Color Range in this pack exists for. It's circular-aware: you tell it which channel is a hue and its period, and the band wraps, so a hue centred on 0 also keeps values near the top of the range in one pass. It also takes a centre plus a per-channel tolerance rather than you working out lower/upper by hand, and a single tolerance value broadcasts. There's a CV Color Range From Sample companion that learns the band from the image itself - sample the pixels (optionally inside a mask), and out come center and tolerance you can feed into CV Color Range to reuse on another frame.

Short version: use this raw node when your threshold is a simple non-circular band and you want to see the numbers. Use CV Color Range the moment hue is involved.

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). inRange is core OpenCV so it doesn't need the contrib wheel - but the pack as a whole does, and installing a non-contrib opencv-python on top of a contrib one silently empties the contrib submodules, which makes contrib nodes vanish with no error at all. tools/repair_opencv_contrib.py --check diagnoses; --apply fixes.

Practical notes

Range, not value. inRange on an 8-bit image works in 0..255 per channel. If your NPARRAY is float 0..1, your bounds need to match it. Dtype mismatches are the classic silent failure here - a mask that comes back all black.

Bounds are inclusive on both ends. If two adjacent bands are supposed to tile a range without overlap, mind the shared endpoint.

Preview before you trust. Preview CV Array shows the mask as imagery; a quick look catches the inverted band (pixels you wanted, everything else white) far faster than debugging downstream.

The pack's own README is upfront that it's a personal, heavily LLM-assisted project, not production software. For a threshold-and-mask step in a personal workflow that's an acceptable trade; just verify the mask on your own footage, which you were going to do anyway.

Categoryimage/CV/low-level/cv2 I

Inputs (3)

NameTypeDefaultDescription
srcNPARRAY,IMAGE,MASKfirst input array. 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.
lowerbNPARRAY,IMAGE,MASKinclusive lower boundary array or a scalar. 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.
upperbNPARRAY,IMAGE,MASKinclusive upper boundary array or a scalar. 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.

Outputs (1)

NameTypeDescription
nparrayNPARRAY—