Head Orientation Node - by PabloGFX
The node that tells you which way the face is looking
- image
- reference_images
- sorted_images
- data
You've generated forty variations and need the one where the subject is actually facing the camera. Or you want a swapped face to sit at the same head angle as the reference photo so the re-light doesn't fight you. That's the exact gap this node fills: it reads each face, converts it to pitch/yaw/roll, and hands you both the numbers and a batch of images sorted by how close they are to a pose you care about.
It's a small, focused pack - one node, no API, no key, no model you have to hunt down. PabloGFX built it for head-pose-aware workflows, and the README reads like a changelog of real user pain, which is a good sign.
How it works
Under the hood it's MediaPipe + OpenCV, not a diffusion model. Each image goes through Google's FaceLandmarker (the modern mediapipe.tasks.python.vision API), which detects the face and returns 478 landmarks. Six canonical points - eyes, nose, mouth corners, chin - get fed into OpenCV's solvePnP with a simple pinhole camera model, and the resulting rotation is decomposed into pitch (nodding), yaw (turning left/right), and roll (tilting).
A few nice touches in the current version. The node auto-downloads face_landmarker.task (~3.7 MB) from Google's storage on first run, so there's nothing to fetch by hand unless your machine is offline. And v1.4.0 added multi-scale detection: MediaPipe's BlazeFace works on tiny internal tiles and silently fails on 4K phone photos, so the node downscales to detection_max_side (default 1280), and if detection fails it retries at 1600 → 960 → 640 → 480 automatically. Your output images stay at original resolution - only the detection runs on the downscaled copy.
The inputs that matter
image and reference_images are both required IMAGE tensors, so you always feed two batches. Everything else is optional, and most of it you can leave alone:
sort_source- the one that confused people for two versions.references(default) treatsimageas your query/target pose andreference_imagesas the candidate pool:sorted_imagesis the pool reordered by similarity.inputsis the legacy v1.x meaning, sortingimageby similarity to the references. If you have one main image and a stack of candidates to rank, you want the default.min_detection_confidence(default 0.3) - lower it toward 0.05 for dim portraits, faces under caps, or heavily cropped heads. The detector rebuilds when you change it.detection_max_side(default 1280) - the fix for giant source images that return[0,0,0]orientations.data_content/data_format- what thedatastring reports and how it's shaped.data_content = "outputs"matchessorted_imagesrow-for-row;data_formatcan becompact,labeled,csv,json, orverbose, withinclude_distanceappending the angular gap to the closest opposite-batch item.
sort_mode (similarity_to_refs, reverse_similarity, match_references, as_is), similarity_metric, and output_count fine-tune the ranking - nice to have, not required reading.
Outputs
sorted_images(IMAGE) - the batch, reordered persort_source+sort_mode.data(STRING) - per-row pitch/yaw/roll text. Feed it to a text display or parse it for a condition.
Installing
Through ComfyUI Manager, search "Head-Orientation-Node - by PabloGFX" and hit install. Or the manual route:
cd ComfyUI/custom_nodes
git clone https://github.com/lazniak/Head-Orientation-Node-for-ComfyUI---by-PabloGFX.git
cd Head-Orientation-Node-for-ComfyUI---by-PabloGFX
pip install -r requirements.txt
Restart ComfyUI. It needs numpy, opencv-python, Pillow, torch, and mediapipe>=0.10.0 - that last one is the real dependency, because older slimmed-down MediaPipe builds (the old Windows portable shipped one) were missing the legacy face mesh API. v1.1.0 fixed that by using the Tasks API, so keep MediaPipe current.
Where people get burned
[0,0,0]for every face on big images - that's the BlazeFace large-image failure; raise nothing, just confirm you're on 1.4.0, or nudgedetection_max_sidedown.- No faces detected at all - drop
min_detection_confidence, then check that the model file actually downloaded tomodels/face_landmarker.task. Offline machine? Grab that file manually and drop it in. - Wrong-batch sorting - if your outputs don't match what you expected, check
sort_sourcefirst; the semantics changed at v1.3.0 and old workflows carried over the legacy meaning.
It's a niche tool, but when "which of these 40 faces is looking at me" is your question, it answers in one run.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| reference_images | IMAGE | — | |
| sort_sourceopt | COMBO | references | Which batch is placed in `sorted_images`. references (default): treat `image` as the query / target pose and `reference_images` as the candidate pool. sorted_images = candidates reordered by similarity to the query. Use this when you have one main image and want the most similar candidates from a pool. inputs (legacy v1.x semantic): sort the `image` batch by similarity to `reference_images`. sorted_images = reordered inputs. |
| sort_modeopt | COMBO | similarity_to_refs | How the pool is ordered. similarity_to_refs (default): sort by similarity to the target batch, closest first. reverse_similarity: same metric, farthest first. match_references: greedy 1-to-1 match; output count = target count. as_is: pass the pool through in its original order. |
| similarity_metricopt | COMBO | min_to_any_ref | Distance metric used for similarity sorting. min_to_any_ref: distance to the nearest target. mean_to_refs: average distance to all targets. first_ref_only: distance to the first target only. |
| output_countopt | INT | 00–4096 | Force the number of output images. 0 = auto: pool count for similarity/reverse/as_is modes, target count for match_references. Excess items are truncated; missing items repeat the last. |
| data_contentopt | COMBO | outputs | What goes into the `data` string. outputs (default): one line per sorted output image, in sorted order. references: one line per `reference_images` item, in input order. inputs: one line per `image` item, in input order. paired: one line per sorted output with the closest match from the opposite batch. |
| data_formatopt | COMBO | compact | compact (default, legacy v1.1.0): [pitch,yaw,roll] per line. labeled: 'pitch=X yaw=Y roll=Z' per line. csv: comma-separated values (optional header). json: JSON array of objects. verbose: multi-line human-readable per item. |
| decimalsopt | INT | 20–6 | Decimal places for angles and distance. |
| angle_unitopt | COMBO | degrees | Output unit for angles and distance. |
| min_detection_confidenceopt | FLOAT | 0.300.05–1 | MediaPipe face detection confidence threshold. Lower = more permissive (catches dim / cropped / cluttered faces, may produce false positives). Default 0.3 is more forgiving than the original 0.5 and works better for dark portraits, faces in caps, etc. |
| detection_max_sideopt | INT | 1280256–8192 | Longest side (px) the image is downscaled to BEFORE running MediaPipe face detection. Default 1280. MediaPipe's BlazeFace works on ~128 px internal tiles and often misses faces in very large images (e.g. 4K phone photos). On failure the node also tries other preset sizes (1600 / 960 / 640 / 480) automatically. The final sorted_images output is always at the ORIGINAL resolution. |
| include_indexopt | BOOLEAN | false | Prepend the 1-based row index to each line. |
| include_distanceopt | BOOLEAN | false | Append the angular distance from this row's orientation to the closest item in the OPPOSITE batch. |
| include_headeropt | BOOLEAN | false | csv format only: prepend a header row with column names. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| sorted_images | IMAGE | — |
| data | STRING | — |