├── .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 | ![Overview Image](data/reference_images/overview.png)
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 | ![Training and Validation loss with Epochs](data/reference_images/loss_without_scheduler.png) 41 | 42 | ![Training and Validation Accuracy with Epochs](data/reference_images/Accuracy_without_scheduler.png) 43 | 44 | Some __prediction results__ are shown below 45 | 46 | ![result-1](data/reference_images/sampple_pred_1.png) 47 | 48 | ![result-2](data/reference_images/sample_pred_2.png) 49 | 50 | ![result-3](data/reference_images/sample_pred_3.png) 51 | 52 | __Confusion Matrix__ 53 | 54 | ![Confusion_Matrix](data/reference_images/confusion_matrix.png) 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 | ![Change Detection-1](data/reference_images/change_1.png)
94 | [Source](https://arxiv.org/pdf/1709.00029.pdf) 95 | 96 | 97 | ![Change Detection-2](data/reference_images/change_2.png)
98 | [Source](https://arxiv.org/pdf/1709.00029.pdf) 99 | 100 | ![Change Detection-3](data/reference_images/change_3.png)
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 | --------------------------------------------------------------------------------