Nodes/ComfyUI CV/CV Color Range From Sample
ComfyUI Node

CV Color Range From Sample

Scribble on the colour, let the node work out the numbers

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Color Range From Sample
  • image
  • region
  • mask
  • center
  • tolerance
◄sigma2.5►
◄formatlinear - no circular channel (BGR/RGB/Lab/Luv/YCrCb/gray)►

What this is for

Thresholding by hand is a guessing game with three or six numbers in it. Open the HSV picker, try to find "the hue of this wall", then tune the tolerances until the mask looks about right - and then discover it doesn't transfer to the next frame because the numbers were fitted to one lighting condition.

This node inverts the workflow. You point at the colour you mean, and it measures the band for you: sample the pixels, take the per-channel centre and spread, keep everything within sigma spreads of that centre. Given a scribble over the wall - the region output of CV Annotate Points plugs in directly - you get a mask in one step instead of a tuning session.

How it works

With region connected, only non-zero pixels of that mask are sampled; without it, the whole image is sampled (which is fine for a swatch crop, and rarely what you want on a full frame). Then per channel it computes a centre and a spread, and the tolerance is sigma * spread.

The spread is where the care went in. On a linear channel it's the standard deviation. On a circular hue channel it's the circular standard deviation, computed modulo the period - so a reddish sample that straddles the 179/0 seam gets measured as a tight reddish cluster instead of as a spread of ±90 hue that would select the entire wheel. That's the same circular-awareness CV Color Range has on the thresholding side, applied here to the statistics.

Then everything within center ± tolerance is kept, and you get the mask plus the derived numbers.

Inputs and outputs

  • image - colour array to sample and threshold. A ComfyUI IMAGE arrives as BGR uint8, so convert to HSV/HLS first if you want the band expressed in hue terms.
  • sigma (default 2.5) - the band half-width in standard deviations. ~2–3 keeps most of a normally distributed colour region; raise it to be permissive, drop it near 1 for an aggressive pick. This is the one knob you'll actually touch.
  • format - which channel (if any) is a circular hue and its period. Set it to match the space you're sampling in; leave it linear for BGR/Lab.
  • region (optional, MASK) - which pixels to learn from. Nothing to do with which pixels get selected; that's decided by the resulting band. This distinction trips people up: a small scribble is fine and even preferable, because a tight sample gives a tighter spread.

Three outputs:

  • mask - uint8 0/255 of the in-range pixels. Straight into morphology or CV Array -> Mask.
  • center - the sampled per-channel centre, float64, one element per channel. Circular mean for a hue channel.
  • tolerance - the per-channel sigma * spread that was actually used.

Those last two are the good part. Wire them into CV Color Range on a different image and you've reused the band you just learned instead of re-learning it - which is how you get a selection that behaves consistently across a batch of frames rather than drifting with each one. You can also just read them: they're the numbers you'd have been guessing at.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
# restart ComfyUI

Or search ComfyUI CV in ComfyUI Manager (publisher bmad4ever). Needs Python ≥ 3.12, a V3-node-API ComfyUI, and opencv-contrib-python-headless~=5.0.0.93 (installed from the pack requirements). No models; it's numpy statistics plus a range check.

Common issues

  • The mask is empty. Your scribble landed on the background, or the sample is too tight - raise sigma. Very small regions are also a risk: ten pixels give a spread you can't trust.
  • The mask swallows the whole image. sigma too high for a sample that already had a wide spread, or the region caught a gradient. Resample from the middle of the colour, not across its shadowed edge.
  • Hue selection selects everything. Either format is linear (so the hue looks like a plain number and 179 vs 0 is treated as far apart), or the sample straddled a colour boundary. Set the circular format and the seam stops mattering.
  • Contrib nodes vanished from the pack. A non-contrib OpenCV wheel clobbered the shared site-packages/cv2; python tools/repair_opencv_contrib.py --check then --apply.
Categoryimage/CV/segmentation

Inputs (4)

NameTypeDefaultDescription
imageNPARRAY,IMAGEColour array to sample and threshold (set 'format' to match its colour space). 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.
sigmaFLOAT2.50–20Band half-width in standard deviations: tolerance = sigma * spread per channel. ~2-3 keeps most of a normally-distributed colour; raise to be more permissive.
formatCOMBOlinear - no circular channel (BGR/RGB/Lab/Luv/YCrCb/gray)Which channel (if any) is a circular hue, and its period. The hue center/spread are then computed circularly.
regionoptNPARRAY,MASKMask marking which pixels to sample (non-zero = sample). The polygon output of 'CV Annotate Points' plugs in directly. Omit to sample the whole image. 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 (3)

NameTypeDescription
maskNPARRAYuint8 0/255 mask of the in-range pixels.
centerNPARRAYPer-channel sampled center (circular mean for the hue channel) - float64, one element per channel.
toleranceNPARRAYPer-channel sigma * spread used for the band - feed it and 'center' into 'CV Color Range'.