PIPNet: Pixel-in-Pixel Net for Facial Landmark Detection
May 5, 2026 · View on GitHub
Tip
The models and functionality in this repository are integrated into UniFace — an all-in-one face analysis toolkit.<br>
ONNX Runtime inference for PIPNet facial landmark detection. Uses UniFace for face detection.
| Original | Result |
|---|---|
![]() |
![]() |
![]() |
![]() |
98-point landmarks predicted by pipnet_r18_wflw_98.onnx (face boxes from SCRFD).
Models
PyTorch checkpoints and the ONNX exports built from them. Download via bash download.sh or from Releases.
| Model | Dataset | Landmarks | Input | Backbone | PyTorch | ONNX |
|---|---|---|---|---|---|---|
| PIPNet R18 WFLW-98 | WFLW (supervised) | 98 | 256×256 | ResNet-18 | pth | onnx |
| PIPNet R18 300W+CelebA-68 (GSSL) | 300W+CelebA (semi-supervised) | 68 | 256×256 | ResNet-18 | pth | onnx |
Both variants share the same architecture and produce 5 output tensors
(cls_map, offset_x, offset_y, nb_x, nb_y). The PIPNet class
auto-selects the 68-point or 98-point mean-face table from the ONNX output
channel count.
Installation
pip install -r requirements.txt
bash download.sh # Download model weights
Usage
CLI
python main.py assets/samples/face.jpg --weights weights/pipnet_r18_wflw_98.onnx
Python API
import cv2
from model import PIPNet
from uniface.detection import SCRFD
detector = SCRFD()
landmarker = PIPNet('weights/pipnet_r18_wflw_98.onnx')
image = cv2.imread('face.jpg')
faces = detector.detect(image)
landmarks = landmarker.get_landmarks(image, faces[0].bbox) # (98, 2) float32
get_landmarks(image, bbox) returns landmarks in original-image pixel
coordinates. The class handles the asymmetric 1.2× bbox crop, ImageNet
normalization, and the pixel-in-pixel neighbor-averaging decoding step
internally.
Exporting Your Own Weights
onnx_export.py converts a .pth checkpoint to ONNX. Install the extra deps first:
pip install -r requirements-export.txt
Then run against one of the .pth files from Models (or any compatible PIPNet checkpoint):
# WFLW (supervised, 98 points):
python onnx_export.py \
--weights weights/pipnet_r18_wflw_98.pth \
--num-lms 98 \
--output weights/pipnet_r18_wflw_98.onnx
# 300W+CelebA (GSSL, 68 points):
python onnx_export.py \
--weights weights/pipnet_r18_300w_celeba_68.pth \
--num-lms 68 \
--output weights/pipnet_r18_300w_celeba_68.onnx



