Introduction
Blue Marble Ships is a family of five ship detection models for Sentinel-2 imagery, from 32k to 13.5M parameters. Every model is under 15M parameters and runs fully in int8 on the Qualcomm Dragonwing QCS8275 NPU (Arduino VENTUNO Q).

Highlights
- 86.6 AP on our Sentinel-2 vessel test split (tiles unseen in training), 82.4 AP on April 2026 scenes; ship length mean absolute error 17 m (medium).
- 81.2 AP at 32k parameters; ship position and length in one pass.
- 3.6 ms per 256x256 tile (medium) on the Arduino VENTUNO Q NPU, int8, every layer on the NPU.
Model Downloads
| Model | Params | int8 size | VENTUNO Q latency | Ops on NPU | Ships test (ours): fp32 / int8 | Download |
|---|---|---|---|---|---|---|
| Blue-Marble-Ships-pico | 0.032M | 0.14 MB | 1.0 ms | 62/62 | 81.2 / 78.7 | pico/ |
| Blue-Marble-Ships-nano | 0.19M | 0.36 MB | 1.0 ms | 76/76 | 84.3 / 81.6 | nano/ |
| Blue-Marble-Ships-tiny | 1.26M | 1.62 MB | 1.3 ms | 117/117 | 82.7 / 82.0 | tiny/ |
| Blue-Marble-Ships-small | 2.4M | 2.86 MB | 1.5 ms | 130/130 | 80.8 / 78.3 | small/ |
| Blue-Marble-Ships-medium | 13.5M | 14.58 MB | 3.6 ms | 224/224 | 86.6 / 86.2 | medium/ |
Each size folder contains:
| File | Use |
|---|---|
model_qcs8275.dlc |
Compiled for the Qualcomm QCS8275 NPU (QNN runtime): runs as-is on the Arduino VENTUNO Q |
model_int8.onnx |
int8 ONNX, the source of the compiled model; ONNX Runtime or your own NPU toolchain |
model_fp32.onnx |
Full precision ONNX (CPU / GPU); the scores below are for this model |
model.safetensors + config.json |
PyTorch weights for fine-tuning, with models.py in this repository |
ventuno_q_profile.json |
On-device profile on the Arduino VENTUNO Q (Qualcomm AI Hub): the compute unit of every op, total latency, memory, load times |
Scores in this table are for the exact files in this repository, re-scored on our held-out ships test split (332 ships). Latency and ops on NPU: on-device profile of model_qcs8275.dlc on an Arduino VENTUNO Q (Qualcomm AI Hub).
Evaluation Results
Models under 15M parameters
| Model | Params | Ships test (ours) (AP) | Ships Apr-2026 (ours) (AP) |
|---|---|---|---|
| Blue Marble pico | 0.032M | 81.2 | 81.7 ± 0.6 |
| Blue Marble nano | 0.19M | 84.3 | 80.1 ± 0.5 |
| Blue Marble tiny | 1.26M | 82.7 | 80.7 ± 0.7 |
| Blue Marble small | 2.4M | 80.8 | 82.2 ± 0.5 |
| Blue Marble medium | 13.5M | 86.6 | 82.4 ± 0.8 |
Blue Marble, test: the released checkpoint in this repository; Apr-2026: mean ± std over 3 training runs. Both are our own splits, not public leaderboards. Other models: published numbers, with inputs noted where they differ from ours (our models use Sentinel-2 only); * = trained and evaluated by us on the same data and split (parameter count includes our decoder). Best per column among models under 15M in bold. Labels: Global Fishing Watch Sentinel-2 vessel detections (AIS-matched, plus high-confidence detections without AIS); ambiguous detections are ignored. Test (332 ships) and Apr-2026 (April 2026 scenes) use tiles not seen in training. Ship length error: mean absolute error of the predicted length for matched ships. AP: detections are local maxima of the heatmap, a detection is correct if its centre is within 50 m of an unmatched label, all-point interpolated average precision.



Quickstart
import numpy as np, onnxruntime as ort
MEAN = np.array([2357.089, 2137.385, 2018.788, 2082.986, 2295.651, 2854.537, 3122.849, 3040.560, 3306.481, 1473.847, 506.070, 2472.825, 1838.929], np.float32)[:, None, None]
STD = np.array([1624.683, 1675.806, 1557.708, 1833.702, 1823.738, 1733.977, 1732.131, 1679.732, 1727.26, 1024.687, 442.165, 1331.411, 1160.419], np.float32)[:, None, None]
def prepare(dn): # dn: Sentinel-2 L1C digital numbers, [13, H, W], H and W multiples of 16
dn = np.clip(dn - 1000, 0, None) # only for products from 2022 on (processing baseline 04.00+)
return ((dn - MEAN) / STD)[None].astype(np.float32)
sess = ort.InferenceSession("tiny/model_int8.onnx")
out = sess.run(None, {"image": prepare(dn)})[0]
PyTorch:
import json, torch
from safetensors.torch import load_file
from models import BlueMarble # models.py from this repository
cfg = json.load(open("tiny/config.json"))
model = BlueMarble(cfg["arch"], cfg["num_classes"]).eval()
model.load_state_dict(load_file("tiny/model.safetensors"))
- Input: 13 Sentinel-2 L1C bands, B01 to B12 including B8A and B10. Missing bands (other sensors): set to 0 after normalisation.
- Output:
[1, 2, H, W]: channel 0 ship-centre heatmap logit (sigmoid; 5x5 local maxima above 0.3 are ships), channel 1 log ship length (length_m = exp(v) * 10).
License
Apache-2.0. Contains modified Copernicus Sentinel data.
Citation
@misc{bluemarble2026,
title = {Blue Marble: Edge Earth Observation Models},
author = {Harsh Kumar},
year = {2026},
url = {https://huggingface.co/Civimatica}
}