Au moment où nous écrivions l'application au hackathon du NASA Space Apps Challenge

Du 20 au 21 octobre, le hackathon international NASA Space Apps Challenge s'est tenu à Moscou. Ses organisateurs en Russie étaient des gars de la communauté des hackers russes . Dans le cadre de l'événement, les participants ont été invités à résoudre 20 cas sur différents sujets: du tournage d'un film sur le hackathon au développement d'applications de surveillance et à la conception d'avions autonomes. La liste complète des sujets peut être étudiée par référence ou dans un article sur Habré .

Notre équipe «Space Monkeys», qui comprenait Oleg Borodin (développeur front-end au laboratoire Singularis), Vladislav Plotnikov (ingénieur QA au laboratoire Singularis), Yegor Shvetsov, Dmitry Petrov, Yuri Bederov et Nikolai Denisenko, a décidé de résoudre le problème sous le signe du catchy intitulé «Spot that fire!», qui est libellé comme suit: « Appliquez le crowdsourcing afin que les gens puissent contribuer à la détection, à la confirmation et au suivi des incendies de forêt. La solution pourrait être une application mobile ou Web. "

En raison du fait que l'équipe a rassemblé 5 développeurs ayant une expérience dans le développement pour diverses plates-formes, il a été immédiatement décidé que le prototype de notre application serait mis en œuvre pour les plates-formes Web et mobiles.

Quelles données de la NASA avons-nous utilisées?


Pourtant, le hackathon a eu lieu sous les auspices de la National Aeronautics and Space Administration, il serait donc faux de ne pas utiliser les données ouvertes des garde-manger de la NASA. De plus, nous avons immédiatement trouvé l'ensemble de données Active Fire Data dont nous avions besoin. Cet ensemble de données contient des informations sur les coordonnées des incendies dans le monde (vous pouvez télécharger des informations sur un continent spécifique). Les données sont mises à jour quotidiennement (vous pouvez recevoir des données pendant 24 heures, 48 ​​heures, 7 jours).


Le fichier contient des informations sur les champs suivants: latitude, longitude, luminosité, scan, piste, acq_date, acq_time, satellite, confidence, version, bright_t31, frp, daynight, dont nous n'avons utilisé que les coordonnées des points de feu (latitude et longitude).


Le principe de l'application


Étant donné que l'application est externalisée, elle devrait idéalement être utilisée par un grand nombre d'utilisateurs. Le principe de l'application est le suivant:


  1. L'utilisateur, après avoir détecté un incendie, en prend une photo (avec géolocalisation) et la charge à l'aide du service. Les photos avec des géo-tags et les coordonnées d'envoi vont au serveur d'application. La photographie peut être téléchargée à partir de la version Web ou mobile de l'application.

  2. La photo résultante est traitée sur le serveur par un réseau de neurones formé pour confirmer que la photo est vraiment en feu. Le résultat du script est la précision de la prédiction, si> 0,7, alors la photo se déclenche vraiment. Sinon, nous n'enregistrons pas ces informations et demandons à l'utilisateur de télécharger une autre photo.

  3. Si le script d'analyse d'image a donné un résultat positif, les coordonnées du géomarqueur sont ajoutées au jeu de données avec toutes les coordonnées. Ensuite, les distances entre le i- ème point de l'ensemble de données de la NASA et le point de l'utilisateur sont calculées. Si la distance entre les points est 3 km, le point de l'ensemble de la NASA est ajouté au dictionnaire. Nous passons donc par tous les points. Après cela, renvoyez json avec des coordonnées satisfaisant la condition du côté client de l'application. S'il n'y a pas de coordonnées trouvées par la condition donnée, alors nous retournons le seul point que nous avons reçu de l'utilisateur.

  4. Si le serveur renvoie un tableau de points, la partie cliente de l'application dessine une zone d'incendie sur la carte. Si le serveur a renvoyé un point, il est marqué sur la carte avec une étiquette spéciale.


Pile technologique utilisée


Partie front-end de l'application web


