Skip to content
← Back to QGIS from first project to capstone
QGIS · Part 08 of 20Beginner· 10 min readSources

Band Combinations and Spectral Indices in QGIS with Sentinel-2 and Landsat 8

Build true and false colour composites, apply scale factors, mask clouds, and calculate NDVI, NDWI, MNDWI, NDBI and NBR from open satellite data.

Data used

  • Sentinel-2 L2A, tile 43PGQ, 15 March 2025: B02, B03, B04, B08, B11, B12, SCL
  • Landsat 8 Collection 2 Level-2, path 144 row 051, 29 March 2025: SR_B2 to SR_B7

Both are surface reflectance products, which means they have already had atmospheric correction applied. Check the product metadata to confirm what correction a scene has before processing it again.

The bands you need

RegionSentinel-2 bandResolutionLandsat 8 bandResolution
BlueB0210 mSR_B230 m
GreenB0310 mSR_B330 m
RedB0410 mSR_B430 m
Near infrared (NIR)B0810 mSR_B530 m
Shortwave infrared 1 (SWIR1)B1120 mSR_B630 m
Shortwave infrared 2 (SWIR2)B1220 mSR_B730 m

Band numbers do not line up between the two sensors. NIR is B08 on Sentinel-2 and B5 on Landsat 8. Using the wrong number is the most frequent index error.

1. Prepare the layers

Rename the layers so the expressions stay readable. In the Layers panel, press F2 on a layer and give short names such as S2_B04, S2_B08, L8_B5.

QGIS builds the expression name from the layer name, written as "S2_B04@1", where @1 is the band number inside the file.

Stack the bands for display

Use Raster > Miscellaneous > Build Virtual Raster with Place each input file into a separate band, as in Part 1. Make one stack per sensor, in band order.

For the Landsat stack, use SR_B2, SR_B3, SR_B4, SR_B5, SR_B6, SR_B7 in that order. The band numbers inside the stack will then be 1 to 6.

2. Colour composites

A composite assigns three bands to red, green and blue. Open Layer Properties > Symbology > Multiband color, then choose bands.

Sentinel-2

CompositeRGBGood for
True colourB04B03B02General interpretation
Colour infraredB08B04B03Vegetation (bright red), water (dark)
SWIR compositeB12B08B04Moisture, burn scars, bare soil
AgricultureB11B08B02Crop condition and vegetation vigour

Landsat 8

CompositeRGBGood for
True colour432General interpretation
Colour infrared543Vegetation
Shortwave infrared764Burn areas and geology
Agriculture652Crops and soil moisture

Stretch each band under Min / Max Value Settings. Use Cumulative count cut 2% to 98% as a starting point.

Four Sentinel-2 composites of the same area in a grid
Figure 8.1. Four Sentinel-2 composites of the same area in a grid
Colour infrared Landsat 8 composite with vegetation in red
Figure 8.2. Colour infrared Landsat 8 composite with vegetation in red

3. Scale factors

Surface reflectance values are stored as integers to save space. You must convert them before comparing across scenes, dates or sensors.

Landsat Collection 2 Level-2

reflectance = DN * 0.0000275 - 0.2

Values of 0 are NoData. In the Raster Calculator, apply it to each band:

"L8_B5@1" * 0.0000275 - 0.2

Sentinel-2 L2A

Sentinel-2 L2A values are integers equal to 10,000 times the reflectance. The original ESA files from processing baseline 04.00 onward also carry an offset of 1000, which gives reflectance = (DN - 1000) / 10000. The files in this series come from the Earth Search copies on AWS, and they already have that offset removed. For these files the formula is:

reflectance = DN / 10000

In the Raster Calculator:

"S2_B08@1" / 10000

Check the values instead of assuming. With the offset still present, no valid pixel falls below about 1000. In our tile, clear water in band 11 reads about 165, and about 1 percent of the valid pixels in band 8 are below 1000, so there is no offset to remove. Subtracting 1000 here would give negative reflectance for water and shadow.

4. Cloud masking with SCL

Sentinel-2 L2A includes a Scene Classification Layer (SCL) at 20 m resolution. Each pixel carries a class code.

ValueClass
0No data
1Saturated or defective
2Dark area pixels
3Cloud shadows
4Vegetation
5Not vegetated
6Water
7Unclassified
8Cloud, medium probability
9Cloud, high probability
10Thin cirrus
11Snow or ice

Our scene has 0% cloud, so the mask changes little here. The steps still apply to any cloudier scene.

  1. Load SCL.tif, rename it S2_SCL.
  2. Open the Raster Calculator and build a clear-pixel mask:
