Remote Sensing Image Classification Strategies

#BelajarGEE 13: Optimizing multispectral classification in Google Earth Engine

Wahyu Ramadhan
13 min readFeb 10, 2024

Halo semuanya,

*This is the English version of my content here.

Previously, I’ve written about Land Use/Land Cover (LULC) classification using multispectral classification methods here. Along the way, I’ve come across various sources that made me realize there are some gaps to address. Specifically, it’s about how we gather training data or samples (different terms, but they mean the same thing, which I’ll refer to as training samples henceforth)

Let’s get to the core of the discussion, followed by the inclusion of the Google Earth Engine (GEE) script along with its explanations. So, why is it important to pay attention to the method of collecting training samples? The answer is quite straightforward: it significantly impacts the quality of the samples and the accuracy level of the resulting LULC model. Unfortunately, I didn’t emphasize this in my previous content, even though it’s fundamental and crucial.

What exactly are training sample(s)? In remote sensing image classification, they constitute a set of data used to train the classification model (training data). Not only that, but they also serve as test data to assess the accuracy level of the output. The model will then learn features and patterns from the data over an epoch or the number of times the algorithm passes through all the training data, assuming the algorithm we use is machine learning [4].

Training samples should ideally be polygons

In my previous content, I used points to sample, which was not very accurate as it only represented 1 (one) pixel. However, the surrounding pixels could belong to the same object or vice versa. Therefore, it’s better to use polygons to capture the spatial extent and object more accurately and representatively [1] [2].

A single pixel is not sufficient to represent an object, especially if the image has medium to low resolution
Training samples in the form of points only represent single pixels.

Another justification for using polygons to create training samples is to reduce the effects of spatial autocorrelation and data heterogeneity in the image. Spatial autocorrelation, for example, means that neighboring pixels are more similar than pixels further apart. Meanwhile, heterogeneity refers to the fact that the same class can have different spectral signatures in different locations (for example, within the water class, there are oceans, rivers, lakes, etc.) [1] [2] [3].

Training samples with polygons are more adaptive and representative of the shape of objects [5]

There’s another reason actually, but I think the discussion veers into how machine learning and/or deep learning algorithms are aided by training samples. But the goal is clear: to improve the quality and accuracy of the output.

Determining the quantity and size of training samples

There’s a formula that can serve as a reference for determining the number of training areas, which is n+1. Additionally, since the training sample is in polygon form, it must also meet the requirement of using 100n pixels. Both of these criteria use n, which represents the number of bands used [1] [2] [4].

My content utilizes PlanetScope image data with NIR (b8), Red (b6), Green (b4), and Blue (b2) bands. If other images or products are used, adjustments can be made accordingly. The details are as follows:

The context here is a sampling from vegetation classes

  • n= 4
  • The minimum number of sample locations for vegetation classes n+1= 4+1= 4 locations taken evenly.
  • Then, each sample must meet the number of pixels that, when added together, should meet 100n=100(4)=400 pixels.
  • If evenly distributed for each sample location of vegetation classes, then 400 pixels / 4 locations = 100 pixels per sample location

Processing steps

1. Load image data

We will use PlanetScope image data with a spatial resolution of 3 meters, acquired on 27 October 2023. Visit this link to download the data.

  1. Map.setOptions('SATELLITE'): Configures the basemap display option to satellite mode when the command is executed.
  2. Map.centerObject(image, 13): Positions the map view to center around the specified object, ‘image’, with a zoom level of 13.
  3. Map.addLayer(image, { min:0, max: 3000, bands: ['b6', 'b4','b2']}, 'Bali Strait 27 October 23 (true color)'): Adds the image layer ‘image’ to the map with the following parameters:
    (a) min: Minimum pixel value to be displayed.
    (b) max: Maximum pixel value to be displayed.
    (c) bands: List of bands to be displayed (b6, b4, and b2). (d) Layer title set as Bali Strait 27 October 23 (true color) and Bali Strait 27 October 23 (false color).
Map.setOptions('SATELLITE')
Map.centerObject(image, 13)

// Show image
Map.addLayer(image,{
min:0,
max: 3000,
bands: ['b6', 'b4','b2']},
'Bali Strait 27 October 23 (true color)')

Map.addLayer(image,{
min:0,
max: 4000,
bands: ['b8', 'b3','b4']},
'Bali Strait 27 October 23 (false color)')
True color composite (left) and false color composite (right) visualization

2. Determine the bands used

