The Normalized Difference Vegetation Index (NDVI) is the global benchmark remote sensing index for quantifying photosynthetic vegetation canopy density, agricultural crop vigor, and forestry biomass. Healthy vegetation exhibits a distinct spectral signature: leaf chlorophyll absorbs incoming solar radiation strongly in the visible Red band (~660 nm) for photosynthesis, while the spongy mesophyll cell structure strongly scatters and reflects radiation in the Near-Infrared (NIR) band (~860 nm). By comparing the ratio of absorption to scattering, NDVI isolates live green vegetation from soil, water, and built-up surfaces.
š Prerequisites
- QGIS 3.34+ LTR installed.
- Multispectral satellite scene: Sentinel-2 (B4 Red & B8 NIR) or Landsat 8-9 (B4 Red & B5 NIR).
- Surface Reflectance (Bottom of Atmosphere - BOA) calibrated imagery.
š ļø Technical Environment
Required Software: QGIS Raster Calculator / Python (Recommended: QGIS 3.34 LTR)
Practice Dataset: Sentinel-2 L2A Band 4 (Red) & Band 8 (NIR)
Source Portal: ESA Copernicus Data Space
CRS / Format: WGS 84 / UTM (GeoTIFF (10m Resolution))
š Conceptual Technical Diagram: Spectral Reflectance Signatures & NDVI Formulation
The biological mechanism behind the Normalized Difference Vegetation Index (NDVI) comparing healthy vs stressed plant canopy.
Step-by-Step Workflow & Methodological Execution
Module 1: The NDVI Mathematical Equation and Band Mapping
The NDVI formula normalizes spectral differences onto a standardized scale from -1.0 to +1.0: $$NDVI = \frac{NIR - Red}{NIR + Red}$$ Band assignments vary across satellite sensors: ⢠Sentinel-2 MSI: Band 8 (NIR, 10m) and Band 4 (Red, 10m). ⢠Landsat 8-9 OLI: Band 5 (NIR, 30m) and Band 4 (Red, 30m). Interpreting the NDVI scale: ⢠-1.0 to 0.0: Deep water, reservoirs, snow, and clouds (water absorbs NIR completely). ⢠0.0 to 0.2: Bare soil, sand dunes, asphalt, and rock formations. ⢠0.2 to 0.5: Sparse vegetation, scrubland, senescent crops, and urban lawns. ⢠0.5 to 0.9: Dense, healthy green canopies, agricultural crops at peak vegetative stage, and tropical rainforests.
Module 2: Calculating NDVI with the QGIS Raster Calculator
Execute the calculation directly inside QGIS: 1. Open Raster -> Raster Calculator. 2. Construct Expression: Double-click the raster bands to avoid typo errors. For Sentinel-2: `("sentinel2@8" - "sentinel2@4") / ("sentinel2@8" + "sentinel2@4")` 3. Output Layer: Define output path as `ndvi_sentinel2.tif`. 4. Output Format: Choose GeoTIFF. Verify that the output CRS matches your input bands. 5. Click OK. QGIS processes the floating-point calculation across millions of pixels in seconds.
Module 3: Styling Symbology and Generating Crop Stress Maps
Raw NDVI outputs appear as dull grayscale images. Apply professional cartographic styling: 1. Open Layer Properties -> Symbology. 2. Render Type: Change from 'Singleband gray' to 'Singleband pseudocolor'. 3. Color Ramp: Select the standard RdYlGn (Red-Yellow-Green) color ramp. 4. Mode: Set mode to 'Equal Interval' or 'Quantile' with 5 classes: ⢠< 0.1: Red (Bare Soil / Water) ⢠0.1 - 0.25: Orange (Severe Drought Stress) ⢠0.25 - 0.45: Yellow (Moderate Vigor) ⢠0.45 - 0.65: Light Green (Healthy Vegetation) ⢠> 0.65: Dark Forest Green (Vigorous Canopy) 5. Crop Masking: Use Raster -> Extraction -> Clip Raster by Mask Layer to isolate NDVI values strictly inside your agricultural farm boundary.
ā ļø Common Errors & Troubleshooting
ā NDVI values fall entirely between 0 and 0.001
š” Resolution: Check raster data types; if using integer division (e.g. in Python), cast input bands to floating point (float32) prior to arithmetic.
ā Clouds appearing as high vegetative values
š” Resolution: Clouds can introduce saturated values; apply a SCL (Scene Classification Layer) mask to exclude cloud and shadow pixels.
š” Expert Tips & Best Practices
- Apply the 'RdYlGn' (Red-Yellow-Green) color ramp with Min = -0.2 and Max = 0.8 for intuitive visual crop health interpretation.
- Values between 0.2 and 0.5 indicate sparse/moderate shrub vegetation; values >0.6 represent dense agricultural or tropical canopy.
š Python Rasterio & NumPy NDVI Vectorized Calculation
import rasterio
import numpy as np
# Open Sentinel-2 Band 4 (Red) and Band 8 (NIR)
with rasterio.open("B04_Red_10m.jp2") as red_ds:
red = red_ds.read(1).astype('float32')
profile = red_ds.profile
with rasterio.open("B08_NIR_10m.jp2") as nir_ds:
nir = nir_ds.read(1).astype('float32')
# Calculate NDVI with division protection
np.seterr(divide='ignore', invalid='ignore')
ndvi = (nir - red) / (nir + red)
# Clean NaN values
ndvi = np.nan_to_num(ndvi, nan=-9999.0)
# Configure output profile for Cloud-Optimized GeoTIFF
profile.update(
dtype=rasterio.float32,
count=1,
nodata=-9999.0,
compress='lzw'
)
# Export calculated NDVI GeoTIFF
with rasterio.open("calculated_ndvi.tif", "w", **profile) as dst:
dst.write(ndvi.astype('float32'), 1)
print("NDVI calculation completed. GeoTIFF successfully saved.")