L'application web, accessible depuis un navigateur, se focalisait sur les écrans d'ordinateurs, et n'était pas adaptative, cependant, les technologies utilisées permettaient facilement d'affiner cet aspect pour les appareils mobiles. Nous avons utilisé la pile technologique suivante du côté Web:



Scénario de travail


L'utilisateur ouvre l'application et voit son emplacement:




Initialisation de la carte et de la géographie de l'utilisateur:


this.map = L.map('map').setView([latitude, longitude], 17); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '& copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }).addTo(this.map); L.circle([latitude, longitude]).addTo(this.map) .bindPopup('You are here') .openPopup(); 

S'il y a un feu dans le rayon n (variable personnalisée) de kilomètres, il sera affiché sous la forme d'un polygone avec un résumé des informations supplémentaires:




L'utilisateur sélectionne un emplacement d'incendie sur la carte:




Réglage de la balise incendie:


 let marker; this.map.on('click', function (e) { if (marker) { self.map.removeLayer(marker); } marker = L.circle([e.latlng.lat, e.latlng.lng], { color: 'red', fillColor: '#f03', fillOpacity: 0.5, radius: 15 }).addTo(self.map) .bindPopup(' ') .openPopup(); self.appService.coordinatesStorage.latitude = e.latlng.lat; self.appService.coordinatesStorage.longitude = e.latlng.lng; console.log('fire', self.appService.coordinatesStorage); }); 

Ensuite, l'utilisateur télécharge une photo de l'incendie à l'aide de ng2-file-upload .


À la suite de ces actions, les données suivantes sont transférées au serveur:


  • coordonnées utilisateur
  • coordonnées du feu spécifié
  • photo de feu

La sortie de l'application est le résultat de la reconnaissance.



Applications d'application mobile


Technologies utilisées


  • React native - un cadre pour développer des applications multiplateformes pour iOS et Android
  • Redux - contrôle du flux de données des applications
  • Redux-saga - bibliothèque utilisant des effets secondaires dans Redux

Scénario de travail


Sélection de photos d'incendie


Commentaire de l'utilisateur


Marque de feu



Partie back-end de l'application


  • Langage de programmation - JAVA 8

  • Plateforme cloud - Microsoft Azure

  • Framework d'application Web - Play Framework

  • Cartographie objet-relationnelle - framework Ebean


Le serveur a 2 scripts écrits en Python: predite.py et getZone.py, les bibliothèques Python suivantes ont été installées pour leur travail:


  • pandas - pour le traitement et l'analyse des données
  • géopandas - pour travailler avec des géodonnées
  • numpy - pour travailler avec des tableaux multidimensionnels
  • matplotlib - pour la visualisation des données graphiques bidimensionnelles (2D) (les graphiques 3D sont également pris en charge)
  • bien fait - pour la manipulation et l'analyse d'objets géométriques plats.

API du serveur: fire.iconx.app/api


  • coordonnées de chargement

 post /pictures {} return { id } 

  • télécharger une photo

 post /pictures/:id 

Script Predict.py


Un script d'entrée a reçu une image, un simple prétraitement de l'image a eu lieu (plus à ce sujet dans la section «Formation de modèle») et basé sur le fichier enregistré avec des poids, qui se trouve également sur le serveur, une prédiction a été émise. Si le modèle produit une précision> 0,7, alors le feu est fixe, sinon - non.


Le script est exécuté de manière classique.

 $ python predict.py image.jpg 

