DINOv3 geo pre-filter

A binary image classifier that runs before GeoCLIP geo-estimation. It decides whether an image shows a place that can plausibly be located, such as streets, buildings, landscape or landmarks. Images without one go no further, so GeoCLIP never assigns a location to them. That covers portraits and selfies, close-ups, screenshots, logos, documents and other graphics.

Files

File Backbone Size Input batch sha256
25.09.25-dinov3_vitb16_geo_filter.onnx DINOv3 ViT-B/16 (frozen) + MLP head 346 MB dynamic 49bb52b6d5bc22153cd2a4f433ac2ec00ea9a0e144712a73d45501e907119599
04.10.25-dino_v3_linear.onnx DINOv3 + linear head (previous version) 87 MB 1 5ca53cc5a07d5b3102c6d68e85fdb8670f3c8e94622fa48964f61bf5ab54efb3

Both files have the same interface, so either is a drop-in replacement for the other.

Interface

  • Input pixel_values: float32 [batch, 3, 224, 224]. RGB, resized to 224ร—224 without keeping aspect ratio or cropping (bilinear), scaled to [0, 1], then normalised with ImageNet mean [0.485, 0.456, 0.406] and std [0.229, 0.224, 0.225].
  • Output classification: float32 [batch, 2] logits. Index 0 means usable for geo-estimation, index 1 means reject.
  • Decision: take the argmax. The operating threshold is built into the head's bias, so callers need no threshold of their own. Apply a softmax for probabilities.
  • ONNX opset 17. Tested with onnxruntime 1.22.0.
import numpy as np
import onnxruntime as ort
from PIL import Image

session = ort.InferenceSession("25.09.25-dinov3_vitb16_geo_filter.onnx")
image = Image.open("photo.jpg").convert("RGB").resize((224, 224), Image.BILINEAR)
x = (np.asarray(image, np.float32) / 255 - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225]
logits = session.run(None, {"pixel_values": x.transpose(2, 0, 1)[None].astype(np.float32)})[0][0]
usable_for_geo = int(np.argmax(logits)) == 0

Model (25.09.25)

  • Backbone: facebook/dinov3-vitb16-pretrain-lvd1689m, frozen.
  • Features: the CLS token concatenated with the mean of the patch tokens (register tokens excluded), 1536 values.
  • Head: dropout โ†’ linear 1536โ†’512 โ†’ GELU โ†’ dropout โ†’ linear 512โ†’2. Feature standardisation is folded into the first layer's weights.
  • Trained with class-weighted cross-entropy and AdamW (lr 3e-4, weight decay 0.05, cosine schedule), early-stopped on validation average precision for the usable class.

Training data

  • 40,000 de-duplicated images sampled at random from an internal collection of user-supplied images. The images are not published.
  • Each image was labelled by SmolVLM2-2.2B-Instruct. It answered two yes/no questions, read from next-token probabilities:
    • whether the photo shows enough of a real place to guess where in the world it was taken;
    • whether a person, face, animal or object fills most of the picture.
  • A logistic regression fitted on 200 hand-labelled images combined the two answers into a score with two cut-offs. Images between the cut-offs were dropped as uncertain.
  • 36,163 labelled images remained, split by image hash:
Split Images Usable for geo
train 25,307 2,185
validation 5,477 492
test 5,379 439

Evaluation

VLM-labelled test split (5,379 images, 439 usable): ROC AUC 0.995, average precision 0.963. At the built-in threshold, 89.6% of the images it passes are usable, and it keeps 90.7% of the usable images.

Hand-labelled held-out set: 300 random images, never used for training or tuning. Ambiguous images are excluded from these counts.

04.10.25 (previous) 25.09.25
Usable images kept (of 10) 10 8
Reject images passed (of 264) 143 9
Passed images that are usable 6.5% 47%

Fresh unlabelled images (3,000, never seen in training): the previous filter passes 60.1% of them and the new one 11.9%.

Known weakness: people posed outdoors with some scenery behind them, such as group photos or a selfie on a street, are the most common remaining false passes. Dense street crowds can also pass.

Runtime

Per image, batch 1, onnxruntime 1.22.0 on CPU (AMD EPYC 7401P), median:

Threads 04.10.25 25.09.25
6 97 ms 260 ms
24 76 ms 177 ms

The new backbone needs about 4ร— the compute of the previous one. GPU latency was not measured.

License

Both files contain DINOv3 weights, so they are distributed under the DINOv3 License.

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

Model tree for matanrich/dinov3_onnx_classifier