Nodes/ComfyUI CV/cv2.line
ComfyUI Node

cv2.line

Draw on an IMAGE or MASK without leaving ComfyUI (and yes, that color is BGR)

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
cv2.line
  • source
  • pt1
  • pt2
  • result
◄color(0, 0, 0, 0)►
◄thickness1►
◄lineTypeLINE_AA►
◄shift0►

Every CV pipeline eventually needs a rubber band: draw the detected edge, draw the box, draw the guide line, draw a crosshair so you can see where your crop landed. cv2.line is that, exposed as a node. Two points, a colour, and you get your canvas back with a line on it.

It's also the friendliest node in this family to learn the pack's conventions on, because it's one of the ones that politely hands your IMAGE straight back to you instead of dumping an array on the floor.

How it works

OpenCV's line() draws into a canvas and returns it. The pack wraps that canvas as an input socket, so source is whatever you want to scribble on, and the output echoes the input's format. That echo is deliberate and worth internalising: it's how the pack models "this operation produces a picture of the same frame". Link an IMAGE, get an IMAGE back. Link a MASK and you get a MASK back - drawing on a mask is a legitimate move, not a type error. Link an NPARRAY and it stays an NPARRAY.

Under the hood the pack hands cv2 a private copy of your array before drawing. cv2's drawing primitives mutate their canvas argument in place, and that array might be another node's cached output. So your upstream node doesn't get scribbled on. Good design, and it means you can draw twice off the same source and both results are independent.

The inputs and outputs that matter

source - IMAGE, MASK or NPARRAY, as above.

pt1 and pt2 are CV_TUPLE sockets holding (x, y) in pixels. The tooltip makes the point that a CV_TUPLE travels as one value: it can't arrive half-connected. Either type both components inline, or wire it from CV Tuple. Coming from the geometry nodes, this is exactly the socket that cv2.minEnclosingCircle's subpixel center and friends are shaped for.

color is a string literal: "(0, 255, 0)" - and it's BGR, OpenCV order. So that literal is green. A bare number broadcasts to every component, "255" meaning white. Anything past the target's channel count is ignored by OpenCV, so a 4-component BGRA literal is harmless on a 3-channel canvas.

Of the optionals, thickness (default 1) is the one you'll touch. lineType presets to LINE_AA - anti-aliased, which is nicer than OpenCV's own default and the reason your diagonal lines will look soft rather than stair-stepped. shift is fractional bits: coordinates get multiplied by 2^shift, so shift: 4 with pt1 = (100, 100) actually draws from (6.25, 6.25). Useful for sub-pixel placement off a geometry node, baffling if you enable it by accident.

One output, and it echoes source's format.

Installing the pack

ComfyUI CV (bmad4ever/comfyui_cv) - search "comfyui_cv" in ComfyUI Manager, or:

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

Restart ComfyUI. Python ≥ 3.12 and a V3-API ComfyUI, one pinned contrib OpenCV wheel, no models to download. This particular node is a good first test that the install worked: it appears in image/CV/low-level/cv2 L and needs nothing else.

Where people get burned

The colour is wrong. You typed RGB. It's BGR. "(0, 0, 255)" is red, and the pack's colour inputs all speak this dialect.

Nothing appears. Check the coordinates are inside the frame. There's no normalised 0–1 mode here; pt1 is absolute pixels in the array's own space, so a line from (0,0) to (0,0) draws a single dot, and coordinates off-canvas simply don't render.

Both endpoints stuck at (0, 0). That's the widget default, and a zero-length line is invisible - which reads as "the node does nothing". Set the numbers.

A missing CV_TUPLE wire caused by a half-typed pair. If only one component is filled in, the value still travels as a whole; use CV Tuple to author it explicitly and you'll never wonder which half took.

Because it's per-frame safe in this pack, a whole IMAGE batch gets a line drawn on every frame - handy for stamping a watermark-ish corner mark across a batch, or annotating every frame with the same fixed guide.

One honest caveat that applies to the whole pack: it's an independent project, unaffiliated with the OpenCV team, and its code was written with heavy LLM assistance. The author's own README says not to use it in production without reviewing the source. A line drawing routine is about as safe as this gets, but it's why the nodes are named "ComfyUI CV" and not "OpenCV for ComfyUI".

Categoryimage/CV/low-level/cv2 L

Inputs (7)

NameTypeDefaultDescription
sourceCOMFY_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.
pt1CV_TUPLE0,0First point of the line segment. One value with 2 components (x, y) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place.
pt2CV_TUPLE0,0Second point of the line segment. One value with 2 components (x, y) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place.
colorSTRING(0, 0, 0, 0)Line color. cv2 Scalar as a literal, e.g. "(0, 255, 0)" (BGR) or "(0, 255, 0, 64)" (BGRA). A bare number broadcasts to every component, so "255" means (255, 255, 255, 255). Components past the target's channel count are ignored by OpenCV.
thicknessoptINT1-2147483648–2147483647Line thickness. Preset to the OpenCV default (1).
lineTypeoptCOMBOLINE_AAType of the line. See #LineTypes.
shiftoptINT0-2147483648–2147483647Number of fractional bits in the point coordinates. Preset to the OpenCV default (0).

Outputs (1)

NameTypeDescription
resultCOMFY_MATCHTYPE_V3Echoes the 'source' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY.