├── .gitignore
├── LICENCE
├── Land_Cover_Classification_using_Sentinel_2_Satellite_Imagery_and_Deep_Learning.ipynb
├── README.md
├── app.py
├── config.py
├── data
└── reference_images
│ ├── Accuracy_without_scheduler.png
│ ├── change_1.png
│ ├── change_2.png
│ ├── change_3.png
│ ├── confusion_matrix.png
│ ├── loss_without_scheduler.png
│ ├── overview.png
│ ├── sample_pred_2.png
│ ├── sample_pred_3.png
│ └── sampple_pred_1.png
├── dataset.py
├── model.py
└── predict.py
/.gitignore:
--------------------------------------------------------------------------------
1 | data/ds/*
2 | data/Image_Dataset/*
3 | data/Dataset.zip
4 | data/Dataset/*
5 | data/model/*
6 |
7 | # Byte-compiled / optimized / DLL files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 |
12 | # C extensions
13 | *.so
14 |
15 | # Distribution / packaging
16 | .Python
17 | build/
18 | develop-eggs/
19 | dist/
20 | downloads/
21 | eggs/
22 | .eggs/
23 | lib/
24 | lib64/
25 | parts/
26 | sdist/
27 | var/
28 | wheels/
29 | share/python-wheels/
30 | *.egg-info/
31 | .installed.cfg
32 | *.egg
33 | MANIFEST
34 |
35 | # PyInstaller
36 | # Usually these files are written by a python script from a template
37 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
38 | *.manifest
39 | *.spec
40 |
41 | # Installer logs
42 | pip-log.txt
43 | pip-delete-this-directory.txt
44 |
45 | # Unit test / coverage reports
46 | htmlcov/
47 | .tox/
48 | .nox/
49 | .coverage
50 | .coverage.*
51 | .cache
52 | nosetests.xml
53 | coverage.xml
54 | *.cover
55 | *.py,cover
56 | .hypothesis/
57 | .pytest_cache/
58 | cover/
59 |
60 | # Translations
61 | *.mo
62 | *.pot
63 |
64 | # Django stuff:
65 | *.log
66 | local_settings.py
67 | db.sqlite3
68 | db.sqlite3-journal
69 |
70 | # Flask stuff:
71 | instance/
72 | .webassets-cache
73 |
74 | # Scrapy stuff:
75 | .scrapy
76 |
77 | # Sphinx documentation
78 | docs/_build/
79 |
80 | # PyBuilder
81 | .pybuilder/
82 | target/
83 |
84 | # Jupyter Notebook
85 | .ipynb_checkpoints
86 |
87 | # IPython
88 | profile_default/
89 | ipython_config.py
90 |
91 | # pyenv
92 | # For a library or package, you might want to ignore these files since the code is
93 | # intended to run in multiple environments; otherwise, check them in:
94 | # .python-version
95 |
96 | # pipenv
97 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
98 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
99 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
100 | # install all needed dependencies.
101 | #Pipfile.lock
102 |
103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
104 | __pypackages__/
105 |
106 | # Celery stuff
107 | celerybeat-schedule
108 | celerybeat.pid
109 |
110 | # SageMath parsed files
111 | *.sage.py
112 |
113 | # Environments
114 | .env
115 | .venv
116 | env/
117 | venv/
118 | ENV/
119 | env.bak/
120 | venv.bak/
121 |
122 | # Spyder project settings
123 | .spyderproject
124 | .spyproject
125 |
126 | # Rope project settings
127 | .ropeproject
128 |
129 | # mkdocs documentation
130 | /site
131 |
132 | # mypy
133 | .mypy_cache/
134 | .dmypy.json
135 | dmypy.json
136 |
137 | # Pyre type checker
138 | .pyre/
139 |
140 | # pytype static type analyzer
141 | .pytype/
142 |
143 | # Cython debug symbols
144 | cython_debug/
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 raoofnaushad
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Land Cover and Land Use Classification using Sentinel-2 Satellite Imagery With Deep Learning
2 | ----------------------------------------------------------------------
3 | __This work has been published on <> [Sensors - MDPI](https://www.mdpi.com/1424-8220/21/23/8083)__
4 |
5 |
6 | __For better understanding whole idea <> [Check out this blog](https://medium.com/datadriveninvestor/applying-deep-learning-on-satellite-imagery-classification-5f2588b932c1)__
7 |
8 | For implementation:
9 |
10 | * Run the notebook to generate the model.
11 | * Dataset can be downloaded from the link [here](https://github.com/phelber/eurosat).
12 | * Save model in the Models directory
13 | * Install required packages
14 | * Run app.py (send an image file as a request to the classify end poing and will get the class for it).
15 |
16 |
17 | ### Scenario
18 | Many government programs are taking enormous efforts to make satellite images free and open sourced inorder to bring in innovation and entrepreunership. This is being used by many domains and are coming up with good results. Inorder to get great insights and knowledge from this satellite data we have to segment and understand it for further studies. Such type of a task is Landcover classification which come up and automate the process of identifying how the land area is used. We have seen a great spike in the growth of Machine learning and Artificial intelligence. Almost all domain in the world is using Deep learning techniques to improve the performance and are benefiting from this. So here we try to use deep learning methods to work with land cover classification.
19 |
20 |
21 | ### Overview
22 |
23 | 
24 | [Source](https://arxiv.org/pdf/1709.00029.pdf)
25 |
26 | A satellite scans the Earth to acquire images of it. Patches extracted out of these images are used for classification.
27 | The aim is to automatically provide labels describing the represented physical land type or how the land is used. For this
28 | purpose, an image patch is feed into a classifier, in this illustration a neural network, and the classifier outputs the class shown
29 | on the image patch.
30 |
31 | This satellite conver 13 spectral bands, where the three bands B01, B09 and B10 are intended to be used for the correction of atmospheric effects (e.g., aerosols, cirrus or water vapor). The remaining bands are
32 | primarily intended to identify and monitor land use and land cover classes. Each satellite will deliver imagery for at least 7 years with a spatial resolution of up to 10 meters per pixel.
33 |
34 | In order to improve the chance of getting valuable image patches, they have selected satellite images with a low cloud level. Besides the possibility to generate a cloud mask, ESA provides a cloud level value for each satellite image allowing to quickly select images with a low percentage of clouds covering the land scene.
35 |
36 |
37 | ### Results
38 |
39 | Below shows the graph of __accuracy__ and __losses__ against __epochs__ for both training and testing data.
40 | 
41 |
42 | 
43 |
44 | Some __prediction results__ are shown below
45 |
46 | 
47 |
48 | 
49 |
50 | 
51 |
52 | __Confusion Matrix__
53 |
54 | 
55 |
56 |
57 | ### Dataset
58 | 1. EuroSAT dataset is open sourced.
59 | 2. It consist of satellite images RGB and multi spectral - covering 13 spectral bands (including visible, newar infrared, shortwave infrared) with 10 unique classes.
60 | 3. It consist of 27000 labeled and geo-referenced images.
61 | 4. The dataset is published and benchmarked with CNN by a paper titled EuroSAT: A Novel Dataset and Deep Learning Benchmark for Land Use and Land Cover Classification and they have made dataset public through this repo.
62 |
63 | The authors were able to sort out some common issues that come up with studying satellite data and were able to sort it out for land use and land cover classification.
64 |
65 | * Cloud appearence
66 | * Color casting due to atmospheric effects
67 | * Dead/pixels
68 | * Ice or snow
69 |
70 |
71 |
72 | ##### Band Evaluation
73 | In order to evaluate the performance of deep CNNs in a multi spectral dataset using single-band images as well shortwave-infrared and color infrared band combinations, we used the pretrained wide ResNet-50 with a fixed training-test split to compare the performance of the different spectral bands. For evaluating single-band image they inserted the information from a single spectral band on all three input channels. Bands with a lower spatial resolution have been up sampled to 10 meters per pixel using cubic-spline interpolation.
74 |
75 |
76 | ### Working
77 |
78 | Here we use deep learning along with transfer learning to improve the accuracy and also make the training faster. We will work on __Pytorch__ as the framework for our deep learning model.
79 |
80 | We can get the dataset from [here](https://github.com/phelber/eurosat). Dataset is provided in both `.tiff` format and also `image` format. `.tiff` format can be used to extract other spectral information since they provided multi spectral data with 13 different spectrum, We use RGB because pretrained models are already trained in RGB images so we can take the image dataset and work on it.
81 |
82 |
83 | We have to create the dataset class, transformations and dataloaders. We create a EuroSAT dataset class inherited from torch dataset library. Creating our own method to get data which takes the name of the file and taking it from each directory. We also add transforms as some data augmentation and preprocessing stepfor using it with the pretrained model. Then we create dataset and data loader for training and validation with the preferred batch_size, feel free to experiment with more transformation which might help you to improve accuracy.
84 |
85 | For creating the model. We use transfer learning, here we use __wide_resnet50_2__ model as a pretrained model which is already trained on a huge image dataset. We change the classification layer of wide_resnet50_2 with some additional sequential layers for fine tuning. This added layer includes Linear layer(n_inputs, 256) => ReLU layer => Dropout Layer => Linear Layer (256, num_classes) => LogSoftmax layer In the model class we also include freeze and unfreeze function in order to select training the whole architecture or only the classification layer we added.
86 |
87 | We trained the model for 10 epochs and save the best model based on the accuracy and losses.
88 |
89 | ### Applications
90 | Since the Sentinel-2 satellite constellation will scan the Earth's land surface for about the next 20–30 years on a repeat cycle of about five days, a trained classifier can be used for monitoring land surfaces and detect changes in land use or land cover. These land cover changes can be used for various studies and purposes. In future may be we can add a real time open sourced web network for everyone in the world to see how the world around them changes in years. Some changes that are understood in various years on the same place is shown below. This is also part of the paper.
91 |
92 |
93 | 
94 | [Source](https://arxiv.org/pdf/1709.00029.pdf)
95 |
96 |
97 | 
98 | [Source](https://arxiv.org/pdf/1709.00029.pdf)
99 |
100 | 
101 | [Source](https://arxiv.org/pdf/1709.00029.pdf)
102 |
103 |
104 | ### Challenges
105 |
106 | There are lots of challenges while evaluating raw images from satellite for prediction which includes Cloud appearence, Color casting due to atmospheric effects, Dead/pixels, Ice or snow. More than that a classification system trained with 64x64 image patches does not allow a finely graduated per-pixel segmentation. Also when there is mixed elemets in the same image patches that can also lead to trouble.
107 |
108 |
109 |
110 | ### Reference
111 |
112 | [EuroSAT: A Novel Dataset and Deep Learning
113 | Benchmark for Land Use and Land Cover
114 | Classification](https://arxiv.org/abs/1709.00029)
115 |
116 |
117 |
118 | @inproceedings{helber2018introducing,
119 | title={Introducing EuroSAT: A Novel Dataset and Deep Learning Benchmark for Land Use and Land Cover Classification},
120 | author={Helber, Patrick and Bischke, Benjamin and Dengel, Andreas and Borth, Damian},
121 | booktitle={IGARSS 2018-2018 IEEE International Geoscience and Remote Sensing Symposium},
122 | pages={204--207},
123 | year={2018},
124 | organization={IEEE}
125 | }
126 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, jsonify, request
2 | import predict
3 |
4 | app = Flask(__name__)
5 |
6 | MODEL = None
7 | DEVICE = None
8 |
9 |
10 | @app.route('/', methods=['GET'])
11 | def check():
12 | response = {
13 | "message": "EuroSAT satellite image predictor"
14 | }
15 | return jsonify(response)
16 |
17 |
18 | @app.route('/classify', methods=['POST'])
19 | def classify():
20 | response = request.files['file']
21 | file_name = response.filename
22 | response.save('check/image.jpg')
23 | result = predict.predict_single()
24 | response = {
25 | "Type": result
26 | }
27 | return jsonify(response)
28 |
29 |
30 |
31 | if __name__ == "__main__":
32 | app.run(debug=True, port=5000)
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | IDX_CLASS_LABELS = {
2 | 0: 'AnnualCrop',
3 | 1: 'Forest',
4 | 2: 'HerbaceousVegetation',
5 | 3: 'Highway',
6 | 4: 'Industrial',
7 | 5: 'Pasture',
8 | 6: 'PermanentCrop',
9 | 7: 'Residential',
10 | 8: 'River',
11 | 9: 'SeaLake'
12 | }
13 |
14 | PATH = 'check/image.jpg'
15 | MODEL_PATH = 'data/model'
16 | NUM_CLASSES = 10
17 |
--------------------------------------------------------------------------------
/data/reference_images/Accuracy_without_scheduler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/Accuracy_without_scheduler.png
--------------------------------------------------------------------------------
/data/reference_images/change_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/change_1.png
--------------------------------------------------------------------------------
/data/reference_images/change_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/change_2.png
--------------------------------------------------------------------------------
/data/reference_images/change_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/change_3.png
--------------------------------------------------------------------------------
/data/reference_images/confusion_matrix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/confusion_matrix.png
--------------------------------------------------------------------------------
/data/reference_images/loss_without_scheduler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/loss_without_scheduler.png
--------------------------------------------------------------------------------
/data/reference_images/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/overview.png
--------------------------------------------------------------------------------
/data/reference_images/sample_pred_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/sample_pred_2.png
--------------------------------------------------------------------------------
/data/reference_images/sample_pred_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/sample_pred_3.png
--------------------------------------------------------------------------------
/data/reference_images/sampple_pred_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raoofnaushad/Land-Cover-Classification-using-Sentinel-2-Dataset/6dc5f8bc1fb7a6a2ec7faa0c85371635840de32c/data/reference_images/sampple_pred_1.png
--------------------------------------------------------------------------------
/dataset.py:
--------------------------------------------------------------------------------
1 | import torch
2 | from torchvision.transforms import transforms
3 |
4 | import config
5 | from PIL import Image
6 | import matplotlib.pyplot as plt
7 | import numpy as np
8 |
9 | def get_image(path):
10 | img = Image.open(path)
11 | # img.show()
12 | transform = transforms.Compose([
13 | transforms.Resize(size=(224, 224)),
14 | # transforms.CenterCrop(224),
15 | transforms.ToTensor(),
16 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
17 | ])
18 |
19 | img = transform(img)
20 | return img
21 |
22 |
23 |
24 |
25 | def get_device():
26 | if torch.cuda.is_available():
27 | return torch.device('cuda')
28 | else:
29 | return torch.device('cpu')
30 | def to_device(data, device):
31 | if isinstance(data, (list, tuple)):
32 | return [to_device(x, device) for x in data]
33 | return data.to(device, non_blocking=True)
34 |
35 | class DeviceDataLoader():
36 | def __init__(self, dl, device):
37 | self.dl = dl
38 | self.device = device
39 |
40 | def __iter__(self):
41 | for b in self.dl:
42 | yield to_device(b, self.device)
43 |
44 | def __len__(self):
45 | return len(self.dl)
46 |
--------------------------------------------------------------------------------
/model.py:
--------------------------------------------------------------------------------
1 | import torch.nn as nn
2 | import torchvision.models as models
3 | import torch
4 | import config
5 |
6 |
7 | class LULC_Model(nn.Module):
8 | def __init__(self):
9 | super().__init__()
10 | self.network = models.wide_resnet50_2(pretrained=True)
11 | n_inputs = self.network.fc.in_features
12 | self.network.fc = nn.Sequential(
13 | nn.Linear(n_inputs, 256),
14 | nn.ReLU(),
15 | nn.Dropout(0.5),
16 | nn.Linear(256, config.NUM_CLASSES),
17 | nn.LogSoftmax(dim=1)
18 | )
19 | def forward(self, xb):
20 | return self.network(xb)
21 |
22 | def freeze(self):
23 | for param in self.network.parameters():
24 | param.require_grad=False
25 | for param in self.network.fc.parameters():
26 | param.require_grad=True
27 | def unfreeze(self):
28 | for param in self.network.parameters():
29 | param.require_grad=True
30 |
31 | def get_model():
32 | use_cuda = torch.cuda.is_available()
33 | DEVICE = torch.device('cuda' if use_cuda else 'cpu')
34 | model = LULC_Model()
35 | model.load_state_dict(torch.load(config.MODEL_PATH, map_location=lambda storage, loc: storage))
36 |
37 | return model
--------------------------------------------------------------------------------
/predict.py:
--------------------------------------------------------------------------------
1 | from dataset import to_device, get_device, get_image
2 | from model import get_model
3 | import torch
4 | import config
5 |
6 |
7 |
8 | def predict_single():
9 | image = get_image(config.PATH)
10 | device = get_device()
11 | model = get_model()
12 | model.eval()
13 | xb = image.unsqueeze(0)
14 | xb = to_device(xb, device)
15 | preds = model(xb)
16 | _, prediction = torch.max(preds.cpu().detach(), dim=1)
17 | return decode_target(int(prediction), text_labels=True)
18 |
19 |
20 |
21 | def decode_target(target, text_labels=True):
22 | result = []
23 | if text_labels:
24 | return config.idx_class_labels[target]
25 | else:
26 | return target
27 |
--------------------------------------------------------------------------------