Nodes/ComfyUI-JH-PixelPro/Landmark Facial Aligner
ComfyUI Node

Landmark Facial Aligner

Put every face in the same canonical frame before you run ControlNet

By jetthuangai·Created 5 months ago·Updated 4 months ago· 5
Landmark Facial Aligner
  • image
  • image_aligned
  • inverse_matrix_json
landmarks[[820, 650], [1420, 640], [1120, 900], [930, 1150], [1310, 1145]]
target_size1024
padding0.20

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 - wire JHPixelProFaceDetect's landmarks_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.

CategoryComfyUI-JH-PixelPro/geometry

Inputs (4)

NameTypeDefaultDescription
imageIMAGE
landmarksSTRING[[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_sizeINT1024512–1024Square output size in pixels (512 / 768 / 1024). Smaller is faster; 1024 is SDXL-friendly.
paddingFLOAT0.200–0.5Ratio of canonical frame reserved around the face (hair/chin room). 0 = tight crop, 0.5 = half-frame padding.

Outputs (2)

NameTypeDescription
image_alignedIMAGE
inverse_matrix_jsonSTRING