colab Open in SageMaker Studio Lab Open in Planetary Computer

Histogram Matching

Tutorial created by **Aaron Zuspan**: GitHub | Twitter

Let’s start!

If required, please uncomment:

[1]:
# !pip install eemont
# !pip install geemap

Import the required packages.

[2]:
import ee, eemont, geemap

Authenticate and Initialize Earth Engine and geemap.

[3]:
Map = geemap.Map()

Matching Landsat 5 to Landsat 8

Histogram matching performs band-wise adjustments to match the spectral characteristics of one image to an overlapping image. It can be used for images taken at different times or by different sensors to allow comparisons.

In this example we’ll match a Landsat 5 image to a Landsat 8 image. We’ll use images from different seasons to demonstrate a dramatic example of histogram adjustment, but note that significant changes between images such as land cover, seasonality, or clouds and snow may create inaccurate imagery.

Load and visualize a Landsat 5 image from winter of 2011.

[4]:
source = ee.Image("LANDSAT/LT05/C01/T1_SR/LT05_195028_20110208")

Map.addLayer(source, {"min": 170, "max": 2110, "bands": ["B3", "B2", "B1"]}, "Landsat 5 - Source")
pt = ee.Geometry.Point([6.6346, 46.6989])
Map.centerObject(pt, zoom=12)
Map

Load a Landsat 8 image from the same location in summer of 2018. Notice how the Landsat 8 image is much greener.

[5]:
target = ee.Image("LANDSAT/LC08/C02/T1_L2/LC08_196027_20130714")
Map.addLayer(target, {"min": 7800, "max": 13700, "bands": ["SR_B4", "SR_B3", "SR_B2"]}, "Landsat 8 - Target")
Map

Specify a region to match histograms within. Image histograms will be calculated within this region, so choose an area that is representative of each image and that does not contain clouds. If no geometry is provided, the full source image will be used. We’ll visualize the region later using an outline.

[6]:
region = ee.Geometry.Polygon(
    [[[6.499245, 46.640447],
      [6.499245, 46.763351],
      [6.779451, 46.763351],
      [6.779451, 46.640447],
      [6.499245, 46.640447]]]
)

regionOutline = ee.Image().byte().paint(featureCollection=region, color=1, width=1)

Specify the corresponding band pairs to match using a dictionary with the source bands as keys and the target bands as values. Only the bands specified will be matched and included in the output image. If you’re not sure which bands correspond between platforms, try checking the band descriptions in the Earth Engine Data Catalog or use `ee.Image.getSTAC() <https://colab.research.google.com/github/davemlz/eemont/blob/master/tutorials/019-Checking-STAC-Info.ipynb>`__.

[7]:
bands = {
    # Red
    "B3": "SR_B4",
    # Green
    "B2": "SR_B3",
    # Blue
    "B1": "SR_B2"
}

Histogram-match the Landsat 5 image to the Landsat 8 image and visualize the result. Notice how the adjusted Landsat 5 image is much greener, matching the Landsat 8 image.

[8]:
matched = source.matchHistogram(target, bands, geometry=region)
Map.addLayer(matched, {"min": 7800, "max": 13700, "bands": ["B3", "B2", "B1"]}, "Landsat 5 - Matched")
Map.addLayer(regionOutline, {"palette": "#00FFFF"}, "Region")
Map

Matching Sentinel-2 to MODIS

Create a fresh map.

[9]:
Map = geemap.Map()

In this example, we’ll match the histogram of a Sentinel-2 image to a MODIS image taken on a similar date to minimize atmospheric effects in the Sentinel image. First, we’ll load the MODIS imagery.

[10]:
target = ee.Image("MODIS/006/MOD09A1/2018_08_05")
Map.addLayer(target, {"min": 600, "max": 4000, "bands": ["sur_refl_b01", "sur_refl_b04", "sur_refl_b03"]}, "MODIS - Source")
pt = ee.Geometry.Point([29.4450, 12.9945])
Map.centerObject(pt, zoom=9)
Map

Next, we’ll load a Sentinel-2 image. Notice how the colors do not match the MODIS image.

[11]:
source = ee.Image("COPERNICUS/S2/20180923T081641_20180923T083023_T35PQQ")
Map.addLayer(source, {"min": 600, "max": 4000, "bands": ["B4", "B3", "B2"]}, "Sentinel-2 - Source")
Map

Specify which band pairs to match.

[12]:
bands = {
    # Red
    "B4": "sur_refl_b01",
    # Green
    "B3": "sur_refl_b04",
    # Blue
    "B2": "sur_refl_b03"
}

Histogram-match the Sentinel-2 image to the MODIS image. Because we’re not specifying a region, the entire Sentinel-2 image footprint will be used for matching.

Notice how the matched image now blends in with the MODIS image.

[13]:
matched = source.matchHistogram(target, bands)
Map.addLayer(matched, {"min": 600, "max": 4000, "bands": ["B4", "B3", "B2"]}, "Sentinel-2 - Matched")
Map