Wie wir die Anwendung beim NASA Space Apps Challenge Hackathon geschrieben haben

Vom 20. bis 21. Oktober fand in Moskau der internationale Hackathon der NASA Space Apps Challenge statt. Die Organisatoren in Russland waren Leute aus der Russian.Hackers- Community. Im Rahmen der Veranstaltung wurden die Teilnehmer gebeten, 20 Fälle zu verschiedenen Themen zu lösen: von der Aufnahme eines Films über den Hackathon über die Entwicklung von Überwachungsanwendungen bis hin zum Entwurf autonomer Flugzeuge. Die vollständige Liste der Themen kann durch Bezugnahme oder in einem Artikel über Habré studiert werden .

Unser Team „Space Monkeys“, zu dem Oleg Borodin (Front-End-Entwickler im Singularis-Labor), Vladislav Plotnikov (QS-Ingenieur im Singularis-Labor), Jegor Shvetsov, Dmitri Petrow, Juri Bederow und Nikolai Denisenko gehörten, beschlossen, das Problem unter dem Eingängigen zu lösen mit dem Titel „Spot that fire!“, der wie folgt lautet: „ Wenden Sie Crowdsourcing an, damit Menschen zur Erkennung, Bestätigung und Verfolgung von Waldbränden beitragen können. Die Lösung könnte eine mobile oder eine Webanwendung sein.

Aufgrund der Tatsache, dass das Team 5 Entwickler mit Erfahrung in der Entwicklung für verschiedene Plattformen gesammelt hat, wurde sofort entschieden, dass der Prototyp unserer Anwendung für Web- und Mobile-Plattformen implementiert wird.

Welche NASA-Daten haben wir verwendet?


Trotzdem wurde der Hackathon unter der Schirmherrschaft der National Aeronautics and Space Administration abgehalten, daher wäre es falsch, keine offenen Daten aus den Vorratskammern der NASA zu verwenden. Außerdem haben wir sofort den benötigten Active Fire Data- Datensatz gefunden. Dieser Datensatz enthält Informationen zu den Koordinaten von Bränden auf der ganzen Welt (Sie können Informationen zu einem bestimmten Kontinent herunterladen). Die Daten werden täglich aktualisiert (Sie können Daten für 24 Stunden, 48 Stunden, 7 Tage erhalten).


Die Datei enthält Informationen zu folgenden Feldern: Breite, Länge, Helligkeit, Scan, Track, acq_date, acq_time, Satellit, Vertrauen, Version, bright_t31, frp, Tag und Nacht, von denen wir nur die Koordinaten der Feuerpunkte (Breite und Länge) verwendet haben.


Das Prinzip der Anwendung


Da es sich bei der Anwendung um Crowdsourcing handelt, sollte sie idealerweise von einer großen Anzahl von Benutzern verwendet werden. Das Prinzip der Anwendung ist wie folgt:


  1. Nachdem der Benutzer ein Feuer entdeckt hat, macht er ein Bild davon (mit Geo-Tagging) und lädt es über den Dienst. Fotos mit Geo-Tags und Sendekoordinaten werden an den Anwendungsserver gesendet. Die Fotografie kann aus der Web- oder Mobile-Version der Anwendung heruntergeladen werden.

  2. Das resultierende Foto wird auf dem Server von einem trainierten neuronalen Netzwerk verarbeitet, um zu bestätigen, dass das Foto wirklich in Flammen steht. Das Ergebnis des Skripts ist die Genauigkeit der Vorhersage. Wenn> 0,7, wird das Foto wirklich ausgelöst. Andernfalls zeichnen wir diese Informationen nicht auf und bitten den Benutzer, ein weiteres Foto hochzuladen.

  3. Wenn das Bildanalyseskript ein positives Ergebnis liefert, werden die Koordinaten des Geotags mit allen Koordinaten zum Datensatz hinzugefügt. Als nächstes werden die Abstände zwischen dem i- ten Punkt aus dem NASA-Datensatz und dem Punkt vom Benutzer berechnet. Wenn der Abstand zwischen den Punkten 3 km beträgt, wird der Punkt aus dem NASA-Set zum Wörterbuch hinzugefügt. Also gehen wir alle Punkte durch. Geben Sie danach json mit Koordinaten, die die Bedingung erfüllen, an die Clientseite der Anwendung zurück. Wenn durch die gegebene Bedingung keine Koordinaten gefunden werden, geben wir den einzigen Punkt zurück, den wir vom Benutzer erhalten haben.

  4. Wenn der Server ein Array von Punkten zurückgibt, zeichnet der Client-Teil der Anwendung eine Brandzone auf der Karte. Falls der Server einen Punkt zurückgegeben hat, ist dieser auf der Karte mit einer speziellen Beschriftung gekennzeichnet.


