How to Export the PFLD Model for Mobile Deployment
To export the PFLD model for mobile deployment, restore a TensorFlow 1.x checkpoint saved by train_model.py, freeze the graph using graph_util.convert_variables_to_constants, and convert the frozen protobuf to TensorFlow Lite format using tf.lite.TFLiteConverter.
The PFLD (Pose-aware Face Landmark Detection) repository by guoqiangqi provides a TensorFlow 1.x implementation for facial landmark detection. While the training pipeline saves standard checkpoints, mobile deployment requires a frozen graph converted to .tflite format that can be executed by the lightweight TensorFlow Lite interpreter on Android or iOS devices.
Understanding the Checkpoint Structure
During training, train_model.py persists model parameters using tf.train.Saver. According to the source code at lines 123-162, the saver stores trainable variables without writing meta graphs at every epoch:
# train_model.py (excerpt)
save_params = tf.trainable_variables()
saver = tf.train.Saver(save_params, max_to_keep=None)
saver.save(sess, checkpoint_path, global_step=epoch, write_meta_graph=False)
A checkpoint directory contains three file types: model.ckpt-<epoch>.data-00000-of-00001, model.ckpt-<epoch>.index, and model.ckpt-<epoch>.meta. To export for mobile, you must reconstruct the inference graph defined in model2.py, restore these variables, and freeze them into a standalone graph file.
Step 1: Restore the Trained Checkpoint
First, rebuild the inference graph exactly as it was constructed during training. In model2.py (lines 11-92), the create_model function defines the MobilenetV2-based architecture. You must recreate the placeholder structure and call create_model with the same hyper-parameters used during training:
import tensorflow as tf
import argparse
from model2 import create_model
def build_graph():
# Input placeholders must match training shapes
img_ph = tf.placeholder(tf.float32, shape=[None, 112, 112, 3], name='image_batch')
lm_ph = tf.placeholder(tf.float32, shape=[None, 196], name='landmark_batch')
phase_ph = tf.placeholder(tf.bool, name='phase_train')
# Recreate the exact namespace used during training
args = argparse.Namespace(
weight_decay=5e-5,
batch_norm_params={
'decay': 0.995,
'epsilon': 0.001,
'updates_collections': None,
'variables_collections': [tf.GraphKeys.TRAINABLE_VARIABLES],
'is_training': False
})
# Build inference graph (returns heatmap and landmarks)
_, landmarks = create_model(img_ph, lm_ph, phase_ph, args)
return landmarks
# Restore session
landmarks = build_graph()
saver = tf.train.Saver()
Step 2: Freeze the Graph
After restoring the checkpoint in a session, convert all variables to constants using tf.graph_util.convert_variables_to_constants. This produces a single model.pb file containing the graph definition and embedded weights:
from tensorflow.python.framework import graph_util
ckpt_path = 'models1/model_test/model.ckpt-1000' # Adjust to your checkpoint
output_node = 'pfld_inference/fc/MatMul' # Final landmark tensor name
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
saver.restore(sess, ckpt_path)
# Freeze graph: convert variables to constants
frozen_graph_def = graph_util.convert_variables_to_constants(
sess,
sess.graph_def,
[output_node])
# Write frozen graph
with tf.io.gfile.GFile('pfld_frozen.pb', 'wb') as f:
f.write(frozen_graph_def.SerializeToString())
Identifying the output node: The final landmark tensor is created in model2.py at line 90 within the pfld_inference scope. Depending on your graph inspection (TensorBoard or print(landmarks.name)), the output node may be named pfld_inference/fc/MatMul or the preceding reshape operation pfld_inference/conv8/Flatten/Reshape.
Step 3: Convert to TensorFlow Lite
Feed the frozen pfld_frozen.pb file to tf.lite.TFLiteConverter to generate the mobile-ready .tflite file:
import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_frozen_graph(
graph_path='pfld_frozen.pb',
input_arrays=['image_batch'], # Must match placeholder name
output_arrays=['pfld_inference/fc/MatMul']) # Must match output node
# Optional: Enable optimizations for smaller binary size
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
with open('pfld_mobile.tflite', 'wb') as f:
f.write(tflite_model)
Post-training quantization: For reduced latency on mobile CPUs, enable integer-only quantization by providing a representative dataset:
import numpy as np
converter.representative_dataset = lambda: \
iter([np.random.rand(1, 112, 112, 3).astype(np.float32)])
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
Complete Export Script
Combine all three steps into a single utility script that you can execute after training completes:
#!/usr/bin/env python
# export_to_tflite.py
# --------------------------------------------------------------
# 1. Restore checkpoint written by train_model.py
# 2. Freeze the graph (variables → constants)
# 3. Convert to TensorFlow Lite
# --------------------------------------------------------------
import argparse
import tensorflow as tf
from tensorflow.python.framework import graph_util
from model2 import create_model
import numpy as np
def build_graph():
img_ph = tf.placeholder(tf.float32, shape=[None, 112, 112, 3], name='image_batch')
lm_ph = tf.placeholder(tf.float32, shape=[None, 196], name='landmark_batch')
phase_ph = tf.placeholder(tf.bool, name='phase_train')
args = argparse.Namespace(
weight_decay=5e-5,
batch_norm_params={
'decay': 0.995,
'epsilon': 0.001,
'updates_collections': None,
'variables_collections': [tf.GraphKeys.TRAINABLE_VARIABLES],
'is_training': False
})
_, landmarks = create_model(img_ph, lm_ph, phase_ph, args)
return img_ph, landmarks
def freeze_ckpt(ckpt_path, output_node):
_, landmarks = build_graph()
saver = tf.train.Saver()
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
saver.restore(sess, ckpt_path)
frozen_def = graph_util.convert_variables_to_constants(
sess, sess.graph_def, [output_node])
with tf.io.gfile.GFile('pfld_frozen.pb', 'wb') as f:
f.write(frozen_def.SerializeToString())
print('-> Frozen graph written to pfld_frozen.pb')
def convert_tflite(output_node):
converter = tf.lite.TFLiteConverter.from_frozen_graph(
graph_path='pfld_frozen.pb',
input_arrays=['image_batch'],
output_arrays=[output_node])
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
with open('pfld_mobile.tflite', 'wb') as f:
f.write(tflite_model)
print('-> TFLite model written to pfld_mobile.tflite')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--ckpt',
default='models1/model_test/model.ckpt-1000',
help='Path to checkpoint to export')
args = parser.parse_args()
OUTPUT_NODE = 'pfld_inference/fc/MatMul'
freeze_ckpt(args.ckpt, OUTPUT_NODE)
convert_tflite(OUTPUT_NODE)
Run the export after training:
python export_to_tflite.py --ckpt models1/model_test/model.ckpt-1000
This produces pfld_frozen.pb for debugging and pfld_mobile.tflite ready for Android or iOS integration.
Summary
- Checkpoint restoration: Use
tf.train.Saverto load weights fromtrain_model.pycheckpoints into the graph structure defined inmodel2.py. - Graph freezing: Convert variables to constants using
graph_util.convert_variables_to_constantsto create a standalonemodel.pbfile. - TFLite conversion: Use
tf.lite.TFLiteConverter.from_frozen_graphwith input arrayimage_batchand output array matching the final fully connected layer inpfld_inference. - Mobile optimization: Apply
tf.lite.Optimize.DEFAULTor full integer quantization for faster inference on edge devices.
Frequently Asked Questions
What is the exact output node name for the PFLD model?
The output node depends on the final operation in model2.py. According to the source at line 90, the landmarks tensor is produced by a fully connected layer scoped as pfld_inference/fc, resulting in a node name like pfld_inference/fc/MatMul or the preceding pfld_inference/conv8/Flatten/Reshape. Verify by printing landmarks.name after building the graph in Python.
Can I deploy the PFLD model without freezing the graph?
No. TensorFlow Lite requires a frozen graph (constants only) because mobile interpreters cannot restore variable checkpoints. The freeze step (Step 2) is mandatory to embed trained weights directly into the graph definition before TFLite conversion.
What input dimensions does the mobile model expect?
The PFLD model expects input tensors of shape [None, 112, 112, 3] representing batch size, height, width, and RGB channels respectively. This is defined in model2.py and must match when you specify input_arrays=['image_batch'] during TFLite conversion.
Is quantization supported for the PFLD TensorFlow Lite model?
Yes. The TensorFlow Lite converter supports post-training quantization for PFLD. Enable converter.optimizations = [tf.lite.Optimize.DEFAULT] for basic optimization, or provide a representative_dataset and set inference_input_type to tf.uint8 for full integer quantization that reduces model size and improves CPU inference speed.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →