ComfyUI Extension: Head-Orientation-Node - by PabloGFX
Run ComfyUI workflows without the setup
No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.
A custom node for ComfyUI that analyzes and sorts images based on head orientation using MediaPipe. It detects facial landmarks, calculates head pose, and intelligently sorts images for enhanced AI image processing workflows.
README
Head Orientation Node for ComfyUI
Version 1.4.0 — by PabloGFX
A custom ComfyUI node that detects faces, estimates head pose (pitch / yaw / roll), sorts a batch of images by similarity to a query / reference batch, and emits a configurable, well-formatted string describing the orientations.
What's new in 1.4.0
Multi-scale face detection for large images
MediaPipe's BlazeFace detector works on small internal tiles (~128 px) and silently fails on very large source images (e.g. 4K phone photos, 4080 × 2336 references) — even at the minimum confidence threshold — because the face ends up covering too few pixels after the model's internal preprocessing.
v1.4.0 adds an automatic downscale step that runs detection at a manageable size first and falls back through a small list of preset sizes when needed:
- New
detection_max_sideparameter (INT, default1280, range256–8192, step64). The image is downscaled so its longest side is at most this value before face detection runs. The finalsorted_imagesoutput is always at the ORIGINAL resolution — only detection happens on the downscaled copy. - If detection fails at the chosen size, the node automatically retries at
1600 → 960 → 640 → 480and logs which size finally succeeded. - Landmarks come back in normalized [0, 1] coordinates, so the PnP head-pose math uses the ORIGINAL image dimensions and is unaffected by the downscale.
In the user-reported case (1 query at 4608 × 3712 + 2 references at
2336 × 4080) detection now succeeds where it previously returned [0,0,0] at
min_detection_confidence=0.05.
What's new in 1.3.0
New sort_source parameter — fixes the "query + candidate pool" use case
In v1.0.x – v1.2.0 the node always sorted the image input batch using
reference_images as the target poses, and put the sorted inputs into
sorted_images. That semantic confused users who had one main image and a
pool of candidate references they wanted ranked — the most common case in
ComfyUI workflows.
v1.3.0 adds sort_source with two modes:
sort_source = "references"(new default) — treatsimageas the query / target pose andreference_imagesas the candidate pool.sorted_images= candidate references reordered by similarity to the query. Use this when you have one main image and want the most-similar candidates from a pool.sort_source = "inputs"(legacy v1.x semantic) — sorts theimagebatch by similarity toreference_images.sorted_images= reordered inputs.
In the user-reported bug case (1 image + 2 references), v1.3.0 with default
settings now returns 2 sorted references in sorted_images and 2 matching
lines in data — instead of 1 input + N duplicate data lines.
Lower default detection confidence + new parameter
min_detection_confidence is now exposed (0.05–1.0, default 0.3,
previously hard-coded to 0.5). Lower values are more permissive and detect
faces in dim portraits, faces under hats, low-contrast frames etc. The
detector is rebuilt automatically when the value changes.
data_content default changed to outputs
With the new sort_source default, outputs is the most useful default —
each row in data describes the corresponding sorted output image (aligned
with what you see in sorted_images). Other values:
references— orientations of everyreference_imagesitem in input order.inputs— orientations of everyimageitem in input order.paired— sorted outputs paired with the closest match from the OPPOSITE batch.
Per-row distance is now distance to the OPPOSITE batch
include_distance = true always reports the angular distance from this row's
item to the closest item in the opposite batch (works for outputs,
inputs, references, paired). Previously distance was always
"to references"; now it follows the data content.
What's new in 1.2.0
Bug fix
- The
dataoutput no longer repeats the same row when the input batch is smaller than the reference batch. In v1.1.0 (and earlier),datawould emit duplicate lines because it reflected the sorted output image's orientation — and when the legacy sorter ran out of unique inputs it just repeated the last one. The new defaultdata_content = "references"emits one line per reference image instead, so every line carries distinct, meaningful information.
New sort modes
similarity_to_refs(new default) — sort all input images by similarity to the reference batch, closest first. Output count = input count.reverse_similarity— same metric, farthest first.match_references— legacy v1.1.0 / v1.0.x behaviour: greedy 1-to-1 match; output count = reference count; the last assigned input is repeated to fill.as_is— pass inputs through in their original order (still computes orientations).
A similarity_metric parameter chooses how each input is scored against the
reference batch:
min_to_any_ref(default) — distance to the nearest reference.mean_to_refs— average distance to all references.first_ref_only— distance to the first reference only.
New output_count override
output_count = 0(default) uses the mode's natural size (input count for similarity / as_is, reference count for match).- Set any positive integer to force a fixed batch size. Excess items are truncated; missing items are filled by repeating the last.
New configurable data string
| Parameter | Default | Effect |
| ------------------- | ------------- | ---------------------------------------------------------------------------- |
| data_content | references | What rows the string contains: references, outputs, inputs, paired |
| data_format | compact | compact, labeled, csv, json, verbose |
| decimals | 2 | Decimal places for angles and distance |
| angle_unit | degrees | degrees or radians |
| include_index | false | Prepend the 1-based row index to each line |
| include_distance | false | Append the angular distance to the closest reference (outputs / inputs / paired) |
| include_header | false | csv only — prepend a header row with column names |
Data content modes
references(default) — one line per reference image. Use this when you want to see what target poses you supplied.outputs— one line per sorted output image.inputs— one line per ORIGINAL input image (before sorting).paired— one line per sorted output, paired with its closest reference and distance. Best when you want a side-by-side comparison.
Data format examples
compact (default, same shape as v1.1.0):
[pitch,yaw,roll]
[pitch,yaw,roll]
labeled:
pitch=-0.72 yaw=-2.11 roll=-0.13
pitch=-0.81 yaw=1.72 roll=0.12
csv with header + index + distance + paired:
index,pitch,yaw,roll,ref_pitch,ref_yaw,ref_roll,distance
1,-0.72,-2.11,-0.13,-0.72,-2.11,-0.13,0.00
2,-0.81,1.72,0.12,-0.81,1.72,0.12,0.00
json:
[
{
"label": "output",
"pitch": -0.72,
"yaw": -2.11,
"roll": -0.13,
"reference": { "pitch": -0.72, "yaw": -2.11, "roll": -0.13 },
"distance": 0.0
}
]
verbose:
Output 1:
Orientation: pitch=-0.72, yaw=-2.11, roll=-0.13
Reference: pitch=-0.72, yaw=-2.11, roll=-0.13
Distance: 0.00
What's new in 1.1.0
- Compatibility with modern MediaPipe (>=0.10). The previous code relied on
mediapipe.solutions.face_mesh, which is missing from the slimmed-down mediapipe builds shipped with recent Python embeds (e.g. the ComfyUI Windows portable). The node now uses the newmediapipe.tasks.python.vision.FaceLandmarkerAPI and falls back to the legacysolutions.face_meshAPI if it is available. - Automatic model download of the official Google-hosted
face_landmarker.task(~3.7 MB) intomodels/. - Correct RGB handling (the legacy code unintentionally swapped R and B
via an extra
cv2.cvtColorcall), graceful handling of grayscale / RGBA inputs, and safer batch sorting when no input images are provided.
Installation
- Clone this repository into your ComfyUI
custom_nodesdirectory:git clone https://github.com/lazniak/Head-Orientation-Node-for-ComfyUI---by-PabloGFX.git - Install the required dependencies (use the Python that runs ComfyUI — for
the Windows portable that is
python_embeded\python.exe):pip install -r requirements.txt - Restart ComfyUI. The first run will download
models/face_landmarker.taskautomatically.
Requirements
- numpy >= 1.19.3
- opencv-python >= 4.5.5.64
- mediapipe >= 0.10.0
- Pillow >= 8.3.1
- torch >= 1.9.0
Usage
- In ComfyUI, locate the node in the browser as Head Orientation Node - by
PabloGFX (category
image/PabloGFX). - Connect a batch of images to
image. - Connect a reference batch to
reference_images. - (Optional) Tweak
sort_mode,data_content,data_format, etc. - The node outputs the input batch sorted/matched and the per-row data string.
How it works
- Each image is converted to an RGB
uint8array and run through MediaPipe face landmark detection (Tasks API by default; legacysolutionsAPI as a fallback). - Six canonical landmarks (eyes, nose, mouth corners, chin) are fed into
OpenCV's
solvePnPtogether with a simple pinhole camera model to recover the head rotation, then decomposed into pitch / yaw / roll. - Inputs are sorted (or matched 1-to-1) against the references using the selected mode and metric.
- The data string is built according to
data_content,data_format, and the formatting flags.
Notes about the MediaPipe model
The first run downloads the official Google-hosted face landmarker model from:
https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task
It is cached at head-orientation-node/models/face_landmarker.task. If your
ComfyUI installation cannot reach the internet, drop the file there manually
before using the node.
License
This project is licensed under the Apache License 2.0. This license is compatible with the licenses of the major dependencies used in this project:
- MediaPipe: Apache License 2.0
- OpenCV: Apache License 2.0
- NumPy: BSD 3-Clause License
- PyTorch: BSD 3-Clause License
- Pillow: HPND License
The Apache License 2.0 allows you to use, modify, distribute, and sublicense
the code, while also providing an express grant of patent rights from
contributors to users. It requires preservation of copyright and license
notices. For the full license text see the LICENSE file or
https://www.apache.org/licenses/LICENSE-2.0.
Contributing
Contributions are welcome. By contributing to this project you agree to license your contributions under the Apache License 2.0.
Acknowledgements
Thanks to the developers of MediaPipe, OpenCV, NumPy, PyTorch and Pillow.
Run ComfyUI workflows without the setup
No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.