Tree counting with satellite imagery

June 30, 2026 · View on GitHub

Tree counting with satellite imagery

arXiv paper Dataset Models

This repository contains the code and data for the upcoming ECCV26 paper, Counting Trees from Satellite Imagery with Noisy Supervision:

  • TinyTrees — a multi-sensor tree counting benchmark with point-level annotations across three geographic regions and satellite sensors.
  • TreeMatch — a training method for tree density estimation that leverages optimal transport to learn from both strong and weak annotations.

TreeMatch

TinyTrees Dataset

TinyTrees provides georeferenced satellite imagery with per-tree point annotations across three regions, sensors, and resolutions:

RegionSensorGSDTrain (strong)Train (weak)TestTotal treesTotal area
RwandaPlanetScope3.0 m231 tiles / 309k trees73 tiles / 3.4M trees645 tiles / 237k trees3.9M283 km²
ChinaGaofen-20.8 m446 tiles / 55k trees16,364 tiles / 7.7M trees2,189 tiles / 70k trees7.8M2,344 km²
FranceSPOT-61.5 m492 tiles / 11k treesCHM-derived (via Open-Canopy)493 tiles / 11k trees22k0.7 km²

Each tile is a 5-band GeoTIFF (4 spectral bands + 1 binary validity mask). Point annotations are stored in a single GeoPackage per split with a tile column linking each point to its image.

SPOT weak labels are CHM-derived pseudolabels bundled in spot/train_weak/. The corresponding SPOT-6 imagery is not redistributed; to use it, download the Open-Canopy dataset and point spot_imagery_root at its canopy_height/ directory:

canopy_height/
├── 2021/spot/compressed_pansharpened_*.tif
├── 2022/spot/compressed_pansharpened_*.tif
└── 2023/spot/compressed_pansharpened_*.tif

Add to conf/local/local.yaml:

spot_imagery_root: /path/to/open-canopy/datasets/canopy_height

Download

The dataset is hosted on HuggingFace and mirrored at https://sid.erda.dk/cgi-sid/ls.py?share_id=ET2ix678WL. PlanetScope and Gaofen imagery are under the CC BY-NC 4.0 license: usage is reserved for research and education only.

tinytrees/
├── ps/              # PlanetScope (Rwanda)
│   ├── train_strong/   # *.tif + points.gpkg
│   ├── train_weak/
│   └── test/
├── gf/              # Gaofen-2 (China)
│   ├── train_strong/
│   ├── train_weak/
│   └── test/
└── spot/            # SPOT-6 (France)
    ├── train_strong/
    ├── train_weak/  # pseudolabels only (imagery from Open-Canopy)
    └── test/

Loading data

from data.ps import PlanetScopeStrong
from data.gf import GaofenStrong
from data.spot import SPOTStrong

# Each dataset returns (image, valid_mask, count_map)
# image: (C+1, H, W) float tensor — C normalized bands + 1 validity channel
# valid_mask: (1, H, W) binary tensor
# count_map: (1, H, W) sparse 0/1 tensor with tree locations

ds = PlanetScopeStrong(imsize=64, split="train_strong", root="/path/to/tinytrees/ps")
ds = GaofenStrong(imsize=64, split="train_strong", root="/path/to/tinytrees/gf")
ds = SPOTStrong(imsize=64, split="train_strong", root="/path/to/tinytrees/spot")

TreeMatch

TreeMatch is an optimal-transport-based training method for tree density estimation that supports mixed supervision from strong (expert) and weak (e.g. CHM-derived) point annotations. It uses unbalanced optimal transport to match predicted density maps to point annotations, with a slack mechanism to down-weight weak labels.

Pretrained models

Pretrained TreeMatch checkpoints for all three sensors are available on HuggingFace.

from hub_model import UNetR50
import torch

model = UNetR50.from_pretrained("dgominski/TinyTrees", subfolder="ps")   # Rwanda / PlanetScope
# model = UNetR50.from_pretrained("dgominski/TinyTrees", subfolder="gf")   # China / Gaofen-2
# model = UNetR50.from_pretrained("dgominski/TinyTrees", subfolder="spot") # France / SPOT-6
model.eval()

# Input: (B, 5, H, W) — RGBI + 1 binary validity mask (last channel)
x = torch.randn(1, 4, 128, 128)
valid = torch.ones(1, 1, 128, 128)            # 1 = valid pixel, 0 = nodata
density = model(torch.cat([x, valid], dim=1)) # (B, 1, H, W), trees/pixel

Training

Training is configured via Hydra. The main entry point is train.py:

# Train TreeMatch on PlanetScope with strong labels only
python train.py dataset=ps model=treematch train.strong_ratio=1.0 model.lr=8e-05

# Train with 80% strong + 20% weak labels
python train.py dataset=ps model=treematch train.strong_ratio=0.8 model.lr=8e-05

# Train density regression baseline
python train.py dataset=ps model=density_regressor

# Train DM-Count baseline
python train.py dataset=ps model=dm_count

Override dataset paths for your machine in conf/local/local.yaml:

# @package _global_
dataset:
  root: ${dataset_roots.${hydra:runtime.choices.dataset}}
dataset_weak:
  root: ${dataset_roots.${hydra:runtime.choices.dataset_weak}}
dataset_roots:
  ps: /path/to/tinytrees/ps
  gf: /path/to/tinytrees/gf
  spot: /path/to/tinytrees/spot
  ps_weak: /path/to/tinytrees/ps/train_weak
  gf_weak: /path/to/tinytrees/gf/train_weak
  spot_weak: /path/to/tinytrees/spot/train_weak

# SPOT weak labels use imagery from Open-Canopy (not bundled in TinyTrees)
spot_imagery_root: /path/to/open-canopy/canopy_height

Available models

ModelConfigDescription
TreeMatchmodel=treematchUnbalanced OT density matching with slack for weak labels
Density Regressionmodel=density_regressorGaussian density map regression (MSE loss)
DM-Countmodel=dm_countBalanced OT with total variation loss
CenterNetmodel=centernetKeypoint detection with heatmap regression
P2PNetmodel=p2pPoint-to-point matching

Available backbones

BackboneConfigArchitecture
ResNet-50 U-Netbackbone=resnet50segmentation_models_pytorch U-Net with ResNet-50 encoder
Swin Transformerbackbone=swintSwinV2-Small with FPN decoder
ViTbackbone=vitVision Transformer with FPN decoder

Rwanda-Tanzania application

We trained a PlanetScope+Sentinel-1+Sentinel-2 (20-band composite) model to count trees in Rwanda and Tanzania.

Reference

@inproceedings{gominski2026counting,
  title     = {Counting Trees from Satellite Imagery with Noisy Supervision},
  author    = {Gominski, Dimitri and Mugabowindekwe, Maurice and Xu, Qiue and Tong, Xiaowei and Brandt, Martin and Le, Hieu and Fensholt, Rasmus and Samaras, Dimitris and Landrieu, Loic},
  booktitle = {European Conference on Computer Vision},
  year      = {2026}
}

,