Detecting settlements using supervised classification and deep learning

  • 🔬 Data Science
  • 🥠 Deep Learning and pixel-based classification

Introduction

Deep-Learning methods tend to perform well with high amounts of data as compared to machine learning methods which is one of the drawbacks of these models. These methods have also been used in geospatial domain to detect objects [1,2] and land use classification [3] which showed favourable results, but labelled input satellite data has always been an effortful task. In that regards, in this notebook we have attempted to use the supervised classification approach to generate the required volumes of data which after cleaning was used to come through the requirement of larger training data for Deep Learning model.

For this, we have considered detecting settlements for Saharanpur district in Uttar Pradesh, India. Settlements have their own importance to urban planners and monitoring them temporally can lay the foundation to design urban policies for any government.

Necessary imports

%matplotlib inline

import pandas as pd
from datetime import datetime
import matplotlib.pyplot as plt

import arcgis
from arcgis.gis import GIS
from arcgis.geocoding import geocode
from arcgis.geometry import Geometry
from arcgis.learn import prepare_data, UnetClassifier
from arcgis.raster.functions import clip, apply, extract_band, colormap, mask, stretch
from arcgis.raster.analytics import train_classifier, classify, list_datastore_content

Connect to your GIS

gis = GIS('home')

Get the data for analysis

Search for Multispectral Landsat layer in ArcGIS Online. We can search for content shared by users outside our organization by setting outside_org to True.

landsat_item = gis.content.search('title:Multispectral Landsat', 'Imagery Layer', outside_org=True)[0]
landsat = landsat_item.layers[0]
landsat_item
Multispectral Landsat
Landsat multispectral and multitemporal imagery with on-the-fly renderings and indices for visualization and analysis. The Landsat 8 and 9 imagery in this layer is updated daily and is directly sourced from the USGS Landsat collection on AWS.
Imagery Layer by esri
Last Modified: July 01, 2022
3 comments, 14,15,012 views

Search for India State Boundaries 2018 layer in ArcGIS Online. This layer has all the District boundaries for India at index - 2.

boundaries = gis.content.get('76f5897f7e354d399d261dac03af2ae3')
boundaries
India: District Boundary 2024
This layer shows Indian administrative boundaries up to the District level.
Feature Layer Collection by esri_IN_content0
Last Modified: January 03, 2024
0 comments, 5,463 views
district_boundaries = boundaries.layers[0]
district_boundaries
<MapFeatureLayer url:"https://livingatlas.esri.in/server/rest/services/IAB2024/IAB_District_2024/MapServer/0">
from arcgis.map.symbols import SimpleFillSymbolEsriSFS
from arcgis.map.symbols import SimpleLineSymbolEsriSLS
from arcgis.map.renderers import SimpleRenderer

m = gis.map('Saharanpur, India')

outline = SimpleLineSymbolEsriSLS(type = "esriSLS", style = "esriSLSSolid", color = [255, 0, 0, 255], width = 2)
symbol = SimpleFillSymbolEsriSFS(outline = outline, style = "esriSFSSolid", color = [255, 120, 0, 80])
renderer = SimpleRenderer(symbol = symbol)

m.content.add(district_boundaries, drawing_info = {"renderer":renderer}, options = {"title":"District Boundaries"})

m.legend.enabled = True
m
<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=1275x677>
m.zoom = 7

As this notebook is to detect settlements for Saharanpur district, you can filter the boundary for Saharanpur.

area = geocode("Saharanpur, India", out_sr = landsat.properties.spatialReference)[0]
saharanpur = district_boundaries.query(where = "ID = '9177673'")   # query for Saharanur district boundary
saharanpur_geom = saharanpur.features[0].geometry                  # Extracting geometry of Saharanpur district boundary   

extent = dict(Geometry(saharanpur_geom).envelope)                  # Set the extent
landsat.extent = extent
saharanpur.features[0].extent = extent                     
Geometry(saharanpur_geom)
<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=337x335>

Get the training points for training the classifier. These are 212 points in total marked against 5 different classes namely Urban, Forest, Agricultural, Water and Wasteland. These points are marked using ArcGIS pro and pulished on the gis server.

data = gis.content.get('0e03348c4d4145fa9bcacc4c800c055e')
data
classificationPointsSaharanpur_Projected

