Dog Facial Landmarks (DogFLW, 46 points)

Demo

46 facial landmarks on a German shepherd, predicted by the models in this repository. The orange box and skeleton are body detection and pose from animal_detection; the cyan box is this repository's face localizer; the coloured points are its 46 landmarks.

Two-stage dog face analysis in TFLite: a face localizer that finds the face, and a landmark detector that predicts the 46-point DogFLW scheme on the resulting crop.

These are the models that ship in the dog_detection Flutter package. As far as I can tell they are the first publicly released weights trained on DogFLW.

Training code, the full experiment journal, and the evaluation harness are at hugocornellier/dog-face-landmarks-training.

Files

File Size What it is
dog_face_localizer.tflite 16 MB Stage 1. EfficientNetB2, 224px, predicts one face box
dog_face_landmarks_full.tflite 11 MB Stage 2. MobileNetV3-Large, 384px, 46 landmarks. The shipped model
dog_face_landmarks_effnetv2s_384.tflite 55 MB Stage 2, EfficientNetV2-S. See the note below before reaching for it
keras/*.keras 35 to 210 MB Full Keras models, for fine-tuning or re-export
metadata/*.json Per-model training config and validation metrics
metadata/*.csv Full per-epoch training logs

Both .tflite files are float16. The two shipped ones are static-shape exports (batch-1 concrete function), which is what allows the GPU delegate to accept the graph. On an M4 Max the landmark stage runs 27.10 ms on XNNPACK CPU against 3.82 ms on GPU via CompiledModel.

Accuracy

NME_IOD (normalized mean error, inter-ocular distance) on the DogFLW validation split of 480 images. Lower is better.

Model Backbone Res NME_IOD Size
dog_face_landmarks_full MobileNetV3-Large 384 8.56 11 MB
dog_face_landmarks_effnetv2s_384 EfficientNetV2-S 384 8.77 55 MB

The 11 MB model is the more accurate one here, which is the opposite of the cat side and was not expected. The MobileNetV3-Large run came later, after the augmentation and fine-tuning schedule had been worked out, and it beats the EfficientNetV2-S model at a fifth of the size. The larger model is included because it is a different error profile and is the base for the 8.04 ensemble, not because it is better on its own.

Best result in the project is 8.04, from a 3-model EfficientNetV2-S ensemble at 256+320+384 with multi-scale and flip TTA, at 18 forward passes. Those weights are not published here; the journal has the recipe.

For reference, the DogFLW paper's ELD ensemble reaches 6.52. This project started near 40.

Localizer: 0.79 bbox IoU. Trained on 3,853 images, validated on 480.

Dogs are much harder than cats. The same architecture reaches 3.27 on CatFLW and 8.77 here. Ear landmarks carry the error at NME 12 to 14 against roughly 5 for eyes, ear tips reach 15 to 18, and the train-val gap sits near 3.4 across every model tried and did not respond to regularization. The journal works through why.

Input and output contract

Localizer takes [1, 224, 224, 3] float32 in [0, 1], letterboxed to square. It returns bbox_xyxy, normalized [0, 1] in letterboxed coordinates. Undo the letterbox to get image coordinates.

Landmarks takes [1, 384, 384, 3] float32 in [0, 1]: crop the image to the face box expanded by a 0.1 margin, then resize to square. It returns landmarks_xy of shape [1, 92], flattened [x0, y0, x1, y1, ... x45, y45], normalized [0, 1] relative to the crop, not the original image. Map them back through the same crop to get image coordinates.

Rescaling to [0, 255] for the backbone happens inside the graph. Do not do it yourself.

Exact per-model config, including every augmentation setting, is in metadata/*.json.

Usage

Python:

import numpy as np, tensorflow as tf

interp = tf.lite.Interpreter("dog_face_landmarks_full.tflite")
interp.allocate_tensors()
inp, out = interp.get_input_details()[0], interp.get_output_details()[0]

# crop: face box + 0.1 margin, resized to 384x384, float32 in [0, 1]
interp.set_tensor(inp["index"], crop[None].astype(np.float32))
interp.invoke()
xy = interp.get_tensor(out["index"]).reshape(46, 2)  # normalized to the crop

Flutter: use dog_detection, which wires both stages together, handles the crop math, and adds a species gate.

License

CC BY-NC 4.0. Non-commercial use only. See LICENSE.

These weights are derived from the DogFLW dataset, which is CC BY-NC 4.0. I asked the dataset authors directly how they wanted weights trained on their annotations to be licensed. They asked for CC BY-NC 4.0 rather than a permissive license, to stay consistent with the non-commercial terms of the source data, and granted permission to publish on that basis.

If you need commercial use, that permission is not mine alone to give. Contact the dataset authors at the Tech4Animals Lab, University of Haifa.

The training code in the GitHub repository is Apache 2.0. Only the weights are non-commercial.

Citation

Please cite the DogFLW paper:

@article{martvel2025dog,
  title={Dog facial landmarks detection and its applications for facial analysis},
  author={Martvel, George and Zamansky, Anna and Pedretti, Giulia and Canori,
          Chiara and Shimshoni, Ilan and Bremhorst, Annika},
  journal={Scientific Reports},
  volume={15},
  number={1},
  pages={21886},
  year={2025},
  publisher={Nature Publishing Group UK London}
}

Acknowledgements

Thanks to George Martvel, Anna Zamansky, Giulia Pedretti, Chiara Canori, Ilan Shimshoni, and Annika Bremhorst at the Tech4Animals Lab, University of Haifa, for publishing DogFLW and for permission to release these weights.

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