Liste des codes:
 import keras import sys from keras.layers import Dense from keras.models import model_from_json from sklearn.externals import joblib from PIL import Image import numpy as np from keras import models, layers, optimizers from keras.applications import MobileNet from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten from keras.layers import Conv2D, MaxPooling2D def crop_resize(img_path, img_size_square): # Get dimensions mysize = img_size_square image = Image.open(img_path) width, height = image.size # resize if (width and height) >= img_size_square: if width > height: wpercent = (mysize/float(image.size[1])) vsize = int((float(image.size[0])*float(wpercent))) image = image.resize((vsize, mysize), Image.ANTIALIAS) else: wpercent = (mysize/float(image.size[0])) hsize = int((float(image.size[1])*float(wpercent))) image = image.resize((mysize, hsize), Image.ANTIALIAS) # crop width, height = image.size left = (width - mysize)/2 top = (height - mysize)/2 right = (width + mysize)/2 bottom = (height + mysize)/2 image=image.crop((left, top, right, bottom)) return image conv_base = MobileNet(weights='imagenet', include_top=False, input_shape=(224, 224, 3)) def build_model(): model = models.Sequential() model.add(conv_base) model.add(layers.Flatten()) model.add(layers.Dense(256, activation='relu')) model.add(layers.Dense(64, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=2e-5), metrics=['acc']) return model image=crop_resize(sys.argv[1],224) image = np.reshape(image,[1,224,224,3]) #Loading models and text processing model = build_model() print('building a model') model.load_weights('./models/mobile_weights.h5') print('model loaded') pred_cat=model.predict(image) if pred_cat > 0.7: print('fire {}'.format(pred_cat)) else: print('no fire {}'.format(pred_cat)) 



Script getZone.py


L'entrée du script correspond aux coordonnées du point provenant du côté client de l'application. Le script resserre toutes les coordonnées de la NASA, ajoute une nouvelle latitude et longitude à ce fichier, écrase le fichier d'origine et commence à rechercher les points les plus proches. La distance entre les points est calculée à l'aide de la formule Haversine .


Pour ce faire, la latitude et la longitude des points sont converties en radians:


 pt1_lon, pt1_lat, pt2_lon, pt2_lat = map(radians, [pt1_lon, pt1_lat, pt2_lon, pt2_lat]) 

Il existe des différences entre la latitude et la longitude pour chacun des points:


 d_lon = pt2_lon - pt1_lon d_lat = pt2_lat - pt1_lat 

Tout cela est substitué dans la formule haversine:


 a = sin(d_lat/2)**2 + cos(pt1_lat) * cos(pt2_lat) * sin(d_lon/2)**2 

Nous prenons la racine du résultat du calcul, calculons l'arc sinus et multiplions le résultat par 2.


 c = 2 * asin(sqrt(a)) 

La distance sera le produit du rayon de la Terre (6371 km) et le résultat du calcul précédent.


Formation modèle


Pour analyser l'image d'un incendie, nous avions besoin d'un ensemble de photos de formation avec des incendies. Les photos ont été collectées par un script du site https://www.flickr.com/ et étiquetées manuellement.


Le téléchargement a eu lieu à l'aide de FlikerAPI. Le script a effectué des opérations de prétraitement standard avec des images: recadrage - carré avec centrage (rapport 1: 1) et redimensionnement au format 256 × 256.


Liste des codes:
 import flickrapi import urllib.request from PIL import Image import pathlib import os from tqdm import tqdm # Flickr api access key flickr=flickrapi.FlickrAPI('your API key', 'your secret key', cache=True) def get_links(): search_term = input("Input keywords for images: ") keyword = search_term max_pics=2000 photos = flickr.walk(text=keyword, tag_mode='all', tags=keyword, extras='url_c', per_page=500, # mb you can try different numbers.. sort='relevance') urls = [] for i, photo in enumerate(photos): url = photo.get('url_c') if url is not None: urls.append(url) if i > max_pics: break num_of_pics=len(urls) print('total urls:',len(urls)) # print number of images available for a keywords return urls, keyword, num_of_pics #resizing and cropping output images will be besquare def crop_resize(img_path, img_size_square): # Get dimensions mysize = img_size_square image = Image.open(img_path) width, height = image.size # resize if (width and height) >= img_size_square: if width > height: wpercent = (mysize/float(image.size[1])) vsize = int((float(image.size[0])*float(wpercent))) image = image.resize((vsize, mysize), Image.ANTIALIAS) else: wpercent = (mysize/float(image.size[0])) hsize = int((float(image.size[1])*float(wpercent))) image = image.resize((mysize, hsize), Image.ANTIALIAS) # crop width, height = image.size left = (width - mysize)/2 top = (height - mysize)/2 right = (width + mysize)/2 bottom = (height + mysize)/2 image=image.crop((left, top, right, bottom)) return image def download_images(urls_,keyword_, num_of_pics_): num_of_pics=num_of_pics_ keyword=keyword_ urls=urls_ i=0 base_path='./flickr_data/' # your base folder to save pics for item in tqdm(urls): name=''.join([keyword,'_',str(i),'.jpg']) i+=1 keyword_=''.join([keyword,'_',str(num_of_pics)]) dir_path= os.path.join(base_path,keyword_) file_path=os.path.join(dir_path,name) pathlib.Path(dir_path).mkdir(parents=True, exist_ok=True) urllib.request.urlretrieve(item, file_path) resized_img=crop_resize(file_path, 256) #set output image size try: resized_img.save(file_path) except: pass urls, keyword, num_of_pics =get_links() continue = input("continue or try other keywords (y,n): ") if continue =='y': download_images(urls, keyword, num_of_pics) elif continue =='n': get_links() else: pass 


