Nodes/Head-Orientation-Node - by PabloGFX/Head Orientation Node - by PabloGFX
ComfyUI Node

Head Orientation Node - by PabloGFX

The node that tells you which way the face is looking

By lazniak·Created 2 years ago·Updated 3 months ago· 11
Head Orientation Node - by PabloGFX
  • image
  • reference_images
  • sorted_images
  • data
sort_sourcereferences
sort_modesimilarity_to_refs
similarity_metricmin_to_any_ref
output_count0
data_contentoutputs
data_formatcompact
decimals2
angle_unitdegrees
min_detection_confidence0.30
detection_max_side1280
include_indexfalse
include_distancefalse
include_headerfalse

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) treats image as your query/target pose and reference_images as the candidate pool: sorted_images is the pool reordered by similarity. inputs is the legacy v1.x meaning, sorting image by 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 the data string reports and how it's shaped. data_content = "outputs" matches sorted_images row-for-row; data_format can be compact, labeled, csv, json, or verbose, with include_distance appending 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 per sort_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 nudge detection_max_side down.
  • No faces detected at all - drop min_detection_confidence, then check that the model file actually downloaded to models/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_source first; 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.

Categoryimage/PabloGFX

Inputs (15)

NameTypeDefaultDescription
imageIMAGE
reference_imagesIMAGE
sort_sourceoptCOMBOreferencesWhich 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_modeoptCOMBOsimilarity_to_refsHow 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_metricoptCOMBOmin_to_any_refDistance 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_countoptINT00–4096Force 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_contentoptCOMBOoutputsWhat 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_formatoptCOMBOcompactcompact (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.
decimalsoptINT20–6Decimal places for angles and distance.
angle_unitoptCOMBOdegreesOutput unit for angles and distance.
min_detection_confidenceoptFLOAT0.300.05–1MediaPipe 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_sideoptINT1280256–8192Longest 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_indexoptBOOLEANfalsePrepend the 1-based row index to each line.
include_distanceoptBOOLEANfalseAppend the angular distance from this row's orientation to the closest item in the OPPOSITE batch.
include_headeroptBOOLEANfalsecsv format only: prepend a header row with column names.

Outputs (2)

NameTypeDescription
sorted_imagesIMAGE
dataSTRING