3DDFA_V2 — LiteRT on-device 3D face alignment

3DDFA_V2 (Guo et al., ECCV 2020, MIT) re-authored for LiteRT: fit a 3D morphable face model to a photo. A MobileNetV1 regresses 62 3DMM parameters (pose + 40 shape + 10 expression) on the CompiledModel GPU; the 68 3D face landmarks (and a dense mesh) are reconstructed from the BFM bases host-side.

Verified on a Pixel 8a: fp16 tflite-vs-PyTorch 62-param corr 0.999999, reconstructed landmarks match to 0.02 px; 68 landmarks in well under a second.

Files

file purpose
tddfa_mb1_fp16.tflite MobileNetV1 3DMM regressor, crop [1,3,120,120] (BGR, (x−127.5)/128) → 62 params — GPU
tddfa_u_base.bin tddfa_w_shp_base.bin tddfa_w_exp_base.bin BFM 68-keypoint bases (interleaved x,y,z)
tddfa_param_mean.bin tddfa_param_std.bin 62-param de-normalization
face box → parse_roi → crop 120² (BGR) → [GPU MobileNetV1] → 62 params
  → denorm (·std+mean) → R,offset,α_shp,α_exp
  → verts = u_base + w_shp_base·α_shp + w_exp_base·α_exp   (interleaved [x0,y0,z0,...])
  → R·verts + offset → similar_transform(roi, 120) → 68 landmarks (x,y,z)

Minimal usage (Python)

import numpy as np
from ai_edge_litert.interpreter import Interpreter

# crop the face ROI to 120x120, BGR, NCHW, (x-127.5)/128  -> inp [1,3,120,120]
it = Interpreter(model_path="tddfa_mb1_fp16.tflite"); it.allocate_tensors()
it.set_tensor(it.get_input_details()[0]["index"], inp); it.invoke()
out = it.get_tensor(it.get_output_details()[0]["index"])[0]           # 62 (normalized)

p = out * np.fromfile("tddfa_param_std.bin", np.float32) + np.fromfile("tddfa_param_mean.bin", np.float32)
R = p[:12].reshape(3, 4)[:, :3]; offset = p[:12].reshape(3, 4)[:, 3:4]
u = np.fromfile("tddfa_u_base.bin", np.float32).reshape(-1, 1)
ws = np.fromfile("tddfa_w_shp_base.bin", np.float32).reshape(204, 40)
we = np.fromfile("tddfa_w_exp_base.bin", np.float32).reshape(204, 10)
verts = (u + ws @ p[12:52, None] + we @ p[52:, None]).reshape(3, -1, order="F")
pts = R @ verts + offset                                              # [3,68], map to image via similar_transform

A complete Android sample (face → 68 landmarks) is in google-ai-edge/litert-samples.

Upstream

cleardusk/3DDFA_V2 (MIT). Paper: Towards Fast, Accurate and Stable 3D Dense Face Alignment (ECCV 2020).

Performance

Measured on a Pixel 8a (Tensor G3, Android 16) with the standard TFLite benchmark_model tool — 10 warm-up runs then 50 timed runs, reported as the tool's mean.

Runtime Backend Graph on GPU Latency
TFLite benchmark_model (TfLiteGpuDelegateV2) GPU (OpenCL) 62 / 62 7.8 ms
TFLite benchmark_model CPU (XNNPACK, 4 threads) XNNPACK declined the graph

Any on-device figure recorded when this model shipped came from a different runtime. It was taken through LiteRT's own CompiledModel accelerator (logcat reports it as LITERT_CL), which is the path the Kotlin sample app and the LiteRT API use, and it appears elsewhere on this card. The rows above are the classic TFLite OpenCL delegate, measured with a tool anyone can download and re-run. The two are not comparable, so read the rows above as a reproducible floor rather than as this model's speed on LiteRT.

XNNPACK declines these fp16 graphs — it reports failed to delegate DEPTHWISE_CONV_2D and then fails to allocate tensors — so there is no usable CPU number. Disabling XNNPACK falls back to reference kernels, which measured about 20× slower than the GPU on models of this size and would not represent CPU inference anyone would ship.

Snapdragon NPU (Hexagon)

The NPU is 1.20x faster than the GPU (0.515 ms against 0.619 ms) and loads 4.15x faster (102 ms against 422 ms).

backend inference (median / min) load
NPU (Hexagon v81) 0.515 ms / 0.497 ms 102 ms
GPU (Adreno) 0.619 ms / 0.506 ms 422 ms

Measured on a Samsung Galaxy S26 (Snapdragon 8 Elite Gen 5 / SM8850, Hexagon v81, Android 16), LiteRT CompiledModel 2.2.0, one accelerator per process, 5 warm-up runs then N=50 timed runs, median reported. Every run held thermal status NONE throughout. Headroom 0.67, where 1.0 is the throttling threshold.

The NPU rows here ran artifacts compiled ahead of time for SM8850 with QAIRT 2.47.0; the GPU rows ran the published files as they are. LiteRT can also compile for the NPU on the device at first load, which is what lets you ship the published file unchanged — that path and the ten runtime libraries it needs are in the NPU recipe, and we did not measure it here. GPU wiring is in the GPU recipe.

Downloads last month
20
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Collection including litert-community/3DDFA-V2-LiteRT