The bands used are quite common, true color or the red (b6), green (b4), and blue (b2) bands, along with an additional band, Near-Infrared (NIR) (b8). Band numbers may vary depending on the satellite image product used.

  • var bands: This is the variable used to store the list of bands to be used in the analysis. In this case, four bands are specified: ‘b8’, ‘b6’, ‘b4’, ‘b2’.
  • 'b8', 'b6', 'b4', 'b2': Refers to the names of spectral bands in the PlanetScope image that will be used for analysis. For example, ‘b8’ refers to Near Infrared (NIR), ‘b6’ refers to the Red band, ‘b4’ to Green, and ‘b2’ to Blue.
// Define bands
var bands = ['b8', 'b6', 'b4', 'b2'];

3. Prepare training sample

I will provide two options: using points with a buffer to ensure the samples remain polygons, and polygons where we trace objects manually.

  • ee.FeatureCollection: This is a collection of features in GEE used to create training samples.
  • water, vegetation, builtup, bareland: Represent points representing different classes in LULC classification.
  • .flatten(): Converts nested feature collections into a single collection.
  • .map(function(feat) { return feat.buffer(10); }): Applies a buffer with a radius of 10 meters to each point in the feature collection to expand the sample area.

Another option

  • The first option used flatten() on ee.FeatureCollection to generate training samples from all existing points.
  • The second option uses .merge(), which works similarly to .flatten(), by merging the geometries of training samples for each class into one
// Sample
// If you use point with buffer to create training sample
// var sample = ee.FeatureCollection([
// water, vegetation, builtup, bareland]).flatten().map(function(feat){
// return feat.buffer(10);
// });

// If you use shape or polygon to create roi
var sample = ee.FeatureCollection([water, vegetation, builtup, bareland]).flatten(); //use this
//var sample = water.merge(vegetation).merge(builtup).merge(bareland); //or use this
Setting each class on each feature for training sample

4. Count the number of pixels

Meanwhile, the way to count the number of pixels that have been obtained in each class is as follows. However, note that if we use the buffered points to sample, the number of pixels will be assumed to be the same as the number of points created for each class.

  • reduceRegion: This function computes statistics for each region specified in the geometry parameter. It aggregates pixel values intersecting with the given geometry (such as points, lines, and polygons) in an image according to the specified reducer.
  • ee.Reducer.count(): This is the reducer used to count the number of pixels intersecting with the specified geometry. This reducer counts the number of pixels inside each geometry.
  • geometry: Each geometry parameter specifies the area used as training samples. In this script, there are separate geometries for water, vegetation, builtup (built-up areas), and bareland (open land). This determines the areas in the image where pixel count calculations will be performed.
  • scale: This parameter specifies the nominal scale in meters of the projection used. It’s used to determine the spatial resolution for calculations. This scale parameter affects the area where the reducer calculates statistics. A lower scale increases spatial resolution but may require more computational resources.
  • countWater, countVegetation, countBuiltup, countBareland: These variables store the results of reduceRegion for each specified geometry. They contain the pixel count within the specified area.
  • print: The print function is used to display the calculated pixel count for each class (water, vegetation, builtup, bareland) to appear in the console.

The note I can convey is that although the image below shows the pixel count for 8 bands, only the selected bands will be included in the processing.

// Calculate the number of pixels within each class
var countWater = image.reduceRegion({
reducer: ee.Reducer.count(),
geometry: water,
scale: 3 // Adjust the scale based on your image resolution
});

var countVegetation = image.reduceRegion({
reducer: ee.Reducer.count(),
geometry: vegetation,
scale: 3
});

var countBuiltup = image.reduceRegion({
reducer: ee.Reducer.count(),
geometry: builtup,
scale: 3
});

var countBareland = image.reduceRegion({
reducer: ee.Reducer.count(),
geometry: bareland,
scale: 3
});

// Print the number of pixels for each class
print('Number of pixels in water:', countWater);
print('Number of pixels in vegetation:', countVegetation);
print('Number of pixels in builtup:', countBuiltup);
print('Number of pixels in bareland:', countBareland);
Number of pixels from the training sample in each class

5. Extracting samples for the image

  • image.select(bands): Selects the predetermined bands for use in the sample extraction process.
  • .sampleRegions({}): This method is used to extract samples from the image at locations corresponding to the previously defined ROI areas.
    (a) collection: sample: Uses sample as the region to extract samples, which have been predefined from the training sample.
    (b) properties: ['class']: Adds the class property ['class'] to each extracted sample. This serves to label the class corresponding to each sample.
    (c) scale: 4: Specifies the scale for sample extraction. This is a scaling factor that affects the number of samples extracted per area.
  • .randomColumn(): Adds a random column to the samples. This is often used in the process of splitting data into training and testing sets by dividing samples based on random values in this column.
