--- license: apache-2.0 tags: - geolocation - image-regression - dinov3 - street-view - jakarta - computer-vision library_name: pytorch language: - id datasets: - nadh0708/JKTSV-primary --- # Img2LocJakarta — DINOv3 street-view geolocation for Jakarta Predict **(latitude, longitude)** of a Google Street View perspective crop taken on a Jakarta road. A frozen **DINOv3 ViT-L/16** backbone extracts features; a U-shaped MLP head regresses a local flat-earth (x, y) offset which is converted to WGS-84 degrees. | | | |---|---| | **Input** | RGB street-level image (any size, resized to 224×224) | | **Output** | `{"lat": float, "lon": float}` — WGS-84 degrees | | **Scope** | Trained on Jakarta (5 administrative cities, motorway + primary roads) | | **Backbone** | DINOv3 ViT-L/16 — frozen; only the regression head was trained | ## Quick Start ```bash pip install torch torchvision pillow huggingface_hub safetensors ``` ```python from huggingface_hub import hf_hub_download import sys, os # Download the inference code for fname in ["inference.py", "modeling_geotag.py"]: hf_hub_download("nadh0708/Img2LocJakarta", fname, local_dir=".") from inference import GeoTagPredictor predictor = GeoTagPredictor("nadh0708/Img2LocJakarta") print(predictor.predict("street.jpg")) # {'lat': -6.2261, 'lon': 106.8123} # Batched print(predictor.predict(["a.jpg", "b.jpg"])) ``` ### From a local checkpoint ```python predictor = GeoTagPredictor("modelD_40e_2.pth") ``` ### CLI ```bash python inference.py nadh0708/Img2LocJakarta street.jpg ``` ### Low-level API ```python import torch from torchvision import transforms from PIL import Image from modeling_geotag import DinoGeoRegressor model = DinoGeoRegressor.from_pretrained("nadh0708/Img2LocJakarta") model.eval() tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) x = tf(Image.open("street.jpg").convert("RGB")).unsqueeze(0) lonlat = model.predict_lonlat(x) # tensor([[lon, lat]]) ``` ## Architecture ``` DinoGeoRegressor ├─ encoder : DINOv3 ViT-L/16 (frozen, patch_size=16) │ last block tokens (B, 201, 1024) ──flatten──► (B, 205824) │ tokens: 196 patch + 1 CLS + 4 storage └─ head : UNet-MLP 205824 ──enc1──► 512 ──enc2──► 512 ↓ bottleneck 512 512 ◄──dec2── 1024 (cat) 512 ◄──dec1── 1024 (cat) skip connections from enc1, enc2 ↓ out linear 2 (x_metres, y_metres) ──coord_convert──► (lon, lat) degrees ``` The output is a flat-earth offset in metres relative to Jakarta origin `(lon 106.828320, lat -6.227468)`, inverted to degrees at inference time. The published checkpoint bundles the **frozen backbone + trained head** — no separate LVD-1689M weight download is required. Only the DINOv3 **code** is pulled from `torch.hub` on first load. ## Performance Evaluated on **137,173 test samples** (Google Street View perspective crops of Jakarta roads, 8 headings: 0°–315° in 45° steps). | Epoch | Mean error | Median error | % < 1 km | % < 5 km | % < 25 km | |-------|-----------|-------------|---------|---------|---------| | e15 | 3.313 km | 1.966 km | 25.6% | 79.4% | 100.0% | | e35 | 3.158 km | 1.588 km | 33.8% | 80.2% | 100.0% | | **e40** | **2.743 km** | **1.272 km** | **42.2%** | **83.1%** | **100.0%** | Error is geodesic (haversine) distance between the predicted and true GPS coordinate. **This checkpoint is epoch 40** (`modelD_40e_2.pth`). ## Training | Hyperparameter | Value | |---|---| | Backbone | DINOv3 ViT-L/16 (`facebookresearch/dinov3`) — frozen | | Head | UNet-MLP, hidden 512, skip connections | | Optimiser | AdamW | | Learning rate | 1e-3 | | Weight decay | 1e-4 | | Batch size | 64 | | Epochs | 40 | | Input size | 224 × 224 | | Loss | MSE on flat-earth (x, y) metres | **Dataset:** [`nadh0708/JKTSV-primary`](https://huggingface.co/datasets/nadh0708/JKTSV-primary) — 685,848 perspective crops (548,675 train / 137,173 test) sampled along Jakarta motorway and primary road segments at 50 m intervals, 8 headings. ## Preprocessing Images are resized to **224 × 224** and normalised with ImageNet statistics: ```python transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ``` `GeoTagPredictor` applies this automatically. If you call `DinoGeoRegressor` directly, apply the transform before passing `pixel_values`. ## Limitations - **Geographically restricted to Jakarta.** Out-of-domain images return coordinates near the projection origin. - Trained on 2023+ Street View panoramas projected to 8 fixed headings (0°, 45°, 90°, 135°, 180°, 225°, 270°, 315°). - Backbone is frozen; only the 3 M-parameter regression head was trained. - Hard examples (48% of the test set, defined as error > 1 km across all measured epochs) tend to cluster in peripheral and coastal areas with low visual distinctiveness. ## Files | File | Purpose | |---|---| | `modeling_geotag.py` | Model classes + `from_pretrained` loader + coordinate conversion | | `inference.py` | `GeoTagPredictor` high-level API + CLI | | `model.safetensors` | Full state dict (frozen backbone + trained head, ~1.2 GB) | | `requirements.txt` | Runtime dependencies | ## Citation If you use this model or the dataset, please cite: ```bibtex @misc{img2locjakarta2025, author = {Nadhif}, title = {Img2LocJakarta: DINOv3 Street-View Geolocation for Jakarta}, year = {2025}, url = {https://huggingface.co/nadh0708/Img2LocJakarta} } ``` ## License This model is released under the **Apache License 2.0**, consistent with the DINOv3 backbone license. See `LICENSE` for details.