Multi-temporal satellite change detection is the analytical discipline of comparing satellite observations captured across two or more time horizons (e.g., Year 2014 vs Year 2024) to quantify the spatial extent, rate, and nature of environmental transformations. Common applications include tracking tropical deforestation, monitoring post-wildfire burn recovery, measuring glacial retreat, and mapping illegal urban expansion. Successful change detection requires rigorous pre-processing: multi-date images must be accurately co-registered, calibrated to surface reflectance, and standardized to minimize false changes caused by differing atmospheric conditions or sun angles.
š Prerequisites
- Two cloud-free satellite scenes over the same geographic area from different dates.
- Calibrated Surface Reflectance (Level-2A) datasets.
- QGIS 3.34+ LTR installed.
š ļø Technical Environment
Required Software: QGIS Raster Calculator / Python (Recommended: 3.34+ LTR)
Practice Dataset: Multi-year Landsat Surface Reflectance (2014 vs 2024)
Source Portal: USGS EarthExplorer
CRS / Format: UTM (GeoTIFF)
Step-by-Step Workflow & Methodological Execution
Module 1: Geometric Co-Registration & Radiometric Normalization
Before running change algorithms, eliminate artificial sources of difference: 1. Geometric Co-Registration: Even a 1-pixel shift between Date 1 and Date 2 produces false change rings along all road edges and river boundaries. Verify alignment by toggling layers; if shifted, execute Coregistration via SAGA or AROSICS. 2. Radiometric Normalization: Never use raw Digital Numbers (DN) or Top-of-Atmosphere (TOA) images! Differences in atmospheric aerosol thickness and seasonal solar elevation angles will mimic false vegetation loss. Always use Bottom-of-Atmosphere Surface Reflectance (BOA) products.
Module 2: Image Differencing & Index Subtraction (Delta-NDVI)
The most straightforward technique is Continuous Index Differencing: 1. Calculate vegetation index for Date 1 ($NDVI_{2018}$) and Date 2 ($NDVI_{2024}$). 2. Compute Differential Raster in Raster Calculator: $$\Delta NDVI = NDVI_{2024} - NDVI_{2018}$$ 3. Statistical Thresholding: In the resulting difference raster: ⢠Values near 0: Stable land cover (no significant change). ⢠Highly Negative Values (< -0.25): Significant loss of green vegetation (deforestation, agricultural harvest, or urban clearing). ⢠Highly Positive Values (> +0.25): Significant vegetation gain (afforestation, agricultural green-up, or ecological recovery).
Module 3: Post-Classification Comparison & Transition Matrices
Continuous index differencing tells you THAT a change occurred, but not WHAT changed into WHAT. To solve this, deploy Post-Classification Comparison (PCC): 1. Classify both Date 1 and Date 2 independently into discrete LULC maps (e.g., 1: Water, 2: Forest, 3: Urban, 4: Agriculture). 2. Matrix Calculation: In Raster Calculator, execute a unique multiplier expression: $$Transition = (LULC_{2018} \times 10) + LULC_{2024}$$ 3. Decode Results: A pixel with value `23` represents class 2 (Forest) transitioning to class 3 (Urban). A pixel with `22` represents stable, unchanged Forest. This produces a complete Land-Use Transition Matrix allowing precise quantification of deforestation acreage.
ā ļø Common Errors & Troubleshooting
ā False change detected across agricultural fields
š” Resolution: Seasonal phenology differences! Always select satellite imagery captured in the same month/season of each year.
ā Pixel misalignment causes pseudo-edge changes along roads
š” Resolution: Ensure both rasters are co-registered with sub-pixel alignment using GDAL Warp or Georeferencer.
š” Expert Tips & Best Practices
- Classify Delta-NDVI into three distinct bins: Vegetation Loss (dNDVI < -0.2), Stable (-0.2 to +0.2), and Regrowth (dNDVI > +0.2).
- Always inspect atmospheric correction levels (prefer L2 Surface Reflectance over L1 Top of Atmosphere).
š Python Rasterio Delta-NDVI Change Detection Pipeline
import rasterio
import numpy as np
# Open pre-event and post-event NDVI rasters
with rasterio.open("ndvi_2018.tif") as ds1:
ndvi1 = ds1.read(1)
profile = ds1.profile
with rasterio.open("ndvi_2024.tif") as ds2:
ndvi2 = ds2.read(1)
# Calculate Delta-NDVI (Change)
delta_ndvi = ndvi2 - ndvi1
# Define change classification threshold
# Standard deviation thresholding: changes beyond 2 sigma
mean_diff = np.nanmean(delta_ndvi)
std_diff = np.nanstd(delta_ndvi)
# Create 3-class change mask:
# 1: Significant Loss, 2: Stable, 3: Significant Gain
change_mask = np.zeros(delta_ndvi.shape, dtype=np.uint8)
change_mask[delta_ndvi < (mean_diff - 1.5 * std_diff)] = 1 # Loss
change_mask[np.abs(delta_ndvi - mean_diff) <= (1.5 * std_diff)] = 2 # Stable
change_mask[delta_ndvi > (mean_diff + 1.5 * std_diff)] = 3 # Gain
# Export classified change map
profile.update(dtype=rasterio.uint8, count=1, nodata=0)
with rasterio.open("vegetation_change_map.tif", "w", **profile) as dst:
dst.write(change_mask, 1)
print("Change detection processing and classification complete.")