CV Draw Labels
Name your keypoints so debugging isn't a guessing game
- source
- points
- labels
- image
- count
Why you'd reach for this
A numbered dot cloud is fine until it isn't. The moment you care which dot is the wrist and which is the fingertip - or which detection score belongs to which box, or which QR code said what - you need text next to the point.
That's the gap this fills. ComfyUI core's Draw BBoxes can label COCO-style boxes; it can't caption an arbitrary point set. This node labels N points with N strings, in order.
How it works
It walks the point array and stamps a label next to each one with cv2.putText, offset by offset_x and offset_y (defaults 4 and −4, i.e. up and to the right of the point), scaled by font_scale, in your colour at your stroke width.
Leave labels unconnected and each point is captioned with its own index - 0, 1, 2 - which is honestly the mode you'll use most, because it's how you find out the order of an array you didn't produce. (It's also how you spot that a MediaPipe hand node numbers its 21 keypoints in a specific order rather than the order you assumed.)
Wire labels and you get real text. The important detail: it's an array of strings or numbers, one per point, not a comma-separated string. CV Text To Array types one per line and produces exactly that; CV Numbers To Array does the numeric version; and several detectors already emit usable text arrays - a QR or barcode detector's texts, CV Load Labels' array, a classifier's label output. If the array length doesn't match the point count, the node raises with both counts, which is a much better outcome than silently mislabelling everything.
outline defaults on, and it's not decoration. These labels land on whatever the photograph happens to show, and a flat single-colour label at font scale 0.4 is often illegible over texture. With it on, the node picks a contrasting halo - black behind a light label, white behind a dark one - by looking at your foreground colour. Turn it off if you're compositing text onto a flat synthetic canvas.
Points with a non-finite coordinate are skipped without renumbering the rest: the pack's convention is that NaN means "no position for this item", not "an error". That's what lets you blank entries out of a gated array and keep the labels aligned with the surviving rows.
Inputs and outputs that matter
- points -
Nx1x2. None or empty passes the image through,count = 0. - font_scale - 0.4 by default. Shrink it for a dense set; a labelled 21-point hand wants about that, a labelled 500-point cloud wants almost nothing.
- color, thickness - the text itself.
- labels (optional) - the array described above. The single most useful optional input in this section of the pack.
- offset_x / offset_y - nudge labels off the dots. With a big marker underneath, 4/−4 isn't always enough.
- outline - the halo.
- image and count. The count tracks the point array you fed in rather than the labels that survived, so a blank canvas reporting a non-zero count means the points are off-canvas, stacked, or blanked out - not that the node failed.
Install
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 and a current ComfyUI (V3 node API), or install the pack as ComfyUI CV from Manager. CPU drawing only.
Common issues
- Labels unreadable. Raise
font_scaleand make sureoutlineis on; also checkcolorisn't a BGR tuple on a single-channel MASK, where only the blue component survives. - All labels say "0". You wired a single text value where an array belongs. Use
CV Text To Array. - Count mismatch. Array length ≠ point count. They describe the same set, so they have to be built together.
- Text overflows the frame. Points near the right edge with a positive
offset_x. Negative offsets or a smaller font. - Contrib trap. The pack needs
opencv-contrib-python-headless; installing a plainopencv-pythonover it empties the shared contrib submodules and contrib nodes vanish.tools/repair_opencv_contrib.py --check, then--apply.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| source | COMFY_MATCHTYPE_V3 | Image or mask to draw on (a copy is made). 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. | |
| points | NPARRAY | Nx1x2 points to label (or None -> passthrough). | |
| font_scale | FLOAT | 0.40.1–8 | cv2 text scale; shrink it for dense point sets. |
| color | STRING | (0, 255, 255) | Color as a single value (broadcast to all channels) or BGR tuple, e.g. '255' or '(0, 255, 255)'. Shorter tuples are zero-padded; longer tuples are truncated. |
| thickness | INT | 11–32 | Text stroke width in pixels. |
| labelsopt | NPARRAY | (N,) ARRAY of strings or numbers, one per point - not a comma-separated string. Type them with 'CV Text To Array' (one per line), or wire an array that already exists: a QR/barcode detector's 'texts', 'CV Load Labels'.labels_array, a classifier's 'labels_out', or any numeric array via 'CV Numbers To Array'. Leave it unwired to caption each point with its INDEX, which is also what a point whose position is NaN keeps - blanked points are skipped without renumbering the rest. | |
| offset_xopt | INT | 4-4096–4096 | Horizontal pixel offset of the text from its point. |
| offset_yopt | INT | -4-4096–4096 | Vertical pixel offset of the text from its point. |
| outlineopt | BOOLEAN | true | Draw a contrasting halo behind each label (black behind a light 'color', white behind a dark one). These labels land on whatever the image happens to show, so a single flat colour at font_scale 0.4 is often unreadable without it. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | COMFY_MATCHTYPE_V3 | Same format as the image input. |
| count | INT | How many labels were drawn. |