Post

On-Device Face Detection and Recognition in TFLite: What You Can Actually Ship Today

On-Device Face Detection and Recognition in TFLite: What You Can Actually Ship Today

Research what is current open-source on-device face detection model and face recognition model that can be used as TFLite file. Summarize file link, code, latency, accuracy, size, and license.

Short answer

  • Detection is solved and permissively licensed; recognition is not. MediaPipe BlazeFace (Apache-2.0, a real weights grant, model card) and OpenCV YuNet (MIT) give production-ready, permissively-licensed face detection as TFLite today. Face recognition has no first-party TFLite from anyone, and — the binding constraint — almost every credible open recognition model is non-commercial because of its training data (MS-Celeb-1M retracted by Microsoft in 2019, Glint360K/WebFace/VGGFace2 all research-only), regardless of how permissive its code license reads.
  • The one free-commercial recognition path is SFace (OpenCV Zoo, Apache-2.0), and this report verifies it works as a self-converted TFLite file — numerical parity with the ONNX reference is exact (cosine 1.000000) and identity separation matches SFace’s documented threshold when paired with true 5-point face alignment. Two asterisks stay open: SFace’s training dataset is undisclosed (OpenCV issue #124 — if it turns out to be MS1MV2, the license grant is moot), and its accuracy numbers cite no named benchmark.
  • Measured on Apple M4 CPU/XNNPACK (this report’s own numbers, not a phone): BlazeFace short-range detects in 0.72 ms/image; the “full-range-sparse” detector is smaller but slower than the dense one, because XNNPACK has no sparse kernel path; SFace embeds a face in 5.25 ms; alignment quality is not a nicety — it is the difference between a working and a broken embedding.
  • BlazeFace is not a crowd detector by design. It found 0/29 faces in a 29-person 1927 conference photo at full frame (faces are 4–7 px at model input, below its floor); a 6-tile sweep recovers 18/29 at real but modest cost (13.6 ms). This is expected behavior per its own model card (out of scope beyond 2–5 m), not a bug.

Detection: comparison table

Every row below was checked directly: URLs curl-verified, .tflite files loaded with ai-edge-litert to read real tensor shapes. Latency rows marked (M4, this report) are first-hand measurements on the host described in What I ran and measured; other latency figures are secondhand and labeled by their own source device.

ModelFileSizeQuantInput → output (post-processing needed)License (weights)AccuracyLatencyCode
BlazeFace short-rangeblaze_face_short_range.tflite229,746 B (230 KB)fp16 weights, f32 I/O[1,128,128,3] f32 → regressors[1,896,16] + classificators[1,896,1], 896 SSD anchors — decode + sigmoid + NMS requiredApache-2.0, stated on the model cardNo WIDER FACE AP published. Model card: avg recall 98.4% across perceived gender, 98.1% skin tone, 99.1% geo-subregion0.72 ms/img (M4 CPU/XNNPACK, this report) · model card ~3.6 ms Pixel 2 CPU · Google docs 2.94 ms Pixel 6 CPU / 7.41 ms Pixel 6 GPUthis report’s blazeface.py (pure decoder, no MediaPipe dep) · patlevin/face-detection-tflite (MIT)
BlazeFace full-rangeblaze_face_full_range.tflite1,083,786 B (1.08 MB)fp16 weights, f32 I/O[1,192,192,3] f32 → reshaped_regressor_face_4[1,2304,16] + reshaped_classifier_face_4[1,2304,1], 2304 CenterNet-like anchors — decode + NMS requiredApache-2.0 (model card, 2021-01-19)No WIDER FACE AP published2.13 ms/img (M4 CPU/XNNPACK, this report) · model card ~50 FPS (20 ms) Pixel 3 CPU / ~145 FPS (6.9 ms) Pixel 4 CPUsame as above
BlazeFace full-range-sparseblaze_face_full_range_sparse.tflite676,746 B (677 KB)fp16 weights, f32 I/OSame shapes as full-range, output tensor names Identity/Identity_1Apache-2.0No WIDER FACE AP published2.52 ms/img (M4 CPU/XNNPACK, this report) — slower than the dense full-range model despite being 38% smaller (below)same as above
YuNet — official ONNXface_detection_yunet_2026may.onnxnot TFLitefp32, dynamic H/WAnchor-free FCOS-style, 3 stridesRepo Apache-2.0; README states model files are MITWIDER FACE (current README): Easy 0.8844 / Medium 0.8656 / Hard 0.7503 — the best-documented accuracy in this surveynot published for ONNX directlyopencv_zoo
YuNet — PINTO TFLite conversion387_YuNetV2 tarball..._float16.tflite151,872 B (152 KB, fp16 variant); fp32 244,560 B; int8 variants 94–123 KBfp32 / fp16 / dynamic-range int8 / full int8 (5 files in one tarball)[1,640,640,3] f32, frozen at 640×640 (loses ONNX’s dynamic-shape advantage) → 12 outputs across 3 strides (cls/obj/bbox/kps ×3) — combine cls×obj, decode per stride, NMS yourselfMIT (PINTO_model_zoo, 4,536★, last push 2026-07-20)AP was measured on the 2023mar ONNX, never on this conversion — treat as unverified for the shipped filenot published for this filetarball ships no runner script; decode per OpenCV’s demo.py logic
Qualcomm face_det_liteface_det_lite-tflite-float.zip (float) / -w8a8.zip (int8)float 3,538,208 B (3.5 MB); w8a8 989,808 B (990 KB)fp32 or uint8 (scale 0.00392157, zp 0)[1,480,640,1] grayscale, single channel → CenterNet heatmap [1,60,80,1] + bbox + landmark heads, no anchors — heatmap peak extraction + per-cell decode“other” — defers to the original implementation, not Apache/MIT; read before commercial useNot published — proprietary training set, its main weaknessNPU: 0.196 ms on Snapdragon 8 Elite Gen 5 QNN_DLC w8a8 (Qualcomm AI Hub)Qualcomm AI Hub model card only
Ultra-Light-Fast-1MB (RFB/slim-320)tflite pretrained1.1–1.2 MBfp32 despite the “1MB” branding[1,240,320,3] → 4420 raw anchors, no NMS baked inMITHard AP 0.395–0.438 at 320 — roughly half YuNet’s 0.750README figures are NCNN/MNN, not TFLite: iPhone 6s+ ~6–8 msincluded in repo, but repo dead 4.4 yr — avoid
SCRFD / RetinaFace (InsightFace) via PINTOone monolithic tarball per modelSCRFD tarball 3.32 GiB; RetinaFace 11.6 GiB for one 500 KB filemixednot enumerated (download blocked by tarball size)InsightFace weights are non-commercial research by the project’s own terms (license: null on GitHub)not verifiednot verifiedimpractical: multi-GB download for a few hundred KB of weights

BlazeFace short-range is the recommended default: only option with an explicit Apache-2.0 weights grant, a real model card, sub-1ms CPU latency, and mature reference code in three languages. Its cost is that no WIDER FACE AP exists anywhere for it, and it is explicitly out of scope beyond 2 m and for crowds — see the group-photo finding below. YuNet via the PINTO fp16 conversion is the pick if a defensible accuracy number matters (MIT, smallest file at competitive accuracy) — with the caveat that the AP was measured on the ONNX, not on this specific conversion.

Recognition: comparison table

No official .tflite exists for any face-recognition model — every row below is either ONNX/PyTorch-only or a third-party TFLite conversion of unclear provenance. The recognition survey’s central finding: the gating factor is almost never the code license, it is the training dataset.

ModelOfficial TFLite?Weights licenseTraining dataInput/embedAccuracyCommercial use?
AuraFace-v1 (fal)No (ONNX)Apache-2.0“commercial + public sources” — unaudited vendor claim112²/512 (unverified against the artifact)LFW 99.65 / CFP-FP 95.19 / AgeDB 96.10Yes, with a trust caveat
SFace (OpenCV Zoo)No (ONNX); converted here, see worked exampleApache-2.0Unstated — OpenCV issue #124112²/128 — corrected from the survey’s assumption of 512-D0.9940/0.9942/0.9932 — benchmark never namedYes, with an audit gap
buffalo_l (w600k_r50)No (ONNX)Non-commercial (paid license available)WebFace600K112²/512LFW 99.83, IJB-C 97.25No — pay InsightFace
buffalo_s/sc (w600k_mbf)No (ONNX)Non-commercialWebFace600K112²/512LFW 99.70, IJB-C 95.02No — pay InsightFace
EdgeFace-XXS-Q (int8, 1.73 MB)No (.pt)CC BY-NC-SA-4.0WebFace 4M/12M112²/512LFW 99.50, IJB-C 92.97No (license + data both block it)
GhostFaceNetV1-1.3-1No (.h5, one-step convert)MITMS1MV3 (retracted lineage)112²/512LFW 99.73, IJB-C 94.94License yes, data no
Community facenet.tflite (128-D)Community onlyMIT code, MS-Celeb-1M dataMS-Celeb-1M (Microsoft-retracted 2019)160²/128LFW 99.2 (claimed)No
Community facenet_512.tfliteCommunity onlyMIT code, VGGFace2 dataVGGFace2 (research-only)160²/512LFW 99.65 claimed / 97.7 independently remeasured (NXP eiq-model-zoo)No
MobileFaceNet (sirius-ai lineage)Community onlyApache-2.0 code, MS1M dataMS1M-refine-v2112²/192 (not 128 as train_nets.py implies)LFW 99.25–99.4No

Full sourcing, contradiction log, and every dead-end (TopoFR’s missing license, the yakhyo/FaceLiVTv2 relicensing traps, GhostFaceNets, PocketNet, AdaFace) live in the source survey; see Sources.

Licensing verdict — why recognition is the hard problem

No, there is no ready-made permissively-licensed commercial-use recognition .tflite. Two paths exist and both require converting yourself (SFace — done in this report — or AuraFace). Everything else fails for the same underlying reason, regardless of its code license:

Training setStatusPoisons
MS-Celeb-1MRetracted by Microsoft, June 2019 — scraped without consent, “Celeb” label misleadingly swept in journalists and academics. Legal status of derivatives unclear.MS1MV2, MS1MV3, and anything merged from them
Glint360KNon-commercial research only (InsightFace’s blanket statement)antelopev2, TopoFR, FaceLiVTv2
WebFace260M/4M/12MAcademic research only, signed agreement, no redistributionEdgeFace, buffalo_*, AdaFace WebFace checkpoints
VGGFace2Non-commercial research; original site withdrawncommunity FaceNet-512, some AdaFace/SphereFace2
CASIA-WebFaceResearch-oriented; the most permissive real dataset, weaker accuracysome AdaFace checkpoints
DigiFace-1M (synthetic, Microsoft)Even the synthetic escape hatch is closed: “non-commercial research”

A code license does not relicense the weights trained on top of it: yakhyo/edgeface-onnx is MIT but redistributes Idiap’s CC BY-NC-SA-4.0 weights unchanged; FaceLiVTv2’s LICENSE file is a verbatim, never-updated copy of Idiap’s — GitHub itself flags it NOASSERTION. Read the weights grant, never the repo badge.

The two viable commercial paths, ranked:

  1. Pay for it: InsightFace now runs a commercial licensing program (recognition-oss-pack@insightface.ai, announced 2025-11-24) covering buffalo_l/antelopev2/buffalo_s/buffalo_m — unambiguous legal footing, no public pricing, still ONNX-only so you still convert.
  2. Free, with an open asterisk: SFace (Apache-2.0) — this report closes the “does it actually work as TFLite” half of that asterisk; the training-data half (issue #124) remains genuinely open. If SFace’s undisclosed dataset turns out to be MS1MV2, the Apache-2.0 grant does not protect a downstream user.
flowchart TD
    Start["Need a face .tflite pipeline"] --> Kind{"Detection or\nrecognition?"}
    Kind -- Detection --> Range{"Selfie/near-field\nor need published AP?"}
    Range -- "≤2m, smallest, Apache-2.0" --> Blaze["BlazeFace short-range\n230 KB, 0.72 ms M4 CPU"]
    Range -- "need WIDER AP, MIT" --> YuNetC["YuNet PINTO fp16\n152 KB, AP 0.88/0.87/0.75 (on ONNX)"]
    Kind -- Recognition --> Lic{"Commercial use\nrequired?"}
    Lic -- "Yes, free" --> SFaceC["SFace ONNX->TFLite\n(this report's conversion)\nApache-2.0, data unaudited"]
    Lic -- "Yes, budget available" --> Insight["InsightFace buffalo_s/sc\npaid commercial license"]
    Lic -- "No, research only" --> Edge["EdgeFace-XXS-Q, 1.73 MB\nCC BY-NC-SA-4.0"]

What I ran and measured

Host for every latency number below: Apple M4 Mac mini, 10 cores, 16 GB RAM, macOS 26.5.1, Python 3.12.13, ai-edge-litert 2.1.6, XNNPACK CPU delegate, batch 1. These are CPU numbers on a Mac, not device or NPU numbers — they rank models against each other, not against phone latency. Sample images: four public-domain Wikimedia Commons photographs resolved via the Commons API — the 1927 Solvay Conference group portrait (29 people), Ferdinand Schmutzer’s 1921 portrait of Einstein, a second Einstein head crop, and a Marie Curie portrait circa 1920s — downloaded to /tmp/facesamples/.

Reproducible code (downloaded/written to /tmp/facemodels/, no MediaPipe dependency):

FileWhat it does
/tmp/facemodels/blazeface.pyBlazeFace SSD anchor generator + TensorsToDetections decoder, ported directly from MediaPipe’s SsdAnchorsCalculator/TensorsToDetections C++ sources, plus NMS. Anchor counts assert-match the model tensors (896 short-range, 2304 full-range) — a load-time correctness check, not a hope.
/tmp/facemodels/align.pyArcFace 5-point canonical destination (arcface_dst, from InsightFace’s face_align.py) and similarity-warp alignment; a 3-point subset for BlazeFace, which emits no mouth corners.
/tmp/facemodels/bench_all.pyEnd-to-end benchmark driver: detect → crop → embed → latency + cosine similarity, dumps /tmp/facemodels/results.json.
/tmp/facemodels/test_detect.py, /tmp/facemodels/diag_group.pyPer-model detection sweep and the group-photo threshold/tiling diagnosis.
/tmp/facemodels/test_recog.pyRecognition ablation across normalization conventions (prewhiten vs [-1,1]).

Detection (median of 50 runs)

ModelSizeInputAnchorsLatency (M4 CPU)
face_detection_short_range229,032 B128²8960.716 ms
blaze_face_short_range (fp16)229,746 B128²8960.72 ms — byte-identical decode output to the legacy file
face_detection_full_range1,083,786 B192²2,3042.13 ms
face_detection_full_range_sparse676,746 B192²2,3042.52 ms

Key finding: the “sparse” full-range model is smaller (677 KB vs 1.08 MB, ~38% less) but slower (2.52 ms vs 2.13 ms) on this hardware. XNNPACK has no sparse-kernel execution path on CPU, so the sparsity costs cycles rather than saving them — a real trap for anyone choosing the file by size alone.

All four variants detect exactly 1 face on both portrait photos (Einstein, Curie) and 0 faces on the 29-person Solvay 1927 group photo at full frame — every variant, not a specific bug. Root cause, traced rather than accepted: the group-photo faces are 27–45 px wide in the original image; at a 192-px model input on a 1280-px-wide photo, that downscales to 4–7 px at the model’s input resolution, below BlazeFace’s detection floor. A 3×2 tiled sweep (0.2 overlap, 6 tiles) recovers 18 of 29 faces at 0.51–0.86 confidence, in 13.65 ms total — the decoder is correct, and BlazeFace is a selfie/near-field detector by design (its own model card states out-of-scope beyond 2 m short-range / 5 m full-range), not a crowd detector. Tiling is the correct workaround, not a bug fix.

Recognition (median of 50 runs; face crop from BlazeFace full-range)

ModelSizeInput/dimLatencySame-identity cosDifferent-identity cos
Community facenet.tflite (128-D)23,705,216 B (23.7 MB)160²/1287.42 ms/face0.64–0.78 (loose crop → aligned crop)0.20–0.29
Community facenet_512.tflite24,394,880 B (24.4 MB)160²/5127.42 ms/face0.60–0.660.20–0.36

Independently confirmed the survey’s contradiction: both files are int8, not fp16 as the source repo claims. Direct tensor-dtype count: 316 float32 tensors (activations), 1 int32, and 132 int8 per-channel weight tensors, zero float16 tensors — for both files. Size math corroborates it: 23.5M params at ~1 byte/param ≈ 23.7 MB matches int8; fp16 would be ~47 MB; the known fp32 reference build is 93.9 MB.

Alignment ablation — the single biggest lever on recognition quality measured in this report:

ModelCrop methodSame-identity cosMargin over hardest different-identity pair
FaceNet-128loose bbox + margin0.647+0.31
FaceNet-1283-point aligned (BlazeFace kps)0.780+0.49
FaceNet-512loose bbox + margin0.652+0.29
FaceNet-5123-point aligned0.605+0.33
SFace (converted, this report)loose bbox + margin0.904−0.04 (fails)
SFace (converted, this report)3-point aligned (BlazeFace kps)0.871−0.05 (fails, worse than loose)

Two readings from the same table: alignment moved FaceNet-128’s margin from +0.31 to +0.49 (a clear win), but 3-point alignment made SFace worse, not better — both crop strategies leave SFace’s margin negative. SFace needed the fifth landmark; see the worked example below.

Worked example: converting SFace to TFLite (closes an open question in the source survey)

The recognition survey flagged this as its biggest open item: “nobody has published a TFLite conversion [of SFace]… SFace is the best free-commercial candidate but unverified.” This report closes that gap end to end.

  1. Downloaded face_recognition_sface_2021dec.onnx (Apache-2.0, OpenCV Zoo) to /tmp/facemodels/sface.onnx (38,692,565 B).
  2. Corrected the survey’s size assumption: the model is 9.67M parameters, 38.7 MB, opset 11, input NCHW [1,3,112,112], output 128-D, 27 PReLU ops — not “MobileFaceNet-sized” and not 512-D. At ~10× a real MobileFaceNet’s parameter count, “MobileFaceNet trained with SFace loss” undersells its actual size.
  3. Converted with the exact PReLU workaround the survey predicted would be needed:
    1
    
    onnx2tf -i sface.onnx -o sface_tf -osd -rtpo PReLU
    

    Output: sface_tf/sface_float32.tflite (38,574,800 B, 38.6 MB) and sface_tf/sface_float16.tflite (19,312,176 B, 19.3 MB).

  4. Verified numerical parity: fp32 TFLite vs ONNX reference on real face crops gives cosine 1.000000 — an exact match, not an approximation. Latency 5.25 ms/face on the M4 CPU (ONNX reference: 3.66 ms).
  5. Found the correct preprocessing by ablating six variants: SFace wants raw [0,255] BGR (OpenCV blobFromImage defaults), not the [-1,1] ArcFace convention most recognition pipelines assume. Feeding [-1,1]-normalized input collapses the embedding space — same-identity and different-identity pairs become indistinguishable.
  6. End-to-end verification with the correct alignment: using OpenCV’s own YuNet-detect + FaceRecognizerSF.alignCrop() 5-point alignment, same-identity cosine came out 0.60, different-identity 0.19/0.27 — a +0.34 margin, matching SFace’s documented 0.363 cosine threshold almost exactly. The reference ONNX pipeline gives the same numbers. The converted TFLite file is verified-correct end to end, provided it gets real 5-point alignment.
  7. The caveat worth a callout: BlazeFace emits no mouth-corner keypoints, so this report’s 3-point alignment fallback (used successfully for FaceNet above) is not good enough for SFace — it collapses the margin to negative (measured: -0.04 loose crop, -0.05 with 3-point alignment; see the ablation table above). SFace/ArcFace-family models have a hard dependency on true 5-point alignment — it is not an accuracy nicety, it is the difference between a working and a non-working pipeline.
  8. A real portability gotcha: the fp16 TFLite conversion fails to load on the CPU/XNNPACK path
    1
    
    CONV_2D node 2 failed to prepare: input_type must be float32/uint8/int8/int16
    

    It needs a GPU delegate. Anyone planning to ship the fp16 file for its smaller size must budget for a GPU-delegate code path, not assume it runs everywhere the fp32 file does.

What could not be verified

Merged from both source surveys plus this report’s own testing gaps — read as open questions before shipping, not settled facts:

Detection

  • WIDER FACE AP for any MediaPipe BlazeFace variant — not in any model card or Google doc; any number circulating online is unsourced.
  • AP for the PINTO YuNet TFLite conversion specifically — the published 0.884/0.866/0.750 numbers are for the ONNX, never re-measured on the converted file.
  • Qualcomm face_det_lite’s AP (proprietary training set) and its exact weights license text (“other” — not fetched).
  • InsightFace SCRFD/RetinaFace weights license (GitHub API returns license: null); assume research-only until confirmed.
  • GPU/NPU latency for any detector on this report’s own hardware — every number here is CPU/XNNPACK on an Apple M4, not a phone.

Recognition

  • SFace’s training dataset — the single biggest open item for the report’s own headline recommendation. OpenCV issue #124 is unresolved; candidates include CASIA-WebFace, VGGFace2, and MS1MV2, and only the last would disqualify it.
  • SFace’s own benchmark identity — the 0.9940/0.9942/0.9932 figures in the OpenCV Zoo README name no dataset.
  • AuraFace’s training-data claim (“commercial + public sources”) is a vendor assertion with no dataset name, no license chain, no third-party audit; its input shape/embedding dim were not independently verified against the artifact.
  • EdgeFace latency on any device — genuinely absent from the paper despite the “for Edge Devices” title.
  • GhostFaceNets Android TFLite latency — the cited IEEE Access source returned HTTP 418.

This report’s own test scope

  • No GPU or NPU latency was measured for any model — every number is Apple M4 CPU/XNNPACK, batch 1.
  • Face samples were four public-domain Wikimedia portraits plus one historical group photo, not the standard WIDER FACE (detection) or LFW (recognition) benchmark sets — the measured accuracy numbers here are identity-separation sanity checks (same-person vs. different-person cosine margins), not benchmark-comparable accuracy figures.
  • SFace’s end-to-end 5-point-alignment verification used OpenCV’s own YuNet + alignCrop() pipeline, a different code path from this report’s BlazeFace-keypoint alignment (align.py) — the two were not merged into one pipeline; a production integration still needs a real 5-point landmark source (YuNet’s own 5 keypoints, or MediaPipe’s face landmarker) paired with SFace, not BlazeFace’s 3.

Recommendation

  • Detection, default choice: BlazeFace short-range (Apache-2.0, 230 KB, 0.72 ms/img on M4 CPU). Accept that it has no published WIDER FACE AP and is out of scope for crowds/distance — tile the image if you need those.
  • Detection, if a defensible accuracy number matters: YuNet via the PINTO fp16 conversion (MIT, 152 KB) — but re-validate the AP on the actual converted file before quoting it.
  • Recognition, free and commercial-safe today: SFace, converted per the worked example above — Apache-2.0, verified cosine-1.0 parity with ONNX, works when paired with true 5-point alignment. Resolve the training-data question (issue #124) before shipping at scale; treat the license as provisional until then.
  • Recognition, if budget allows and legal certainty is required now: license InsightFace’s buffalo_s/buffalo_sc through their commercial program and convert with onnx2tf.
  • Do not ship any circulating community facenet.tflite. The 128-D branch traces to MS-Celeb-1M (Microsoft-retracted); the 512-D branch to non-commercial VGGFace2; every hosting repo is dead (2018–2021); this report’s own tensor-dtype inspection confirms the “FP16” claim on at least one popular mirror is simply false.

Sources

This post is licensed under CC BY 4.0 by the author.

Trending Tags