Melange
Tutorials

Face Landmark Detection

Detect facial landmarks using a two-model pipeline with ZETIC Melange.

Build an on-device face landmark detection application using a two-model pipeline with ZETIC Melange. This tutorial demonstrates how to chain Face Detection and Face Landmark models together for accurate facial landmark extraction on Android and iOS.

We provide Face Landmark demo application source code for both Android and iOS.

What You Will Build

A real-time face landmark detection application that first detects faces, then extracts detailed facial landmarks from each detected face region. This two-step pipeline ensures accurate landmark placement by feeding properly cropped face images to the landmark model.

Prerequisites

What is Face Landmark?

The Face Landmark model in Google's MediaPipe is a highly efficient machine learning model used for real-time face detection and landmark extraction.

Model Pipelining

For accurate use of the face landmark model, it is necessary to pass an image of the correct facial area to the model. To accomplish this, we construct a pipeline with the Face Detection model:

  1. Face Detection: Use the Face Detection model to accurately detect face regions in the image. Extract that part of the original image using the detected face region information.
  2. Face Landmark: Input the extracted face image into the Face Landmark model to analyze facial landmarks.

Step 1: Convert the Models to ONNX

We prepared pre-built models for you — you can skip Steps 1–2 and jump straight to Step 3 using these models from the Melange Dashboard:

Prepare both models from GitHub and convert them to ONNX format.

Face Detection model:

pip install tf2onnx
python -m tf2onnx.convert --tflite face_detection_short_range.tflite --output face_detection_short_range.onnx --opset 13

Face Landmark model:

python -m tf2onnx.convert --tflite face_landmark.tflite --output face_landmark.onnx --opset 13

Step 2: Generate Melange Models

Upload both models and their inputs via the Melange Dashboard:

  • face_detection_short_range.onnx with input input.npy
  • face_landmark.onnx with input input.npy

Step 3: Implement ZeticMLangeModel

The Face Detection model feeds cropped face regions into this model; see Face Detection for its own code.

For detailed application setup, please follow the Android Integration Guide guide.

val faceLandmarkModel = ZeticMLangeModel(this, PERSONAL_KEY, "google/MediaPipe-Face-Landmark")

val pixels: FloatArray = preprocess(croppedFaceBitmap)
val inputs = arrayOf(
    Tensor.of(
        data = pixels,
        dataType = DataType.Float32,
        shape = intArrayOf(1, 192, 192, 3),
    )
)

val outputs = faceLandmarkModel.run(inputs)

For detailed application setup, please follow the iOS Integration Guide guide.

let faceLandmarkModel = try ZeticMLangeModel(personalKey: PERSONAL_KEY, name: "google/MediaPipe-Face-Landmark")

let pixels: [Float] = preprocess(croppedFaceImage)
let inputs = [
    Tensor(
        data: pixels.withUnsafeBufferPointer { Data(buffer: $0) },
        dataType: BuiltinDataType.float32,
        shape: [1, 192, 192, 3]
    )
]

let outputs = try faceLandmarkModel.run(inputs: inputs)

Step 4: Use the Face Landmark Wrapper

We provide a Face Landmark feature extractor as an Android and iOS module.

The Face Landmark feature extractor extension will be released as an open-source repository soon.

// (0) Initialize Face Landmark wrapper
val feature = FaceLandmarkWrapper()

// (1) Preprocess bitmap and get processed float array
val inputs = feature.preprocess(bitmap)

// ... run model ...

// (2) Postprocess to bitmap
val resultBitmap = feature.postprocess(outputs)
import ZeticMLange

// (0) Initialize Face Landmark wrapper
let feature = FaceLandmarkWrapper()

// (1) Preprocess UIImage and get processed float array
let inputs = feature.preprocess(image)

// ... run model ...

// (2) Postprocess to UIImage
let resultBitmap = feature.postprocess(&outputs)

Complete Face Landmark Pipeline Implementation

The complete implementation requires pipelining two models: Face Detection followed by Face Landmark.

Step 1: Face Detection

// (0) Initialize face detection model
val faceDetectionModel = ZeticMLangeModel(this, PERSONAL_KEY, "google/MediaPipe-Face-Detection")
val faceDetection = FaceDetectionWrapper()

// (1) Preprocess image
val faceDetectionInputs = faceDetection.preprocess(bitmap)

// (2) Run face detection model
val faceDetectionOutputs = faceDetectionModel.run(faceDetectionInputs)

// (3) Postprocess to get face regions
val faceDetectionPostprocessed = faceDetection.postprocess(faceDetectionOutputs)

Step 2: Face Landmark

// (0) Initialize face landmark model
val faceLandmarkModel = ZeticMLangeModel(this, PERSONAL_KEY, "google/MediaPipe-Face-Landmark")
val faceLandmark = FaceLandmarkWrapper()

// (1) Preprocess with detected face regions
val faceLandmarkInputs = faceLandmark.preprocess(bitmap, faceDetectionPostprocessed)

// (2) Run face landmark model
val faceLandmarkOutputs = faceLandmarkModel.run(faceLandmarkInputs)

// (3) Postprocess to get landmarks
val faceLandmarkPostprocessed = faceLandmark.postprocess(faceLandmarkOutputs)

Step 1: Face Detection

// (0) Initialize face detection model
let faceDetectionModel = try ZeticMLangeModel(personalKey: PERSONAL_KEY, name: "google/MediaPipe-Face-Detection")
let faceDetection = FaceDetectionWrapper()

// (1) Preprocess image
let faceDetectionInputs = faceDetection.preprocess(bitmap)

// (2) Run face detection model
let faceDetectionOutputs = try faceDetectionModel.run(inputs: faceDetectionInputs)

// (3) Postprocess to get face regions
let faceDetectionPostprocessed = faceDetection.postprocess(faceDetectionOutputs)

Step 2: Face Landmark

// (0) Initialize face landmark model
let faceLandmarkModel = try ZeticMLangeModel(personalKey: PERSONAL_KEY, name: "google/MediaPipe-Face-Landmark")
let faceLandmark = FaceLandmarkWrapper()

// (1) Preprocess with detected face regions
let faceLandmarkInputs = faceLandmark.preprocess(bitmap, faceDetectionPostprocessed)

// (2) Run face landmark model
let faceLandmarkOutputs = try faceLandmarkModel.run(inputs: faceLandmarkInputs)

// (3) Postprocess to get landmarks
let faceLandmarkPostprocessed = faceLandmark.postprocess(faceLandmarkOutputs)

Conclusion

With ZETIC Melange, building multi-model pipelines for on-device AI is straightforward. The Face Detection to Face Landmark pipeline demonstrates how you can chain models together for accurate, real-time facial analysis with NPU acceleration.

We are continually adding new models to our examples and HuggingFace page.

Stay tuned and contact us to collaborate on exciting projects!

On this page