Verwendeter Technologie-Stack


Front-End-Teil der Webanwendung


Die Webanwendung, auf die über einen Browser zugegriffen werden kann, konzentrierte sich auf Computerbildschirme und war nicht anpassungsfähig. Die verwendeten Technologien ermöglichten es jedoch, diesen Aspekt für mobile Geräte zu verfeinern. Wir haben den folgenden Technologie-Stack auf der Webseite verwendet:



Arbeitsszenario


Der Benutzer öffnet die Anwendung und sieht seinen Standort:




Initialisierung der Karte und des Benutzer-Geos:


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(); 

Wenn im Radius n (benutzerdefinierte Variable) von Kilometern ein Brand auftritt, wird dieser in Form eines Polygons mit einer Zusammenfassung zusätzlicher Informationen angezeigt:




Der Benutzer wählt einen Brandort auf der Karte aus:




Fire Tag Einstellung:


 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); }); 

Als nächstes lädt der Benutzer ein Foto des Feuers mit ng2-file-upload hoch .


Infolge dieser Aktionen werden die folgenden Daten an den Server übertragen:


  • Benutzerkoordinaten
  • Koordinaten des angegebenen Feuers
  • Feuerfoto

Die Ausgabe der Anwendung ist das Erkennungsergebnis.



Mobile-App-Anwendungen


Verwendete Technologien


  • React native - ein Framework für die Entwicklung plattformübergreifender Anwendungen für iOS und Android
  • Redux - Steuerung des Anwendungsdatenflusses
  • Redux-Saga - Bibliothek mit Nebenwirkungen in Redux

Arbeitsszenario


Feuerfotoauswahl


Kommentar vom Benutzer


Brandzeichen



Backend-Teil der Anwendung


  • Programmiersprache - JAVA 8

  • Cloud-Plattform - Microsoft Azure

  • Webanwendungs-Framework - Play Framework

  • Objektrelationales Mapping - Ebean-Framework


Der Server verfügt über 2 in Python geschriebene Skripte: Predict.py und GetZone.py. Die folgenden Python-Bibliotheken wurden für ihre Arbeit installiert:


  • Pandas - zur Datenverarbeitung und -analyse
  • Geopandas - für die Arbeit mit Geodaten
  • numpy - zum Arbeiten mit mehrdimensionalen Arrays
  • matplotlib - zur Visualisierung von zweidimensionalen (2D) Datengrafiken (3D-Grafiken werden ebenfalls unterstützt)
  • formschön - zur Manipulation und Analyse flacher geometrischer Objekte.

Server-API: fire.iconx.app/api


  • Ladekoordinaten

 post /pictures {} return { id } 

  • Bild hochladen

 post /pictures/:id 

Skript predigen.py


Ein Eingabeskript erhielt ein Bild, eine einfache Bildvorverarbeitung fand statt (mehr dazu im Abschnitt „Modelltraining“) und basierend auf einer gespeicherten Datei mit Gewichten, die sich ebenfalls auf dem Server befindet, wurde eine Vorhersage ausgegeben. Wenn das Modell eine Genauigkeit> 0,7 erzeugt, ist das Feuer behoben, andernfalls - nein.


Das Skript wird auf klassische Weise ausgeführt.

 $ python predict.py image.jpg 

Codeauflistung:
 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)) 



Skript getZone.py


Die Eingabe in das Skript sind die Koordinaten des Punkts, der von der Clientseite der Anwendung stammt. Das Skript verschärft alle Koordinaten der NASA, fügt dieser Datei einen neuen Breiten- und Längengrad hinzu, überschreibt die Originaldatei und sucht nach den nächsten Punkten. Der Abstand zwischen Punkten wird nach der Haversine-Formel berechnet .


Dazu werden Breite und Länge der Punkte in Bogenmaß umgerechnet:


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

Es gibt Unterschiede zwischen Breiten- und Längengrad für jeden der Punkte:


 d_lon = pt2_lon - pt1_lon d_lat = pt2_lat - pt1_lat 

All dies wird in die Haversinformel eingesetzt:


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