// Extract sample from image
var extract_lc = image.select(bands).sampleRegions({
collection: sample,
properties: ['class'],
scale: 4
}).randomColumn();
//print(extract_lc);

6. Splitting samples to train and test the classification models

  • extract_lc: A collection of samples previously extracted from the image and training samples.
  • .filter(ee.Filter.lte('random', 0.7)): Uses a filter to divide samples into the training set. In this case, samples with a random value less than or equal to 0.7 will be included in the training set (train).
  • .filter(ee.Filter.gt('random', 0.3)): Uses a filter to divide samples into the testing set. Samples with a random value greater than 0.3 will be included in the testing set (test).

This division is performed using the random column previously added to the samples. In this case, approximately 70% of the samples will be used for training the model, while the remaining 30% will be used to test the model’s performance.

// Split train and test
var train = extract_lc.filter(ee.Filter.lte('random', 0.7));
var test = extract_lc.filter(ee.Filter.gt('random', 0.3));

7. Creating the classification model

  • ee.Classifier.smileRandomForest(50): This function creates a classification model using the Random Forest algorithm with 50 decision trees. This algorithm is used to train the classification model based on the provided samples.
  • .train(): This method trains the classification model using the previously created training samples.
    (a) features: train: Uses the training set train as the features to train the model.
    (b) classProperty: 'class': Specifies the class property ['class'] as the label or variable to be predicted by the model.
    (c) inputProperties: bands: Uses the list of predetermined bands as input properties for the model.

After training the model using the training samples, print(model.explain()) is used to print the explanation results of the model. This can provide information on how the model performs classification based on the given input.

// Classification model
var model = ee.Classifier.smileRandomForest(50).train({
features: train,
classProperty: 'class',
inputProperties: bands
});
print(model.explain());
Explanation of model classification of 4 classes and 4 bands in the console

8. Test or train the classification model

  • test.classify(model): Utilizes the previously trained classification model to classify the testing set (test), resulting in classifications for each sample in the testing set.
  • classifiedTest.errorMatrix('class', 'classification'): This method is used to compute the confusion matrix based on the actual class ['class']and the classified class ['classification'] of the classified testing set.
  • print(): Used to print the confusion matrix along with other evaluation metrics such as overall accuracy, kappa index, user accuracy, and producer accuracy.
// Test model
var classifiedTest = test.classify(model);

// Confusion matrix
var cm = classifiedTest.errorMatrix('class', 'classification');
print('Confusion Matrix', cm, 'Overall Accuracy', cm.accuracy(), 'Kappa', cm.kappa());
print('User Accuracy', cm.consumersAccuracy());
print('Producer Accuracy', cm.producersAccuracy());
Accuracy result of each class

9. Visualization parameters

  • values: Represent the values corresponding to each class generated from the classification. For example, value 1 represents the water class, value 2 represents the vegetation class, and so on.
  • palette: A list of colors used to display each class in the classification result. For instance, the water class is represented by blue color, vegetation by green, and so forth.
  • names: A list of class names corresponding to the predefined values. Each class name is associated with the same value in the values list.
// Visualization Parameter
var values = [1, 2, 3, 4];
var palette = ['blue','green', 'red', 'yellow'];
var names = ['Water', 'Vegetation', 'Builtup', 'Bareland'];

10. Apply classification model

  • image.classify(model, 'lc_class'): Performs classification using the trained model (model) on the entire image to be classified (image). The classification result will be stored in the lc_class variable.
  • .set(): Used to add properties or metadata to the classification result. In this case, two properties are added:
    (a) 'lc_class_values': values: Stores the values representing the classes of the classification result.
    (b) 'lc_class_palette': palette: Stores the color palette to be used for visualizing the classification result.
// Apply model
var lc_class = image.classify(model, 'lc_class').set({
'lc_class_values': values,
'lc_class_palette': palette,
});

11. Display the result as a map layer

  • Map.addLayer(): This function adds an image or layer to the interactive map in GEE.
  • lc_class: Represents the LULC classification image to be displayed.
  • { min: 1, max: 4, palette: palette }: Parameters used for displaying the image.
    (a) min: 1: Minimum value on the color map assigned to the classification result image.
    (b) max: 4: Maximum value on the color map assigned to the classification result image.
    (c) palette: palette: Uses the predefined color palette to map class values (from 1 to 4) to corresponding colors.
  • 'Land Cover Bali Strait October 2023': Label used to name the displayed LULC classification image on the map.