Feature Layer Collection by api_data_owner
Last Modified: October 29, 2024
0 comments, 189 views

Filter satellite Imagery based on cloud cover and time duration

In order to produce good results, it is important to select cloud free imagery from the image collection for a specified time duration. In this example we have selected all the images captured between 1 October 2018 to 31 December 2018 with cloud cover less than or equal to 5% for Saharanpur region.

selected = landsat.filter_by(where="(Category = 1) AND (cloudcover <=0.05)",
                             time=[datetime.datetime(2024, 3, 1), 
                                   datetime.datetime(2024, 7, 31)],
                             geometry=arcgis.geometry.filters.intersects(area['extent']))
df = selected.query(out_fields="AcquisitionDate, GroupName, CloudCover, DayOfYear", 
                    order_by_fields="AcquisitionDate").sdf
df['AcquisitionDate'] = pd.to_datetime(df['AcquisitionDate'], unit='ms')
df
OBJECTIDAcquisitionDateGroupNameCloudCoverDayOfYearSHAPE
050132022024-03-08 05:18:43LC08_L1TP_146040_20240308_20240315_02_T1_MTL0.000340{"rings": [[[8796000.0984, 3442899.6076999977]...
150356812024-03-15 05:24:28LC08_L1TP_147039_20240315_20240401_02_T1_MTL0.000239{"rings": [[[8666740.7075, 3627836.4750000015]...
250248102024-03-23 05:24:45LC09_L1TP_147039_20240323_20240323_02_T1_MTL0.00139{"rings": [[[8664390.306899998, 3627647.700999...
350497372024-04-08 05:24:37LC09_L1TP_147039_20240408_20240408_02_T1_MTL0.001539{"rings": [[[8664874.682, 3627657.164499998], ...
450766002024-04-16 05:24:09LC08_L1TP_147039_20240416_20240423_02_T1_MTL0.033639{"rings": [[[8666288.433200002, 3627938.365800...
550661732024-04-17 05:18:18LC09_L1TP_146039_20240417_20240417_02_T1_MTL0.019239{"rings": [[[8837873.4065, 3627741.8351000026]...
650661742024-04-17 05:18:42LC09_L1TP_146040_20240417_20240417_02_T1_MTL0.000540{"rings": [[[8795108.6435, 3442745.924900003],...
750793932024-04-24 05:24:15LC09_L1TP_147039_20240424_20240424_02_T1_MTL0.01739{"rings": [[[8671082.293699998, 3627628.717200...
851002532024-04-25 05:17:54LC08_L1TP_146039_20240425_20240510_02_T1_MTL0.041839{"rings": [[[8837330.6147, 3628004.6987000033]...
951002542024-04-25 05:18:18LC08_L1TP_146040_20240425_20240510_02_T1_MTL0.000640{"rings": [[[8794622.175500002, 3442817.626699...
1051047192024-05-02 05:23:58LC08_L1TP_147039_20240502_20240511_02_T1_MTL0.000139{"rings": [[[8667035.7471, 3627784.549900003],...
1151374942024-05-18 05:23:45LC08_L1TP_147039_20240518_20240604_02_T1_MTL0.001339{"rings": [[[8667733.891199999, 3627766.586599...
1251175072024-05-19 05:18:19LC09_L1TP_146040_20240519_20240519_02_T1_MTL0.023140{"rings": [[[8797172.683400001, 3442675.637599...
1351279542024-05-26 05:24:02LC09_L1TP_147039_20240526_20240526_02_T1_MTL0.039{"rings": [[[8668371.996100001, 3627698.906900...
1451481932024-05-27 05:17:54LC08_L1TP_146040_20240527_20240610_02_T1_MTL0.000340{"rings": [[[8795394.7881, 3442920.6438999996]...
1551521802024-06-03 05:23:42LC08_L1TP_147039_20240603_20240611_02_T1_MTL0.003939{"rings": [[[8669127.840999998, 3627732.152400...
1651542322024-06-11 05:23:56LC09_L1TP_147039_20240611_20240611_02_T1_MTL0.00239{"rings": [[[8666620.895100001, 3627612.287900...
1751753382024-06-12 05:17:31LC08_L1TP_146039_20240612_20240628_02_T1_MTL0.048139{"rings": [[[8842997.084199999, 3627922.498000...
1851753392024-06-12 05:17:55LC08_L1TP_146040_20240612_20240628_02_T1_MTL0.000140{"rings": [[[8800124.631000001, 3442759.240099...
1951896662024-06-19 05:23:50LC08_L1TP_147039_20240619_20240706_02_T1_MTL0.020639{"rings": [[[8670358.190900002, 3627567.752599...

We can now select the image dated 17 April 2024 from the collection using its OBJECTID which is "5066173"

saharanpur_image = landsat.filter_by('OBJECTID=5066173') # 2024-04-17 05:18:42
saharanpur_image
<ImageryLayer url:"https://landsat2.arcgis.com/arcgis/rest/services/Landsat/MS/ImageServer">
m1 = gis.map('Saharanpur, India')
m1.content.add(apply(saharanpur_image, 'Natural Color with DRA'))
m1.content.add(saharanpur, options = {"title" : 'Saharanpur District'})
m1.legend.enabled = True
m1
<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=1277x677>
m1.zoom = 9

A single tile completely covers our area of interest here - 5066173, if our area of interest is larger and can not be enclosed within the tile we have chosen, then a mosaic of two images can be created to enclose the AOI. Mosaicking two rasters with OBJECTID - 5035681 and 5066173 using predefined mosaic_by function.

landsat.mosaic_by(method = "LockRaster", lock_rasters = [5035681, 5066173])
mosaic = apply(landsat, 'Natural Color with DRA')
mosaic
<ImageryLayer url:"https://landsat2.arcgis.com/arcgis/rest/services/Landsat/MS/ImageServer">

We will clip the mosaicked raster using the boundary of Saharanpur which is our area of interest using clip function.

saharanpur_raster_clip = arcgis.raster.functions.clip(mosaic, saharanpur_geom)
saharanpur_raster_clip.extent = extent
arcgis.env.analysis_extent = extent
saharanpur_raster_clip
<ImageryLayer url:"https://landsat2.arcgis.com/arcgis/rest/services/Landsat/MS/ImageServer">
clip_out_name = 'saharanpur_raster_clip' + str(datetime.date.today()).replace("-", "")
saharanpur_raster_clip.save(clip_out_name, gis = gis)
saharanpur_raster_clip20241218
Analysis Image Service generated from GenerateRaster
Tiled Imagery Layer by arcgis_python
Last Modified: December 18, 2024
0 comments, 3 views

Visualize the mosaic and training points on map

m2 = gis.map('Saharanpur, India')
m2.content.add(saharanpur_raster_clip)
m2.content.add(data)
m2.legend.enabled = True
m2
<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=1282x677>
m2.zoom = 9

Classification

With the Landsat layer and the training samples, we are now ready to train a support vector machine (SVM) model using train_classifier function.

saharanpur_raster_clip_search = gis.content.search(clip_out_name)
saharanpur_raster_clip_search = saharanpur_raster_clip_search[0] # Check for the name of the layer if it is the same name with which you have saved it in previous steps or else try by changing indexing
saharanpur_raster_clip_search
saharanpur_raster_clip20241218
Analysis Image Service generated from GenerateRaster
Tiled Imagery Layer by arcgis_python
Last Modified: December 18, 2024
0 comments, 3 views
classifier_definition = train_classifier(input_raster = saharanpur_raster_clip_search, 
                                         input_training_sample_json = data.layers[0].query().to_json, 
                                         classifier_parameters = {"method":"svm",
                                                                "params":{"maxSampleClass":100}},
                                         gis = gis)

Now we have the model, we are ready to apply the model to a landsat image of Saharanpur district to make prediction. This classifier will classify each pixel of the image into 5 different classes namely Urban, Forest, Agricultural, Water and Wasteland. We will use predefined classify function to perform the classification.

classified_output = classify(input_raster = saharanpur_raster_clip, 
                             input_classifier_definition = classifier_definition)
classified_output
Classify_ZVG9NR
Analysis Image Service generated from Classify
Tiled Imagery Layer by arcgis_python
Last Modified: December 18, 2024
0 comments, 4 views

Now let's visualize the classification result based on the colormap defined below.

cmap = colormap(classified_output.layers[0],
                 colormap=[[0, 255, 0, 0],
                          [1, 15, 242, 45],
                          [2, 255, 240, 10],
                          [3, 0, 0, 255],
                          [4, 176, 176, 167]])
cmap
<ImageryLayer url:"https://rasterutils7.arcgis.com/arcgis/rest/services/RasterRendering/ImageServer">
out_name = 'saharanpur_classified_output__' + str(datetime.date.today()).replace("-", "")
cmap.save(out_name, gis = gis)
saharanpur_classified_output__20241218
Analysis Image Service generated from GenerateRaster
Tiled Imagery Layer by arcgis_python
Last Modified: December 18, 2024
0 comments, 3 views
m3 = gis.map('Saharanpur, India')
m3.content.add(cmap)
m3.legend.enabled = True
m3
<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=1275x673>
m3.zoom = 10

Here, '0', '1', '2', '3' and '4' represents pixels from 'Urban', 'Forest', 'Agricultural', 'Water' and 'Wasteland' class respectively.

We can see that our model is performing reasonably well and is able to clearly identify the urban settlements while comparing the prediction with the Landsat image.

Mask settlements out of the classified raster

We have a classified raster which has each of its pixel classified against 5 different classes. But we require the pixels belonging to Urban class only, so we will mask all the pixels belonging to that class.

classified_output = gis.content.search('saharanpur_classified_output', outside_org= True)[0]
classified_output
saharanpur_classified_output__20241120
Analysis Image Service generated from GenerateRaster
Tiled Imagery Layer by arcgis_python
Last Modified: November 20, 2024
0 comments, 7 views

As we are concerned only with detecting settlement pixels, we will mask out others from the classified raster by selecting the pixels with a class value of '0' which represents 'Urban' class.

masked = colormap(mask(classified_output.layers[0],
                       no_data_values =[],
                       included_ranges =[0,0]),
                  colormap=[[0, 255, 0, 0]],
                  astype='u8')
masked.extent = extent
masked
<ImageryLayer url:"https://rasterutils7.arcgis.com/arcgis/rest/services/RasterRendering/ImageServer">

We can save the masked raster containing the settlements to an imagery layer. This uses distributed raster analytics and performs the processing at the source pixel level and creates a new raster product.

masked = masked.save()

We can now Convert the masked pixels to polygons using 'to_features' function.

urban_item = masked.layers[0].to_features(output_name='masked_polygons' + str(datetime.datetime.now().microsecond) ,
                                          output_type='Polygon',
                                          field='Value',
                                          simplify=True,
                                          gis=gis)
urban_layer = urban_item.layers[0]
m4 = gis.map('Saharanpur, India')
m4.legend.enabled = True
m4.content.add(urban_item)
m4
<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=1276x677>
m4.zoom = 11

The settlement polygons have many false positives - mainly there are areas near river bed that are confused with settlements.

These false positives were removed by post processing using ArcGIS Pro. The result obtained after cleaning up the supervised classification are shown below, which are the final detected settlements.

settlement_poly = gis.content.search("'settlement_polygons'", outside_org= True)

Deep Learning

The deep learning approach for classification needs more training data. We will use the results that we obtained after cleaning up false positives from SVM predictions above, and exported training chips against each of those polygons using ArcGIS pro to train a deep learning model.

Export training data

Export training data using 'Export Training data for deep learning' tool, detailed documentation here

  • Set Landsat imagery as 'Input Raster'.
  • Set a location where you want to export the training data, it can be an existing folder or the tool will create that for you.
  • Set the settlement training polygons as input to the 'Input Feature Class Or Classified Raster' parameter.
  • In the option 'Input Mask Polygons' we can set a mask layer to limit the tool to export training data for only those areas which have buildings in it, we created one by generating a grid of 200m by 200m on Urban Polygons layer's extent and dissolving only those polygons which contained settlements to a single multipolygon feature.
  • 'Tile Size X' & 'Tile Size Y' can be set to 224.
  • Select 'Classified Tiles' as the 'Meta Data Format' because we are training an 'Unet Model'.
  • In 'Environments' tab set an optimum 'Cell Size'. For this example, as we have performing the analysis on Landsat8 imagery, we used 30 cell size which meant 30m on a project coordinate system.

Prepare data

We would specify the path to our training data and a few hyper parameters.

  • path: path of folder containing training data.
  • batch_size: No of images your model will train on each step inside an epoch, it directly depends on the memory of your graphic card. 8 worked for us on a 11GB GPU.
  • imagery_type: It is a mandatory input to enable model for multispectral data processing. It can be "landsat8", "sentinel2", "naip", "ms" or "multispectral".
import os
from pathlib import Path

gis = GIS('home')
training_data = gis.content.get('b9602a576cd54bc6a8e0279a290537af')
training_data
detecting_settlements_using_supervised_classification_and_deep_learning

Image Collection by api_data_owner
Last Modified: September 03, 2020
0 comments, 41 views
filepath = training_data.download(file_name=training_data.name)
import zipfile
with zipfile.ZipFile(filepath, 'r') as zip_ref:
    zip_ref.extractall(Path(filepath).parent)
data_path = Path(os.path.join(filepath.split('.')[0]))
# Prepare Data
data = prepare_data(data_path,
                    batch_size=8,
                    imagery_type='landsat8')

For our model, we want out of the 11 bands in Landsat 8 image, weights for the RGB bands to be initialized using ImageNet weights whereas the other bands should have their weights initialized using the red band values. We can do this by setting the "type_init" parameter in the environment as "red band".

arcgis.env.type_init = 'red_band'

Visualize training data

The code below shows a few samples of our data with the same symbology as in ArcGIS Pro.

  • rows: No. of rows we want to see the results for.
  • alpha: controls the opacity of labels (Classified imagery) over the background imagery
  • statistics_type: for dynamic range adjustment
data.show_batch(rows=2, alpha=0.7, statistics_type='DRA')
<Figure size 1500x1000 with 6 Axes>

Here, the light blue patches show the labelled settlements.

Load model architecture

We will be using U-net [4], one of the well recognized image segmentation algorithm, for our land cover classification. U-Net is designed like an auto-encoder. It has an encoding path (“contracting”) paired with a decoding path (“expanding”) which gives it the “U” shape. However, in contrast to the autoencoder, U-Net predicts a pixelwise segmentation map of the input image rather than classifying the input image as a whole. For each pixel in the original image, it asks the question: “To which class does this pixel belong?”. U-Net passes the feature maps from each level of the contracting path over to the analogous level in the expanding path. These are similar to residual connections in a ResNet type model, and allow the classifier to consider features at various scales and complexities to make its decision.

# Create Unet Model
model = UnetClassifier(data)
Downloading: "https://download.pytorch.org/models/resnet34-b627a593.pth" to ~/.cache\torch\hub\checkpoints\resnet34-b627a593.pth
100%|█████████████████████████████████████████████████████████████████████| 83.3M/83.3M [00:07<00:00, 11.0MB/s]

Train the model

By default, the U-Net model uses ImageNet weights which are trained on 3 band imagery but we are using the multispectral support of ArcGIS API for Python to train the model for detecting settlements on 11 band Landsat 8 imagery, so unfreezing will make the backbone trainable so that it can learn to extract features from multispectral data.

Before training the model, let's unfreeze it to train it from scratch.

model.unfreeze()

Learning rate is one of the most important hyperparameters in model training. Here, we explore a range of learning rate to guide us to choose the best one.

# Find Learning Rate
model.lr_find()
<Figure size 640x480 with 1 Axes>
0.00013182567385564074

Based on the learning rate plot above, we can see that the learning rate suggested by lr_find() for our training data is 0.0002754228703338166. We can use it to train our model.

We would fit the model for 100 epochs. One epoch mean the model will see the complete training set once.

# Training
model.fit(epochs=100, lr=0.00022908676527677726);
epochtrain_lossvalid_lossaccuracydicetime
00.9458030.4350320.8431420.32188400:03
10.6353420.1901440.9563040.31848000:02
20.4884400.1510870.9663680.13072900:02
30.4267810.1434720.9693930.09001100:02
40.3835200.1186040.9697020.15490100:02
50.3440740.1634830.9706030.44317300:02
60.3080620.1987240.9618390.50826000:02
70.2824000.1143220.9746240.45269100:02
80.2655420.0913640.9755060.39690900:02
90.2468900.1587010.9709020.59887400:02
100.2307810.0811110.9767370.39251300:02
110.2161690.0811820.9803440.53548300:02
120.2019490.0831620.9816200.59460700:02
130.1894400.0652810.9789790.44155500:02
140.1783170.0666500.9779970.40666500:02
150.1690370.0563570.9816740.57296400:02
160.1610560.0880120.9835980.70985800:02
170.1572070.1658950.9697760.62227500:02
180.1545800.0652130.9766970.31454100:02
190.1487320.1088970.9818490.69432700:02
200.1424210.0546130.9824920.62256800:02
210.1370900.0580680.9845690.69832600:02
220.1345390.0538060.9793080.45251700:02
230.1300900.0486000.9842060.65426000:02
240.1258550.0598890.9853220.73225400:02
250.1213160.0441820.9851120.69658300:02
260.1159730.0496970.9813410.56533800:02
270.1121440.0448090.9858200.72244700:02
280.1074720.0420110.9854060.69446600:02
290.1030980.0589170.9842110.73702500:02
300.1014830.0582960.9845440.73686200:02
310.1007840.0567870.9791680.44411600:02
320.1028960.0549450.9842500.73163000:02
330.1017100.0427270.9835580.63177600:02
340.0991880.0525560.9849080.73828900:02
350.0964300.0403070.9853120.69131800:02
360.0941050.0442960.9857550.74179600:02
370.0910400.0392990.9851870.68225300:02
380.0890000.0667470.9808520.71324700:02
390.0857940.0398110.9853270.69520300:02
400.0837840.0402210.9861940.74629800:03
410.0818600.0420160.9859250.75104800:02
420.0802650.0386480.9855710.69847000:02
430.0808520.0489110.9851270.74670100:02
440.0802310.0415110.9836720.62821700:02
450.0789850.0365950.9862980.72030400:02
460.0782830.0422330.9861290.75822500:02
470.0767070.0411710.9851470.68211100:02
480.0749360.0375360.9865670.74925400:02
490.0743250.0383030.9865170.75144200:02
500.0732700.0365810.9862880.72147700:02
510.0719100.0398280.9863930.75355800:02
520.0708480.0381050.9862730.73071300:02
530.0689700.0398890.9858950.74955900:02
540.0682560.0381180.9865120.75105900:02
550.0674420.0392420.9863630.75428700:02
560.0669690.0364080.9861240.71843100:02
570.0664900.0363110.9863130.72453700:02
580.0653560.0371580.9866970.74963000:02
590.0645980.0373260.9864980.73870000:02
600.0637010.0368860.9865020.73638900:02
610.0629090.0365370.9868960.75245100:02
620.0619840.0359450.9865770.73350600:02
630.0610630.0361440.9868060.74571500:02
640.0606210.0398230.9863230.75740100:02
650.0597870.0383260.9857100.70326200:02
660.0593090.0375630.9860690.71505500:02
670.0583650.0368290.9865920.74152900:02
680.0570360.0368050.9865520.74059500:02
690.0564460.0365050.9863530.72806000:02
700.0575390.0357990.9867570.74253000:02
710.0569470.0359910.9868110.74397500:02
720.0565980.0358010.9865070.73148500:02
730.0562400.0362220.9866720.74058600:02
740.0565800.0361320.9864680.73143900:02
750.0558360.0362520.9861440.71847400:02
760.0547700.0360200.9866470.73904600:02
770.0539460.0368910.9867270.75295200:02
780.0537620.0370660.9866820.75286600:02
790.0540180.0362470.9865370.73627900:02
800.0536090.0380250.9856310.69791900:02
810.0536310.0368790.9860790.71319400:02
820.0534540.0358550.9869360.75410600:02
830.0529800.0379470.9866570.76253000:02
840.0530210.0381650.9866820.76348900:02
850.0533580.0371550.9868460.76117300:02
860.0533170.0357740.9870710.75624700:02
870.0525250.0356540.9869310.75012600:02
880.0525760.0354660.9870010.74978100:02
890.0516480.0355340.9869310.74829600:02
900.0521800.0355520.9868910.74547600:02
910.0515150.0356230.9868810.74433300:02
920.0512330.0356110.9868160.74305900:02
930.0511850.0356910.9868810.74675800:02
940.0522750.0359770.9868210.74781800:02
950.0522340.0359290.9868660.74868300:02
960.0523770.0356580.9869260.74617900:02
970.0529040.0356030.9868160.74237600:02
980.0525560.0356590.9868010.74230800:02
990.0516770.0357590.9868560.74519000:02
model.mIOU()
100.00% [1/1 00:00<00:00]
{'0': 0.9867301422601041, '1': 0.6107132199252355}

Here, we can see loss on both training and validation set decreasing, which shows how well the model generalizes on unseen data thereby preventing overfitting. Also, as we can see our model here is able to classify pixels to settlement or non settlement with an accuracy 0.98. Once we are satisfied with the accuracy, we can save the model.

Save the model

We would now save the model which we just trained as a 'Deep Learning Package' or '.dlpk' format. Deep Learning package is the standard format used to deploy deep learning models on the ArcGIS platform. UnetClassifier models can be deployed by the tool 'Classify Pixels Using Deep Learning' available in ArcGIS Pro as well as ArcGIS Enterprise. For this sample, we will be using this model in ArcGIS Pro to extract settlements.

We will use the save() method to save the model and by default it will be saved to a folder 'models' inside our training data folder itself.

model.save('100e')
Computing model metrics...
WindowsPath('~/AppData/Local/Temp/detecting_settlements_using_supervised_classification_and_deep_learning/models/100e')

Load an intermediate model to train it further

If we need to further train an already saved model we can load it again using the code below and repeat the earlier mentioned steps of training.

# Load Model from previous saved files 
# model.load('100e')

Preview results

The code below will pick a few random samples and show us ground truth and respective model predictions side by side. This allows us to validate the results of your model in the notebook itself. Once satisfied we can save the model and use it further in our workflow.

model.show_results(rows=2)
<Figure size 1000x1000 with 4 Axes>

Deploy model and extract settlements

The model saved in previous step can be used to extract classified raster using 'Classify Pixels Using Deep Learning' tool. Further the classified raster is regularised and finally converted to a vector Polygon layer. The regularisation step uses advanced ArcGIS geoprocessing tools to remove unwanted artifacts in the output.

Generate a classified raster

In this step, we will generate a classified raster using 'Classify Pixels Using Deep Learning' tool available in both ArcGIS Pro and ArcGIS Enterprise.

  • Input Raster: The raster layer from which we want to extract settlements from.
  • Model Definition: It will be located inside the saved model in 'models' folder in '.emd' format.
  • Padding: The 'Input Raster' is tiled and the deep learning model classifies each individual tile separately before producing the final 'Output Classified Raster'. This may lead to unwanted artifacts along the edges of each tile as the model has little context to predict accurately. Padding as the name suggests allows us to supply some extra information along the tile edges, this helps the model to predict better.
  • Cell Size: Should be close to at which we trained the model, we specified that at the Export training data step .
  • Processor Type: This allows to control whether the system's 'GPU' or 'CPU' would be used in to classify pixels, by 'default GPU' will be used if available.
  • Parallel Processing Factor: This allows us to scale this tool, this tool can be scaled on both 'CPU' and 'GPU'. It specifies that the operation would be spread across how many 'cpu cores' in case of cpu based operation or 'no of GPU's' in case of GPU based operation.

Output of this tool will be in form of a 'classified raster' containing both background and Settlements. The pixels having background class are shown in green color and pixels belonging to settlement class are in purple color.

                                        A subset of preditions by our Model

Change the symbology of the raster by setting the second class of the classified pixels to 'No color'.

After this step, we will get a masked raster which will look like the screenshot below.

We can now run 'Raster to Polygon' tool in ArcGIS Pro to convert this masked raster to polygons.

Results obtained from Deep Learning approach are ready to be analyzed.

             Deep Learning Results

Conclusion

This notebook shows how traditional algorithms like Support Vector Machines can be used to generate the necessary quality data for Deep Learning models which after cleaning can serve as the training data required by a DL model for generating accurate results at a much larger extent.

References

[1] https://developers.arcgis.com/python/sample-notebooks/detecting-swimming-pools-using-satellite-image-and-deep-learning/

[2] https://developers.arcgis.com/python/sample-notebooks/extracting-building-footprints-from-drone-data/

[3] https://developers.arcgis.com/python/sample-notebooks/land-cover-classification-using-unet/

[4] Olaf Ronneberger, Philipp Fischer, Thomas Brox: U-Net: Convolutional Networks for Biomedical Image Segmentation, 2015; arXiv:1505.04597.

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.