Wir nehmen die Wurzel des Berechnungsergebnisses, berechnen den Arkussinus und multiplizieren das Ergebnis mit 2.


 c = 2 * asin(sqrt(a)) 

Die Entfernung ergibt sich aus dem Radius der Erde (6371 km) und dem Ergebnis der vorherigen Berechnung.


Modelltraining


Um das Bild für ein Feuer zu analysieren, brauchten wir einen Trainingssatz mit Fotos mit Feuer. Fotos wurden mit einem Skript von der Website https://www.flickr.com/ gesammelt und manuell markiert.


Der Download erfolgte mit FlikerAPI. Das Skript führte Standardvorverarbeitungsvorgänge mit Bildern durch: Zuschneiden - Quadrat mit Zentrierung (Verhältnis 1: 1) und Größenänderung auf das Format 256 × 256.


Codeauflistung:
 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 


Natürlich wurde die Faltungsarchitektur des neuronalen Netzwerks, in der das vorab trainierte Modell verwendet wurde, verwendet, um mit Bildern zu arbeiten. Die Wahl fiel auf MobileNet (erwartet), weil:


  • Leichtgewicht - Es ist wichtig, dass die Reaktionszeit der Anwendung minimal ist.
  • Schnell - Es ist wichtig, dass die Antwortzeit der Anwendung minimal ist.
  • Genau - MobileNet sagt mit der erforderlichen Genauigkeit voraus.

Nach dem Training ergab das Netzwerk eine Genauigkeit von ~ 0,85.


Um das Modell, das Training und die Vorhersage zu erstellen, wurde eine Reihe von Keras + Tensorflow verwendet . Die Arbeit mit Daten wurde über Pandas durchgeführt .


Da es sich bei dem NASA DataSet um geografische Daten handelt, wollten wir die GeoPandas- Bibliothek verwenden. Diese Bibliothek ist eine Erweiterung der Funktionen von Pandas zur Bereitstellung räumlicher Methoden und Operationen für geometrische Typen. Geometrische Operationen werden durch die formschöne Bibliothek implementiert, arbeiten mit Dateien - Fiona, Graphing - Matplotlib.


Nachdem wir fast anderthalb Tage damit verbracht hatten, diese Bibliothek herauszufinden, gaben wir sie auf, weil wir nicht herausfinden konnten, wo sie uns einen echten Vorteil aus der Arbeit mit ihr verschaffen könnte. Unsere Aufgabe, die Koordinaten zu berechnen, war sehr klein, so dass am Ende alles nativ implementiert wurde.


Was weiter?


Alles, was wir als Ergebnis erhalten haben, ist natürlich eine äußerst instabile und grobe Anwendung, die das Recht hat, abgeschlossen zu werden.


Es ist uns gelungen:


  1. Implementieren Sie Prototypen von Mobil- und Webanwendungen, die Fotos aufnehmen konnten (nur mobile Version), hochladen und an den Server senden konnten. Außerdem werden die Koordinaten für das erfolgreiche Senden an den Server gesendet.
  2. Auf dem Server konnten zwei Skripte bereitgestellt werden, die die Hauptlogik der Anwendung implementieren. Der Fluss der Eingabedaten zu diesen Skripten und der Empfang der Ausgabedaten mit anschließendem Senden an den Client-Teil wurde arrangiert.
  3. Implementieren Sie den echten „Prototyp“ unserer Anwendung.

Wir haben es nicht geschafft, es zu implementieren, aber ich möchte die folgenden Probleme lösen und Funktionen hinzufügen (die Elemente richten sich nach der Priorität der Aufgabe):


  1. Organisieren Sie die Aufzeichnung aller Koordinaten aus dem Datensatz in der Datenbank, um direkt mit der Datenbank zu interagieren.
  2. Organisieren Sie das automatische Hochladen einer neuen Datei von der NASA-Website, d. H. Organisieren Sie automatische tägliche Koordinatenaktualisierungen.
  3. Fügen Sie Benachrichtigungen für Benutzer hinzu, die sich in der Nähe des Feuers befinden.
  4. Registrierung hinzufügen (erforderlich, um den ersten Absatz zu implementieren).
  5. Schreiben Sie den Algorithmus zur Berechnung der Brandzone neu.
  6. Lösen Sie Designaufgaben - bringen Sie Schönheit in die Mobil- und Webversionen der Anwendung.

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


All Articles