Remote Sensing with Machine Learning: A Beginner's Guide

Suman SharmaSuman Sharma

Satellite imagery is one of the most useful free datasets in climate work, and one of the most confusing for developers. Images have 13 bands instead of 3. Pixels are 10 metres wide. Clouds ruin everything.

This is the guide I'd give a web developer who wants to do their first useful thing with satellite data.

What a satellite image actually is

A multispectral image is a stack of grids. Each grid (a band) measures reflected light at a specific wavelength. Sentinel-2, the workhorse for environmental ML, has 13 bands at 10, 20, or 60 m resolution, including visible, near-infrared (NIR), red-edge, and shortwave infrared (SWIR).

Plants reflect a lot of NIR and absorb red. Water absorbs NIR. Bare soil sits in between. Those differences are what ML learns.

Indices: features you get for free

Before ML, try a spectral index. The most famous is NDVI:

NDVI = (NIR - Red) / (NIR + Red)

High NDVI means healthy vegetation, near zero means bare soil or built-up areas, and negative means water. Indices make strong features for models too.

Getting data

Options, from easiest:

  • Google Earth Engine: huge catalogue, compute in the cloud, free for research and non-commercial use.
  • Microsoft Planetary Computer / AWS Open Data: cloud-optimised GeoTIFFs you can read with Python.
  • Copernicus Data Space Ecosystem: the official Sentinel source.

Use cloud masks (Sentinel-2 provides a scene classification layer) or build cloud-free composites by taking the median of many images over a season.

A first classifier

The classic first project is land-cover classification: label pixels as water, forest, cropland, or urban.

import numpy as np import rasterio from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report # Stack of bands for one cloud-free composite: shape (bands, height, width) with rasterio.open("composite.tif") as src: image = src.read().astype("float32") # Labels raster: 0 = unlabeled, 1 = water, 2 = forest, 3 = crop, 4 = urban with rasterio.open("labels.tif") as src: labels = src.read(1) bands, h, w = image.shape X = image.reshape(bands, -1).T y = labels.reshape(-1) # Spatial split: left half of the scene trains, right half tests (see below) cols = np.tile(np.arange(w), h) is_train = cols < w // 2 mask = y > 0 X, y, is_train = X[mask], y[mask], is_train[mask] # Add NDVI as a feature (assuming band order: B2, B3, B4, B8, ...) red, nir = X[:, 2], X[:, 3] ndvi = (nir - red) / (nir + red + 1e-6) X = np.column_stack([X, ndvi]) X_train, y_train = X[is_train], y[is_train] X_test, y_test = X[~is_train], y[~is_train] clf = RandomForestClassifier(n_estimators=200, n_jobs=-1) clf.fit(X_train, y_train) print(classification_report(y_test, clf.predict(X_test)))

The trap: spatial leakage

If you randomly split pixels into train and test, neighbouring pixels end up on both sides. They're nearly identical, so accuracy looks amazing, then collapses on a new region.

Split by area: train on some tiles or regions, test on others. This is the single most common mistake in beginner remote sensing ML, and it shows up in published papers too.

Where to go next

Frequently asked questions

Do I need deep learning for remote sensing?

No. Many useful tasks, like land-cover classification from spectral bands, work well with random forests or gradient boosting on pixel features. Deep learning helps with spatial patterns like buildings, roads, and fields.

Where can I get free satellite imagery?

Sentinel-2 and Landsat imagery is free. You can access it through the Copernicus Data Space Ecosystem, USGS EarthExplorer, Google Earth Engine, or cloud-hosted archives such as Microsoft Planetary Computer and AWS Open Data.

What is spatial data leakage?

When training and test pixels come from the same area, they're highly correlated, so the model looks more accurate than it will be on new places. Split data by region or tile, not by random pixel.

Contact

Get in Touch

Want to call me? book a call and I'll respond in the available time slot. I will ignore all soliciting.