# How to Perform Inference Using the PFLD Model for Facial Landmark Detection

> Learn to perform PFLD model inference for facial landmark detection. Feed pre-processed images into the model and extract landmark vectors for accurate results.

- Repository: [Guoqiang QI/pfld](https://github.com/guoqiangqi/pfld)
- Tags: how-to-guide
- Published: 2026-03-02

---

**To perform inference with the PFLD model, restore the TensorFlow 1.x checkpoint, feed a pre-processed 112×112 RGB image into the `image_batch` placeholder with `phase_train` set to False, and extract the 196-dimensional landmark vector from the `landmark_L1` tensor, reshaping it into 98 (x, y) pairs scaled to the original image dimensions.**

The `guoqiangqi/pfld` repository implements a **TensorFlow 1.x** version of the Progressive Face Localization and Detection (PFLD) network. This architecture predicts **98 facial landmarks** (196 coordinate values) from a single RGB face image, utilizing a MobileNet-V2-style backbone for efficient feature extraction.

## Understanding the PFLD Inference Pipeline

Performing inference requires three distinct operations: restoring the serialized computation graph from checkpoint files, locating the specific input and output tensors by name, and executing a forward pass with properly normalized image data. The model expects fixed-size inputs and produces normalized landmark coordinates that must be mapped back to the original image resolution.

## Step-by-Step Implementation

### Restore the Trained TensorFlow Graph

The pretrained model is stored as a collection of checkpoint files (`model.meta`, `model.ckpt-*`). In [`test_model.py`](https://github.com/guoqiangqi/pfld/blob/main/test_model.py) (lines 28‑31), the graph is restored using `tf.train.import_meta_graph` followed by `saver.restore`:

```python
import tensorflow as tf

# Reset default graph to avoid conflicts

tf.reset_default_graph()

# Import and restore

saver = tf.train.import_meta_graph(meta_file)
saver.restore(tf.get_default_session(), ckpt_file)

```

### Locate Input and Output Tensors

After restoration, you must extract tensor references by their scoped names. The graph contains an image batch placeholder (`image_batch:0`), a phase flag (`phase_train:0`), and five intermediate landmark tensors (`landmark_L1:0` through `landmark_L5:0`). As shown in [`test_model.py`](https://github.com/guoqiangqi/pfld/blob/main/test_model.py) (lines 33‑41), the code typically uses `landmark_L1` for final predictions:

```python
graph = tf.get_default_graph()

# Input tensors

images_placeholder = graph.get_tensor_by_name('image_batch:0')
phase_train_placeholder = graph.get_tensor_by_name('phase_train:0')

# Output tensor (landmark_L1 contains the 196-dim vector)

landmark_L1 = graph.get_tensor_by_name('landmark_L1:0')

```

### Pre-process the Input Image

The model expects a 112×112 RGB image with pixel values scaled to `[0, 1]`. Using OpenCV, convert the BGR image to RGB, resize it, and normalize by dividing by 256.0:

```python
import cv2
import numpy as np

# Load image

image = cv2.imread('face_image.jpg')
h, w, _ = image.shape

# Pre-processing pipeline

input_img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
input_img = cv2.resize(input_img, (112, 112)).astype(np.float32) / 256.0
input_img = np.expand_dims(input_img, 0)  # Add batch dimension

```

### Execute Inference and Decode Results

Feed the pre-processed tensor into the graph with `phase_train=False` to disable dropout and batch normalization updates. The raw output is a flat vector of 196 normalized values (range 0‑1). Reshape this into 98 (x, y) pairs and scale by the original image dimensions to obtain pixel coordinates:

```python

# Run inference

feed_dict = {
    images_placeholder: input_img,
    phase_train_placeholder: False
}
landmarks_norm = sess.run(landmark_L1, feed_dict=feed_dict)

# Convert to pixel coordinates

landmarks = landmarks_norm.reshape(-1, 2) * np.array([h, w])

```

## Complete Inference Script

Below is a consolidated, runnable script that loads a pretrained PFLD checkpoint and visualizes the detected landmarks on an input image:

```python
import os
import cv2
import numpy as np
import tensorflow as tf

# Configuration

meta_file = './models2/model0/model.meta'
ckpt_file = './models2/model0/model.ckpt-0'
image_path = 'data/test_data/example.jpg'

def main():
    # Initialize session

    tf.reset_default_graph()
    with tf.Graph().as_default():
        with tf.Session() as sess:
            # Restore model

            saver = tf.train.import_meta_graph(meta_file)
            saver.restore(sess, ckpt_file)
            
            # Get tensors

            graph = tf.get_default_graph()
            img_ph = graph.get_tensor_by_name('image_batch:0')
            phase_ph = graph.get_tensor_by_name('phase_train:0')
            landmark_tensor = graph.get_tensor_by_name('landmark_L1:0')
            
            # Load and preprocess image

            img = cv2.imread(image_path)
            if img is None:
                raise ValueError(f"Could not load image: {image_path}")
            h, w, _ = img.shape
            
            input_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            input_img = cv2.resize(input_img, (112, 112)).astype(np.float32) / 256.0
            input_img = np.expand_dims(input_img, 0)
            
            # Inference

            feed = {img_ph: input_img, phase_ph: False}
            landmarks = sess.run(landmark_tensor, feed_dict=feed)
            
            # Post-process

            points = landmarks.reshape(-1, 2) * np.array([h, w])
            
            # Visualize

            for x, y in points.astype(np.int32):
                cv2.circle(img, (x, y), 2, (0, 0, 255), -1)
            
            cv2.imshow('PFLD Landmarks', img)
            cv2.waitKey(0)
            cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

```

## Model Architecture and Key Source Files

Understanding the underlying implementation helps debug inference issues. The PFLD model in this repository uses a **MobileNet-V2-style backbone** for feature extraction, followed by multi-scale feature aggregation.

**[`model2.py`](https://github.com/guoqiangqi/pfld/blob/main/model2.py)** contains the core architecture definition. The `pfld_inference()` function constructs the network and outputs a **196-dimensional vector** representing 98 (x, y) landmark coordinates:

```python
def pfld_inference(input, weight_decay, batch_norm_params):
    # MobileNet-V2 backbone construction...

    multi_scale = tf.concat([s1, s2, s3], 1)
    landmarks = slim.fully_connected(multi_scale, num_outputs=196,
                                    activation_fn=None, scope='fc')
    return features, landmarks

```

**[`test_model.py`](https://github.com/guoqiangqi/pfld/blob/main/test_model.py)** provides the reference implementation for inference, demonstrating how to restore checkpoints and feed data into the graph.

**[`utils.py`](https://github.com/guoqiangqi/pfld/blob/main/utils.py)** contains visualization helpers such as `LandmarkImage` for generating heatmaps, though these are optional for basic inference.

## Summary

- **PFLD inference** requires restoring a TensorFlow 1.x checkpoint and accessing specific tensor names (`image_batch:0`, `phase_train:0`, `landmark_L1:0`).
- **Input preprocessing** involves resizing images to 112×112 pixels, converting BGR to RGB, and scaling values to `[0, 1]` by dividing by 256.0.
- **Output decoding** requires reshaping the 196-dimensional vector into 98 (x, y) pairs and multiplying by the original image height and width to obtain pixel coordinates.
- The underlying architecture in [`model2.py`](https://github.com/guoqiangqi/pfld/blob/main/model2.py) uses a MobileNet-V2 backbone with multi-scale feature concatenation to produce the final landmark predictions.

## Frequently Asked Questions

### What input image size does the PFLD model require?

The model expects a fixed input size of **112×112 pixels**. During inference, you must resize your input image to these dimensions before normalizing pixel values. The repository uses OpenCV's `resize` function with bilinear interpolation, followed by scaling the uint8 values to the range `[0, 1]` by dividing by 256.0.

### How do I convert normalized landmarks to pixel coordinates?

The model outputs a flat vector of 196 normalized values (range 0–1). First, reshape this array to `(-1, 2)` to create 98 (x, y) pairs. Then multiply the x-coordinates by the original image width and the y-coordinates by the original image height. This scaling maps the normalized predictions back to the original pixel space for visualization or further processing.

### Which tensor contains the final landmark predictions?

During inference, retrieve the tensor named **`landmark_L1:0`** from the restored graph. While the graph contains five intermediate landmark tensors (`landmark_L1` through `landmark_L5`) representing different training stages, the first tensor (`landmark_L1`) contains the final 196-dimensional prediction vector suitable for deployment.

### Can I use PFLD with TensorFlow 2.x?

The `guoqiangqi/pfld` repository is implemented for **TensorFlow 1.x**. To run inference in TensorFlow 2.x environments, you must enable compatibility mode using `tf.compat.v1` and disable eager execution with `tf.compat.v1.disable_eager_execution()`. Alternatively, convert the checkpoint to TensorFlow 2.x format or use the TensorFlow 1.x runtime in a separate environment.