// Display classified image with defined color palette
Map.addLayer(lc_class, {
min: 1,
max: 4,
palette: palette
}, 'Land Cover Bali Strait October 2023');
Image classification results with Random Forest algorithm

12. Display the map legend

  • Map.add(): This function is used to add elements to the map.
  • ui.Panel(): Creates a UI panel containing visual elements to be displayed as the legend.
  • names.map(): Uses the map method to create a series of panels containing labels and color boxes for each LULC class.
  • ui.Label('', { width: '30px', height: '15px', border: '0.5px solid black', backgroundColor: palette[index] }): Creates a small box with the corresponding color from the palette and displays the LULC color.
  • ui.Label(name, { height: '15px' }): Displays the name of the lLULC class.
  • ui.Panel.Layout.flow(): Sets the panel layout, here the panels are arranged horizontally or vertically.
  • { position: 'bottom-left' }: Specifies the position of the legend on the map, in this case, the legend is positioned at the bottom left of the map.
// Legend
Map.add(
ui.Panel(
names.map(function(name, index){
return ui.Panel([
ui.Label('', { width: '30px', height: '15px', border: '0.5px solid black', backgroundColor: palette[index] }),
ui.Label(name, { height: '15px' })
], ui.Panel.Layout.flow('horizontal'));
}),
ui.Panel.Layout.flow('vertical'),
{ position: 'bottom-left' }
)
);
Map legend on the bottom left

13. Export data to Google Drive

  • Export.image.toDrive(): This function is used to export images or data from GEE to Google Drive.
  • image: lc_class: Specifies the image to be exported, in this case, the LULC classification result (lc_class) image.
  • description: 'lc_soreang_dec23': Description or name for this export process.
  • maxPixels: 1e13: Maximum limit of pixels for the export process. This value is set to 1e13 (10¹³) to avoid restrictions on the maximum number of pixels.
  • folder: 'Google Earth Engine': Folder in Google Drive where the export results will be saved.
  • scale: 3: Spatial scale of the image to be exported, in meters per pixel.
  • fileNamePrefix: 'Land Cover Bali Strait Oct 23': File name to be saved in Google Drive.
  • fileFormat: 'GeoTIFF': File format of the export result, in this case, GeoTIFF is used because it supports rich spatial metadata and can be used in many GIS software.
// Export
Export.image.toDrive({
image: lc_class,
description: 'lc_balistrait_oct23',
maxPixels: 1e13,
folder: 'Google Earth Engine',
scale: 3,
fileNamePrefix: 'Land Cover Bali Strait Oct 23',
fileFormat: 'GeoTIFF'
});
Export the results in Tasks

Link to full script

References

[1] Jensen, J. R. (2014). Remote Sensing of the environment an earth resource perspective. Pearson Ed.
[2] Jensen, J. R. (2015). Introductory Digital Image Processing a Remote Sensing Perspective. Pearson Education (US).
[3] Karasiak, N., Dejoux, J.-F., Monteil, C., & Sheeren, D. (2021b). Spatial dependence between training and test sets: Another pitfall of Classification Accuracy Assessment in remote sensing. Machine Learning, 111(7), 2715–2740. https://doi.org/10.1007/s10994-021-05972-1
[4] Zhang, H., He, J., Chen, S., Zhan, Y., Bai, Y., & Qin, Y. (2023). Comparing three methods of selecting training samples in supervised classification of Multispectral Remote Sensing Images. Sensors, 23(20), 8530. https://doi.org/10.3390/s23208530
[5] Hu, Y., An, R., Wang, B., Xing, F., & Ju, F. (2020). Shape adaptive neighborhood information-based semi-supervised learning for Hyperspectral Image Classification. Remote Sensing, 12(18), 2976. https://doi.org/10.3390/rs12182976
[6] Google. (2024). Machine learning in Earth engine | Google Earth engine | google for developers. Google. https://developers.google.com/earth-engine/guides/machine-learning
[6] Google. (2024). Supervised classification | google earth engine | google for developers. Google. https://developers.google.com/earth-engine/guides/classification

Connect with me on LinkedIn

--

--

Wahyu Ramadhan

Mapping my way through the GIScience universe. Join me on this journey!