("S2_SCL@1" != 3) AND ("S2_SCL@1" != 8) AND ("S2_SCL@1" != 9) AND ("S2_SCL@1" != 10)
  1. Save it as clear_mask.tif. It holds 1 for clear pixels and 0 for cloud or shadow.
  2. Multiply an index by the mask later, or use it as an input to Clip.

For Landsat, use the QA_PIXEL band. Its bits flag cloud, cloud shadow and snow, and you decode them with bit tests. A quick way is the Semi-Automatic Classification Plugin, which includes a mask tool.

SCL is 20 m. To use it with a 10 m band, resample the band or the mask to a common grid first (Raster > Projections > Warp).

5. Spectral indices

An index combines bands into one value per pixel so that a surface type stands out.

IndexFormulaHighlights
NDVI(NIR − Red) / (NIR + Red)Green vegetation
NDWI(Green − NIR) / (Green + NIR)Open water (the McFeeters version). Other papers use the same name for a NIR and SWIR index, so check the formula
MNDWI(Green − SWIR1) / (Green + SWIR1)Open water, with less confusion from built-up areas
NDBI(SWIR1 − NIR) / (SWIR1 + NIR)Built-up and bare areas
NBR(NIR − SWIR2) / (NIR + SWIR2)Burned areas and vegetation health

The formula column uses a minus sign for subtraction. In QGIS, type a hyphen.

Sentinel-2 expressions

Resample B11 and B12 to 10 m first, so that all inputs share a grid. Then, with scaled layers named nir, red, green, swir1, swir2:

NDVI  = ("nir@1" - "red@1") / ("nir@1" + "red@1")
MNDWI = ("green@1" - "swir1@1") / ("green@1" + "swir1@1")
NDBI  = ("swir1@1" - "nir@1") / ("swir1@1" + "nir@1")
NBR   = ("nir@1" - "swir2@1") / ("nir@1" + "swir2@1")

Each is one expression. Enter it in Raster > Raster Calculator, with the left-hand name left off.

Landsat 8 expressions

All Landsat bands share a 30 m grid, so no resampling is needed. If you applied the scale factors in step 3, use the scaled layers. Otherwise, include them inline:

NDVI = (("L8_B5@1" * 0.0000275 - 0.2) - ("L8_B4@1" * 0.0000275 - 0.2)) / (("L8_B5@1" * 0.0000275 - 0.2) + ("L8_B4@1" * 0.0000275 - 0.2))

Building scaled layers first, then reusing them, is shorter and less error-prone.

Raster Calculator with the NDVI expression and output settings
Figure 8.3. Raster Calculator with the NDVI expression and output settings

6. Style the results

  1. Layer Properties > Symbology > Singleband pseudocolor.
  2. Choose a ramp: RdYlGn for NDVI, Blues for MNDWI, and a diverging ramp for NBR.
  3. Set Min and Max. For NDVI, -0.2 to 0.9 is a reasonable range.
  4. Click Classify, then Apply.

Use the Histogram tab in Properties to see where your values fall, and let it set the thresholds, since a fixed number from a textbook will not suit every scene.

NDVI map with legend
Figure 8.4. NDVI map with legend
MNDWI showing lakes in Bengaluru
Figure 8.5. MNDWI showing lakes in Bengaluru
NDBI showing the built-up area
Figure 8.6. NDBI showing the built-up area

Reading values

Use the Identify tool on a few known places:

  • Lalbagh Botanical Garden and Cubbon Park should give high NDVI.
  • Ulsoor Lake and Hebbal Lake should give high MNDWI.
  • The airport runway area and the city centre should give higher NDBI than the parks.

Checking against places you know is how you confirm your formula and band numbers are right.

7. Compare Sentinel-2 and Landsat

Make the NDVI for both and place them side by side.

  • Sentinel-2 shows finer detail, because the pixels are 10 m.
  • The two scenes differ by 14 days and by sensor, so values will not match pixel for pixel.
  • Landsat 8 and Sentinel-2 have slightly different band widths, so index values differ a little even for the same surface.
Sentinel-2 NDVI and Landsat NDVI at the same extent
Figure 8.7. Sentinel-2 NDVI and Landsat NDVI at the same extent

Common mistakes

  • Using the Sentinel-2 band number for a Landsat scene.
  • Skipping the scale factor and offset when comparing scenes.
  • Combining 10 m and 20 m bands without resampling.
  • Treating an index as a final answer instead of a clue. A water index, for example, can flag shadow.
  • Using NDWI and MNDWI interchangeably. They use different bands.
  • Leaving cloud, shadow and NoData pixels in the calculation.

Key takeaways