Skip to content
Pedro Braiti
← All projects

Autonomous systems

Satellite-based solar panel detection

You draw an area on the map and the system returns, marked, every solar panel on that roof — in any city covered by satellite imagery.

Satellite-based solar panel detection
Measured

0.887

the real mAP, after I found out my test set was contaminated

Conditions
240 of the 270 images in the test set came from training · the memorized number was 0.997
Role
Author of the model and the proof of concept
Context
Started as my own project; Datlaz adopted it and turned it into a product
Period
2026
Status
Adopted as a product by the company
The raw satellite image, as it arrives
The raw satellite image, as it arrives
The same area after the model, every panel marked with its confidence
The same area after the model, every panel marked with its confidence
From drawing the area to detection, in the running system

The problem

Knowing how many roofs in a city already have solar power is an expensive question: someone has to look at them, one by one. I wanted to answer it from satellite imagery, at scale, for any region.

The user draws a free-form polygon on the map. The system covers the area with a grid of tiles measured in meters — not in pixels, so the scale doesn’t change with latitude — captures each tile, runs the detector, reprojects the boxes back to lat/long and merges everything with global non-maximum suppression. The color of each detection shows the confidence.

The decision that changed the project

The first model gave me mAP 0.997. A number like that is not a reason to celebrate, it’s a reason to be suspicious.

I went to check the “frozen” test set: 240 of the 270 images were already in training. The model hadn’t learned to detect panels — it had memorized the answers to the exam. I redid the split and the real number showed up: 0.887.

Losing 11 points of a metric in a report is embarrassing. Finding that out after the model was already in production would have been much worse.

A second detail cost me hours: post-processing needs per-class sigmoid, not softmax. Softmax forces the classes to compete with each other and inference degrades quietly, without a single error in the log.

The result

I trained the detector on about 1,100 images from 10 areas across 8 Brazilian cities, at 6.7 cm per pixel. Datlaz, the company I contract for, adopted the model and turned it into a product.

The pipeline responds as a stream, with a real per-tile progress bar, and tolerates partial failure: one tile that fails to load doesn’t bring down the whole analysis.

Where it runs

The model is served by the company itself, in ONNX, not by a paid detection API. The reason is arithmetic: on the hosted service every tile burned a credit, and sweeping a whole city means tens of thousands of tiles. Serving the model ourselves, the inference cost disappears and only the satellite image download remains — the review pilot in Brasília came out at roughly US$0.20 per run.

It isn’t an ideological choice; it’s what makes the numbers work at city scale. The same goes for training: the model in production cost $4.90 of rented GPU at $1.05/h, over 4 hours and 40 minutes.

It runs in production on the company’s cluster, behind single sign-on, serving a 115 MB model — about 0.1 second per tile on GPU, with CPU as the fallback.