Naturellement, l'architecture convolutionnelle du réseau de neurones, dans laquelle le modèle pré-formé a été utilisé, a été utilisée pour travailler avec des images. Le choix s'est porté sur (attendu) sur MobileNet , car:


  • Léger - Il est important que le temps de réponse de l'application soit minimal.
  • Rapide - Il est important que le temps de réponse de l'application soit minimal.
  • Exactement - MobileNet prédit avec la précision nécessaire.

Après la formation, le réseau a produit une précision de ~ 0,85.


Pour construire le modèle, la formation et la prédiction, un tas de Keras + Tensorflow a été utilisé . Le travail avec les données a été effectué par le biais des Pandas .


Le NASS DataSet étant des données géographiques, nous voulions utiliser la bibliothèque GeoPandas . Cette bibliothèque est une extension des capacités de Pandas pour fournir des méthodes spatiales et des opérations sur des types géométriques. Les opérations géométriques sont implémentées à travers la bibliothèque bien faite, travailler avec des fichiers - fiona, graphing - matplotlib.


Après avoir passé près d'un jour et demi à essayer de comprendre cette bibliothèque, nous l'avons abandonnée car nous ne pouvions pas trouver où elle pourrait nous donner un réel avantage de travailler avec elle. Notre tâche de calcul des coordonnées était très petite, donc au final, tout a été implémenté nativement.


Et ensuite?


Naturellement, tout ce que nous avons obtenu est une application extrêmement instable et brute, qui a le droit d'être finalisée.


Nous avons réussi:


  1. Mettre en œuvre des prototypes d'applications mobiles et Web capables de prendre des photos (version mobile uniquement), de les télécharger et de les envoyer au serveur. De plus, les coordonnées d'envoi parviennent avec succès au serveur.
  2. Sur le serveur, il a été possible de déployer 2 scripts qui implémentent la logique principale de l'application. Le flux des données d'entrée vers ces scripts et la réception des données de sortie avec l'envoi ultérieur à la partie client a été organisé.
  3. Implémentez le véritable «prototype» de notre application.

Nous n'avons pas réussi à l'implémenter, mais je voudrais résoudre les problèmes suivants et ajouter des fonctionnalités (les éléments vont selon la priorité de la tâche):


  1. Organisez l'enregistrement de toutes les coordonnées du jeu de données vers la base de données afin d'interagir directement avec la base de données.
  2. Organisez le téléchargement automatique d'un nouveau fichier depuis le site Web de la NASA, c'est-à-dire organiser des mises à jour quotidiennes automatiques des coordonnées.
  3. Ajoutez une notification aux utilisateurs situés dans la zone proche du feu.
  4. Ajouter l'enregistrement (nécessaire pour mettre en œuvre le premier paragraphe).
  5. Réécrivez l'algorithme de calcul de la zone d'incendie.
  6. Résolvez les tâches de conception - apportez de la beauté aux versions mobile et Web de l'application.

Source: https://habr.com/ru/post/fr430480/


All Articles