Landmark Facial Aligner
Put every face in the same canonical frame before you run ControlNet
- image
- image_aligned
- inverse_matrix_json
Face detailers and ControlNet have a dirty secret: they work best when the face is in a consistent place and orientation, because that's what the models saw in training. A face tilted 20°, off to the side, or at the wrong scale is a face fighting the model. Professional pipelines solve this by aligning first - rotating, scaling, and translating the face into a canonical frame - and JHPixelProFacialAligner is the pack's version of that pre-processing step.
It takes your image plus 5 landmarks (left eye, right eye, nose, left mouth, right mouth) and applies a similarity transform - rotation + uniform scale + translation, no shear - to map the face onto a canonical, FFHQ-like frame: eyes at Y=0.40, nose at Y=0.55, mouth at Y=0.70, face horizontally centered, all pulled in by the padding setting. Every face ends up with the same eye/nose/mouth geometry, which is exactly what a stable batch or a ControlNet pass wants.
Inputs
landmarks: 5-point JSON[L-eye, R-eye, nose, L-mouth, R-mouth], pixel-absolute or normalized (values ≤ 1.5 are auto-treated as normalized). You can paste these by hand, but the point is automation - wireJHPixelProFaceDetect'slandmarks_json[0]in and it feeds itself.target_size(512 / 768 / 1024): square output. 1024 is SDXL-friendly and the default; smaller is faster.padding(0–0.5, default 0.2): room reserved around the face. 0 = tight crop, 0.5 = half-frame padding. If your alignment crops the chin or the hair, raise this.
Outputs: image_aligned at target_size, and - this is the clever part - inverse_matrix_json, a serialized inverse affine that lets you unwarp the result back onto the original canvas. That's the round-trip that makes aligned-face editing useful: align → run ControlNet/KSampler/inpaint on the canonical crop → unwrap the edit back onto the original composition with the pack's JHPixelProUnwrapFace node. The face is edited at a model-friendly scale and framing, and the rest of the scene never moves.
The honest caveats
The node does not sanity-check face geometry - wrong landmarks in, wrong alignment out, and it will happily warp on garbage. Feed it from JHPixelProFaceDetect (or any upstream detector) in production; hand-pasting is the stop-gap. And the align→unwrap round-trip goes through two bilinear resamples, which softens things by ~34/255 in uint8. Fine for a retouch chain, not near-lossless - don't stack multiple round-trips. The README also notes the core ships a fallback landmark set so the pack loads even without mediapipe installed.
Install
ComfyUI Manager → search ComfyUI-JH-PixelPro, or:
cd ComfyUI/custom_nodes
git clone https://github.com/jetthuangai/ComfyUI-JH-PixelPro.git
cd ComfyUI-JH-PixelPro
pip install -r requirements.txt
Restart ComfyUI; it's under ComfyUI-JH-PixelPro/geometry. The pack needs ComfyUI ≥ 0.43.x, Python ≥ 3.10, plus kornia, mediapipe, opencv-python-headless, and scipy.
If you're already running FaceDetailer-style passes, this node is the upgrade path: align first, and your detailer stops compensating for geometry and starts doing its actual job. It's one of those pieces that feels like extra work until you see a batch of 50 portraits all come out of the aligner staring straight ahead at the same coordinates - then it feels like cheating.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| landmarks | STRING | [[820, 650], [1420, 640], [1120, 900], [930, 1150], [1310, 1145]] | 5-point landmark JSON in order [L-eye, R-eye, nose, L-mouth, R-mouth]. Pixel-absolute or normalized (auto-detected: values ≤ 1.5 treated as normalized). Shape 5x2 for single image or Bx5x2 for batch. |
| target_size | INT | 1024512–1024 | Square output size in pixels (512 / 768 / 1024). Smaller is faster; 1024 is SDXL-friendly. |
| padding | FLOAT | 0.200–0.5 | Ratio of canonical frame reserved around the face (hair/chin room). 0 = tight crop, 0.5 = half-frame padding. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image_aligned | IMAGE | — |
| inverse_matrix_json | STRING | — |