Nodes/ComfyUI CV/CV Scalar
ComfyUI Node

CV Scalar

The tiny constant-vector node the raw cv2 wrappers can't live without

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Scalar
    • nparray
    ◄values(0, 0, 0)►
    ◄dtypefloat64 (cv2 Scalar default)►

    cv2.inRange needs an [low, high] bound pair. cv2.randn needs a mean and a stddev vector. cv2.compare needs a src2. All of those are "a short list of numbers", and in ComfyUI a node input can't just be a list - sockets carry typed values, and there is no native array literal to type (35, 60, 60) into. So the pack ships this: one STRING widget parsed into an NPARRAY, so you can hand a constant vector to any NPARRAY-typed input.

    That's the whole job. It's the least glamorous node in a pack of 780-odd nodes, and it appears in twenty of the pack's example workflows, which tells you how often you actually need it.

    How to write the literal

    values takes a number or a tuple literal. (35, 60, 60) gives you three elements - one per channel, which is the shape the bounds, the means and the compare arguments want. A bare single number like 128 broadcasts to four scalar slots, emitting (128, 128, 128, 128), which is OpenCV's own Scalar convention and not a typo.

    The tooltip gives the polynomial case directly: (1.3, -0.6, 0.3) as coefficients for CV Polynomial Radial Map. Up to 32 elements, so arbitrary coefficient arrays are fine.

    dtype defaults to float64 (cv2 Scalar default), which is what OpenCV expects for Scalar-like arguments - and it's worth leaving there unless you have a specific reason. Feeding a float64 array into something that wanted uint8 bounds is the kind of mismatch that produces a range that selects nothing, with no error to point at.

    Output is nparray, a 1-D constant array. Wire it into anything.

    Where you'll actually use it

    • cv2_inRange lowerb / upperb - the classic HSV colour-range node. Two of these, one for each bound, and you have a colour keyer. workflows/20_color_range_playground.json does exactly that.
    • cv2_randn mean / stddev - noise generation with control over per-channel level, which is how you add sensor noise without touching a model.
    • cv2_compare src2 - threshold or equality against a constant vector.
    • Coefficient inputs - the radial map family, filters, anywhere the API takes a short numeric vector.
    • Sensible defaults for pixel constants - workflows/17_seamless_clone.json, 87_latent_cv_playground.json and a dozen others use it for mask fills and border values.

    It's the twin of CV Array Size, which does the same job for dsize-style Size parameters: where OpenCV wants a small fixed-shape vector and ComfyUI gives you sockets, the pack gives you a literal node.

    One design consequence worth noticing

    By living on the NPARRAY socket type rather than a FLOAT list, this composes with the rest of the pack for free. The same type is used by images-as-arrays, point sets, maps, matrices and feature tables - so a scalar array can be concatenated (CV Concat Arrays), reshaped (CV Reshape Array), cast (CV Cast Array), inspected (CV Inspect CV Data) or previewed like anything else. If you need a constant row in a feature matrix rather than a constant vector for a cv2 call, that combination is how you get it.

    Install

    Ships in ComfyUI CV (bmad4ever/comfyui_cv), GPL-3.0 fork of opencv-comfyui:

    cd ComfyUI/custom_nodes
    git clone https://github.com/bmad4ever/comfyui_cv
    pip install "opencv-contrib-python-headless~=5.0.0.93"
    # restart ComfyUI
    

    Manager users: search the pack title. Python ≥ 3.12 and a ComfyUI on the V3 node API - if the whole pack is invisible, that's the first thing to check, before you start suspecting the OpenCV wheel.

    The catch, and it's a fair one

    It's a STRING widget parsed at execution time. So a malformed literal doesn't fail at graph-load time with a red border; it fails when the graph runs, and the message comes from the parser rather than from the node you wired it into. If a cv2_* wrapper is complaining about shapes, check the literals feeding it first - a stray bracket or a comma in the wrong place is a much more common culprit than the wrapper itself. And because it's a constant, there is no seeding, no randomness and no caching subtlety: the same string always gives the same array, which is exactly what you want from a node whose entire purpose is "hold this number".

    Categoryimage/CV/low-level

    Inputs (2)

    NameTypeDefaultDescription
    valuesSTRING(0, 0, 0)Number or tuple literal, e.g. '128' or '(35, 60, 60)'. One element per channel for bounds/means; a bare single number broadcasts to four scalar slots. For polynomial radial-map coefficients use e.g. '(1.3, -0.6, 0.3)'.
    dtypeCOMBOfloat64 (cv2 Scalar default)Element type of the emitted array. float64 is what cv2 expects for Scalar-like arguments.

    Outputs (1)

    NameTypeDescription
    nparrayNPARRAY1-D constant array. A bare number N emits (N, N, N, N); tuple literals emit one element per entry.