BAMBI Red Deer Sex Classifiers (DINOv3 + triplet-reduced head)

Sex classification (male vs female_juvenile) for red deer in UAV imagery, in the same embedding + head design as bambi-occlusion-classifiers: a raw DINOv3 ViT-H+ embedding goes in, a triplet-loss-trained projection reduces it to a 256-d embedding, and a logistic head reads that. Each self-contained TorchScript .pt records both the reducer and the classifier.

Nine models β€” three projection types x three modalities:

directory projection renders
non_geo/ perspective (non-orthographic) β€”
geo_1k/ orthographic 1k
geo_2k/ orthographic 2k

Each contains sex_rgb.pt, sex_thermal.pt, and sex_matched.pt.

Read this before using the labels

female_juvenile is a merged class, not "female". It means female or juvenile β€” juvenile sex is not determinable from aerial crops, so juveniles of both sexes are pooled with adult females.

Consequences you must design around:

  • A male prediction means adult male. A female_juvenile prediction means "not an adult male" β€” it does not mean female.
  • These models cannot sex juveniles. Do not use them to.
  • You cannot derive a sex ratio from these outputs without separately accounting for juveniles.

The models are also trained on clear (non-occluded) crops only β€” occlusion hides the sex cues (antlers, body shape). Filter your crops first; the occlusion classifiers exist for exactly this, and feeding occluded crops in here is out-of-distribution.

Inputs and outputs

forward(x) takes raw DINOv3 CLS features (N, D) and returns a tuple:

  • embedding (N, 256), L2-normalised
  • probs (N, 2) β€” in classes order ["female_juvenile", "male"]
modality D features
rgb 1280 RGB CLS
thermal 1280 thermal CLS
matched 2560 [rgb, thermal] concatenated, in that order
import torch
from huggingface_hub import hf_hub_download

path = hf_hub_download("cpraschl/bambi-red-deer-sex-classifiers", "non_geo/sex_rgb.pt")
m = torch.jit.load(path)              # no .eval() needed (BatchNorm folded in)

with torch.no_grad():                 # see note below
    emb, probs = m(feats)             # feats: (N, 1280) raw DINOv3 features

label = m.classes[int(probs[0].argmax())]
confidence = float(probs[0].max())

Wrap inference in torch.no_grad(). These were exported with parameters still requiring grad, so calling them outside no_grad builds an autograd graph every call and returns requires_grad=True outputs. (.eval() genuinely is unnecessary β€” BatchNorm is folded in.)

Attributes: m.classes, m.emb_dim (256), m.reduction ("triplet-loss (batch-hard)"), m.task ("sex").

These consume DINOv3 features, not images β€” you must run the DINOv3 ViT-H+ backbone yourself. It is not redistributed here; see the licensing note below.

Architecture

standardize(mu, sd)
  -> Linear(D, 512) + BatchNorm (folded)
  -> ReLU
  -> Linear(512, 256)
  -> L2-normalise            <- triplet loss, batch-hard
  -> logistic head

Accuracy

Held-out balanced accuracy, GroupKFold by track. Sex is a per-individual property, so track-vote is the headline number (per-crop in parentheses):

version rgb thermal matched
non_geo 0.758 (0.728) 0.702 (0.671) 0.688 (0.712)
geo_1k 0.756 (0.734) 0.719 (0.689) 0.721 (0.706)
geo_2k 0.738 (0.750) 0.671 (0.628) 0.685 (0.680)

RGB is strongest β€” sex cues are visual (antlers, body shape) and thermal washes them out. Note that unlike the occlusion task, matched does not help here: fusing a weak thermal signal with a stronger RGB one drags the result below RGB alone. If you use one model from this repo, use rgb.

Limitations

These are weak classifiers. Treat them as a research signal, not a label. Best track-vote balanced accuracy is 0.758 against a 0.5 chance baseline, and the worst configuration (geo_2k/thermal, 0.671) is not far above coin-flip territory. Roughly one in four track-level calls is wrong even in the best configuration.

  • Aggregate to the track, not the crop. Single-crop predictions are noisier than the headline numbers; the track-vote column is what those numbers describe.
  • Do not use these for population sex-ratio estimates without explicit error modelling. At this accuracy, and with juveniles merged into female_juvenile, a naive ratio will be badly biased.
  • Clear crops only (see above) β€” occluded input is out-of-distribution.
  • Feature-space bound. Tied to the exact DINOv3 ViT-H+ features they were trained on; a different backbone or preprocessing silently degrades accuracy while still returning confident-looking probabilities.
  • Match the model to the projection. The three versions are not interchangeable.
  • Red deer, Austrian forest habitats. Other species, regions, seasons, or flight altitudes are unverified.

Training data

The BAMBI UAV dataset β€” 389 paired RGB/thermal aerial sequences from dual-sensor nadir UAVs over Austrian forest habitats, ~5,100 annotated animal tracks. Evaluation uses GroupKFold by track, so frames from one animal never straddle the train/test split β€” without this, near-duplicate consecutive frames would inflate accuracy.

Citation

Please cite the underlying BAMBI dataset β€” the authors ask that work building on it cite their CV4Animals workshop paper (Praschl et al., 2026). See bambi-eco/Dataset for the current citation.

Licensing note on the DINOv3 backbone

These lightweight heads are released under the MIT license. They are only useful on top of DINOv3, which Meta distributes under its own separate, non-MIT terms. MIT here covers these heads only and grants no rights to DINOv3 β€” review its license yourself before use.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Collection including cpraschl/bambi-red-deer-sex-classifiers