cv2.face.drawFacemarks
Plot landmark points on a face, but you bring the points
- image
- points
- nparray
The drawing half of OpenCV's Facemark API. You give it an image and a set of landmark points, it hands you the image back with those points marked on it. That's the whole node, and the interesting part is where the points come from - because this pack cannot produce them for you.
The Facemark gap
OpenCV's face module has a landmark model (createFacemarkLBF) and a set of utilities. This pack's registry is generated from top-level functions only, so class-based entry points like createFacemarkLBF are simply not wrapped - the pack's own README calls that out as a known limitation, and adds that a curated node can bridge such cases whereas here none does. So there is no "detect 68 landmarks" node in this family. What you get is the plumbing: load points from a file with cv2.face.loadFacePoints, detect faces with cv2.face.getFacesHAAR, draw them with this node, and build transforms from them with cv2.estimateAffinePartial2D.
For real landmark detection in 2026 you'd reach outside this corner - MediaPipe for a license-clean, dependency-light option, or the 5-point landmarks that come free with this pack's own CV YuNet Face Detect if 5 points are enough for your alignment. The community consensus on face detection generally, and why segmentation detectors beat boxes for anything that gets pasted back, is laid out in masking-detection-detailing.md.
Inputs and outputs
image accepts an IMAGE, MASK or NPARRAY - the frame it draws on. points is NPARRAY only and is the landmark matrix, one row per point, N x 2. That's the input that stops people: you can't type coordinates into a widget, so the points come from cv2.face.loadFacePoints, from CV Points if you're building a set by hand, or from any node in this pack that emits a point array.
color is optional and takes the pack's Scalar literal - "(0, 255, 0)" in BGR, or a bare number to broadcast. The default is blank, meaning OpenCV's own default colour, so if the marking colour matters to you (green on a face, something visible on a dark crop), say so explicitly.
The single output is an NPARRAY, the annotated frame. Expect a bridge: CV Array → Image to see it, or to carry on into the rest of an IMAGE pipeline. That format asymmetry is the thing to internalise - an IMAGE goes in, a raw array comes out, because the pack's format-echo list covers filters and drawing primitives, and these contrib face helpers aren't on it.
When to use it versus CV Draw Points
Honestly: if you already have points and an image and just want dots on a picture, this pack's curated CV Draw Points is the friendlier node - it's designed for that job, it's typed against the pack's own conventions, and it doesn't force the format round trip. Reach for cv2.face.drawFacemarks when you specifically want OpenCV's own Facemark visualisation, e.g. reproducing the look of the upstream samples, or when you're debugging a landmark file and want to see it rendered the way the library intends. It's a faithful wrapper, not a convenience node, and the difference shows in the wiring.
The other legitimate use is verification: load a .pts file, draw it, look at it. If the dots land on the eyes and mouth, your file is fine and your coordinate frame is right. If they're offset, you've found a scale or crop problem before it silently poisoned a transform fit.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
ComfyUI Manager: search comfyui_cv, install, restart. Python ≥ 3.12 and a recent V3-API ComfyUI. Node path: image/CV/low-level/face.
Contrib is not optional here. These nodes live in OpenCV's contrib face module, and all four OpenCV wheels share a single site-packages/cv2. If a non-contrib opencv-python lands on top of the contrib build, the submodules go empty and this family silently disappears from the menu. tools/repair_opencv_contrib.py --check tells you whether that happened; --apply fixes it. There is no install-time guard, so it's worth knowing the symptom.
Traps
Mismatched shapes are the usual failure - points must be a 2-D array of coordinate rows, not a flat list, and a single-point array renders as a single dot which can look like nothing happened. Coordinate frame mismatches are the sneaky one: if the image was resized or cropped anywhere upstream, landmarks from a file will land somewhere plausible but wrong, so verify on a known image before you trust the overlay. And remember the output is a raw array: handing it to a node expecting an IMAGE without the bridge is the other classic dead end.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE,MASK | - - - 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,IMAGE,MASK | - - - 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. | |
| coloropt | STRING | - - - 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. Leave blank for the OpenCV default. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| nparray | NPARRAY | — |