Land Cover Classification
Land cover classification labels every overhead pixel as forest, water, crop, or urban, producing the base map other monitoring tasks build on.
Why Does This Exist?
Cities need urban growth numbers, insurers need flood-plain maps, and climate models need forest area. All start from one base layer stating what covers each pixel. Land cover classification exists to produce that layer repeatably from satellite stacks, with classes stable enough to compare year to year. A single RGB photo cannot separate wheat from meadow; seasons plus infrared can.
For orbital preprocessing see satellite imagery analysis and for masks see semantic segmentation.
Think of It Like This
Sorting laundry by season, not color alone
White shirts and white sheets look alike in one glance, but sheets appear only on change day. Crops and meadows look alike in June but diverge by September. The model sorts with the calendar attached. The analogy stops at laundry: pixels also need neighborhood texture, not just dates.
How It Actually Works
Inputs stack bands across seasons, for example spring plus summer NIR and red. A segmentation head predicts classes such as forest, water, cropland, grassland, and urban per pixel. Here accuracy is reported per class because 90 percent overall can hide 40 percent on rare urban. Spatial context from surrounding pixels cleans salt-and-pepper noise. Labels come from surveys and prior maps, harmonized to one taxonomy before training.
Worked example
A chip holds 6,000 forest, 2,000 water, 1,500 crop, and 500 urban pixels. A single-date RGB model scores 0.92 overall but only 0.45 on urban, missing 275 urban pixels. Adding summer NIR lifts urban to 0.78, recovering 165 pixels, because rooftops and crops diverge in infrared. The rare class decides usefulness, not the mean.
Code
This snippet scores per-class recall.
import numpy as np
def recall_per_class(truth: np.ndarray, pred: np.ndarray, k: int) -> list[float]: out = [] for c in range(k): tp = int(((truth == c) & (pred == c)).sum()) fn = int(((truth == c) & (pred != c)).sum()) out.append(round(tp / max(1, tp + fn), 2)) return out
truth = np.array([0]*60 + [1]*20 + [2]*15 + [3]*5)pred = truth.copy()pred[95:] = 0 # miss all 5 urbanprint(recall_per_class(truth, pred, 4)) # -> [1.0, 1.0, 1.0, 0.0]Overall 0.95 hides 0.0 urban recall. Report per class.
Watch Out For
Taxonomies that shift between years
Forest in one survey means trees over 5 m, in another over 2 m, so retraining on mixed labels teaches contradiction. The symptom is flickering forest edges with no ground change. Harmonize classes to one definition first, and version the taxonomy with the model.
The Quick Version
- Land cover is the base map for monitoring tasks.
- Seasons plus infrared separate classes RGB confuses.
- Rare urban and water decide quality, not overall accuracy.
- Spatial context removes speckle from per-pixel votes.
- Fix one taxonomy before training across years.