Wie wir die Anwendung über die NASA Space Apps Challenge des Hackathons geschrieben haben

    Vom 20. bis 21. Oktober fand in Moskau der internationale Hackathon NASA Space Apps Challenge statt . Es wurde in Russland von Jungs aus der Russian.Hackers Community organisiert . Während der Veranstaltung wurden die Teilnehmer gebeten, 20 Fälle zu verschiedenen Themen zu lösen: Von der Dreharbeiten über den Hackathon über die Entwicklung von Überwachungsanwendungen bis hin zum Entwurf autonomer Flugzeuge. Eine vollständige Liste der Themen finden Sie unter dem Link oder im Artikel über Habré .

    Unser Team „Space Monkeys“, zu dem Oleg Borodin (Front-End-Entwickler im Singularis-Labor), Vladislav Plotnikov (QA-Ingenieur im Singularis-Labor), Yegor Shvetsov, Dmitry Petrov, Yuri Bederov und Nikolai Denisenko gehörten, entschied sich für die Lösung des Problems mit dem Titel "Spot the fire!", der wie folgt formuliert ist: " Wenden Sie Crowdsourcing an, damit Menschen zur Erkennung, Bestätigung und Verfolgung von Waldbränden beitragen können. Die Lösung kann eine mobile Anwendung oder eine Webanwendung sein. "

    Aufgrund der Tatsache , dass das Team 5 Entwickler mit Erfahrung gesammelt hat , für verschiedene Plattformen zu entwickeln, sobald es wurde beschlossen , dass der Prototyp unserer Anwendung wird von der Web- und Mobile - Plattformen implementiert werden.

    Welche NASA-Daten haben wir verwendet?


    Der Hackathon wurde jedoch unter der Schirmherrschaft der National Aeronautics and Space Administration durchgeführt. Daher wäre es falsch, offene Daten aus NASA-Lagerräumen nicht zu verwenden. Außerdem haben wir sofort das benötigte Active Fire Data- Dataset gefunden . Dieser Datensatz enthält Informationen zu den Koordinaten von Bränden in 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 und 7 Tage abrufen).


    Die Datei enthält Informationen zu den folgenden Feldern: Breitengrad, Längengrad, Helligkeit, Scan, Track, acq_date, acq_time, Satellit, Sicherheit, Version, bright_t31, frp, daynight, von denen wir nur die Koordinaten der Feuerpunkte (Breiten- und Längengrad) verwendet haben.


    Das Prinzip der Anwendung


    Da die Anwendung Crowdsourcing ist, sollte sie idealerweise von einer großen Anzahl von Benutzern verwendet werden. Das Prinzip der Anwendung lautet wie folgt:


    1. Nachdem ein Benutzer ein Feuer gefunden hat, fotografiert er es (mit einem Geotag) und lädt es mit Hilfe des Dienstes. Fotos mit Geotagging und Senden von Koordinaten werden an den Anwendungsserver gesendet. Das Foto kann von der Web- oder Mobile-Version der Anwendung heruntergeladen werden.

    2. Das resultierende Foto wird auf dem Server von einem geschulten neuronalen Netzwerk verarbeitet, um zu bestätigen, dass das Foto tatsächlich in Brand ist. Das Ergebnis der Skriptausführung ist die Vorhersagegenauigkeit. Wenn> 0,7, dann ist das Foto wirklich scharf. Andernfalls korrigieren wir diese Informationen nicht und bitten den Benutzer, ein weiteres Foto hochzuladen.

    3. Wenn das Bildanalyse-Skript ein positives Ergebnis liefert, werden die Koordinaten des Geo-Tags mit allen Koordinaten zum Datensatz hinzugefügt. Ferner werden die Entfernungen zwischen dem i- ten Punkt von NASA-Datensatz und dem Punkt vom Benutzer berechnet . Wenn der Abstand zwischen den Punkten 3 km ist, wird der Punkt aus dem NASA-Set dem Wörterbuch hinzugefügt. Gehen Sie also alle Punkte durch. Danach geben wir json mit Koordinaten zurück, die die Bedingung erfüllen, an den Client-Teil der Anwendung. Wenn die Koordinaten durch die angegebene Bedingung nicht gefunden werden, geben wir den einzelnen 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 Feuerzone auf der Karte. Wenn der Server einen Punkt zurückgibt, wird er auf der Karte mit einem speziellen Etikett gekennzeichnet.


    Verwendeter Technologie-Stack


    Front-End-Teil einer Webanwendung


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



    Skriptarbeit


    Der Benutzer öffnet die Anwendung und sieht seinen Speicherort:




    Initialisierung der Benutzerkarte und Geotagging:


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

    Bei einem Feuer im Umkreis von n (einstellbare Variable) von Kilometern wird es als Polygon mit einer Zusammenfassung zusätzlicher Informationen angezeigt:




    Der Benutzer wählt den Ort des Feuers auf der Karte aus:




    Brandmarke setzen:


    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 .


    Als Ergebnis dieser Aktionen werden die folgenden Daten an den Server übermittelt:


    • Benutzerkoordinaten
    • Koordinaten des angegebenen Feuers
    • Feuer Foto

    Die Ausgabe der Anwendung ist das Ergebnis der Erkennung.



    Apps für mobile Apps


    Verwendete Technologien


    • React native - Framework für die Entwicklung plattformübergreifender Anwendungen für iOS und Android
    • Redux - Flusskontrolle in Anwendungen
    • Redux-Saga - eine Bibliothek, die Nebenwirkungen in Redux verwendet

    Skriptarbeit


    Ein Foto vom Feuer wählen


    Benutzerkommentar


    Beschriftung für Feuer



    Back-End-Teil der Anwendung


    • Programmiersprache - JAVA 8

    • Cloud-Plattform - Microsoft Azure

    • Webanwendungs-Framework - Play Framework

    • Objektrelationales Mapping - Ebean-Framework


    Der Server verfügt über zwei in Python geschriebene Skripts: predict.py und getZone.py. Die folgenden Python-Bibliotheken wurden für ihre Arbeit installiert:


    • Pandas - zur Verarbeitung und Analyse von Daten
    • geopandas - für die arbeit mit geodaten
    • numpy - für die Arbeit mit mehrdimensionalen Arrays
    • matplotlib - zur Visualisierung von Daten in zweidimensionalen (2D) Grafiken (3D-Grafiken werden ebenfalls unterstützt)
    • formschön - für die Bearbeitung und Analyse von flachen geometrischen Objekten.

    Server-API: fire.iconx.app/api


    • Koordinaten laden

    post /pictures {}
    return { id }
    

    • Bilder laden

    post /pictures/:id
    

    Skript vorhersagen.py


    Das Eingabeskript erhielt ein Bild, eine einfache Vorverarbeitung des Bildes fand statt (mehr dazu im Block „Modelltraining“) und basierend auf der gespeicherten Datei mit Gewichtungen, die ebenfalls auf dem Server liegt, wurde eine Vorhersage ausgegeben. Wenn das Modell eine Genauigkeit von> 0,7 ausgegeben hat, ist der Brand behoben, ansonsten - nein.


    Das Skript läuft klassisch ab.

    $ python predict.py image.jpg 

    Code Listing:
    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))
    



    GetZone.py-Skript


    Die Eingabedaten des Skripts sind die Koordinaten des Punkts, der vom Client-Teil der Anwendung stammt. Das Skript zieht alle Koordinaten von der NASA ein, fügt dieser Datei einen neuen Längen- und Breitengrad hinzu, überschreibt die Originaldatei und sucht nach den nächstgelegenen Punkten. Der Abstand zwischen den Punkten wird nach der Haversine-Formel (engl. Haversine-Formel ) berechnet .


    Dazu werden die Breiten- und Längengrade der Punkte in Bogenmaß umgerechnet:


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

    Für jeden Punkt gibt es Unterschiede zwischen Längen- und Breitengrad:


    d_lon = pt2_lon - pt1_lon
    d_lat = pt2_lat - pt1_lat
    

    All dies wird in die Formel von Haversinus eingesetzt:


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

    Nehmen Sie die Wurzel des Berechnungsergebnisses, berechnen Sie den Arkussinus und multiplizieren Sie das Ergebnis mit 2.


    c = 2 * asin(sqrt(a))
    

    Die Entfernung ist das Produkt des Erdradius (6371 km) durch das Ergebnis der vorherigen Berechnung.


    Modelltraining


    Um das Bild zum Thema Feuer zu analysieren, brauchten wir eine Schulung von Fotos mit Feuer. Die Fotos wurden vom Skript von https://www.flickr.com/ gesammelt und manuell markiert.


    Das Herunterladen wurde mit FlikerAPI durchgeführt. Im Skript wurden Standardvorverarbeitungsvorgänge mit Bildern ausgeführt: Das Zuschneiden erfolgt quadratisch mit Zentrierung (Verhältnis 1: 1) und Größenänderung auf das Format 256 × 256.


    Code Listing:
    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
    


    Für die Arbeit mit Bildern wurde natürlich die Faltungsarchitektur des neuronalen Netzwerks verwendet, in der das vorgebildete Modell verwendet wurde. Die Wahl fiel auf MobileNet (erwartet) , weil:


    • Geringes Gewicht - es ist wichtig, dass die Reaktionszeit der Anwendung minimal ist.
    • Schnell - es ist wichtig, dass die Antwortzeit der Anwendung minimal ist.
    • Genau - MobileNet prognostiziert mit der erforderlichen Genauigkeit.

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


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


    Da es sich bei NASA DataSet um geographische Daten handelt, wollten wir die GeoPandas- Bibliothek verwenden . Diese Bibliothek ist eine Erweiterung der Fähigkeiten von Pandas, um räumliche Methoden und Operationen für geometrische Typen bereitzustellen. Geometrische Operationen werden durch die formschöne Bibliothek, die Fiona-Dateibehandlung und die Matplotlib-Grafik implementiert.


    Nachdem wir fast anderthalb Tage damit verbracht hatten, uns mit dieser Bibliothek zu beschäftigen, haben wir sie aufgegeben, weil wir nicht herausfinden konnten, welchen Nutzen sie daraus ziehen könnte. Unsere Aufgabe, die Koordinaten zu berechnen, war ziemlich klein, sodass am Ende alle nativ implementiert wurden.


    Was weiter?


    Alles, was wir als Ergebnis bekommen haben, ist natürlich eine extrem instabile und rohe Anwendung, die das Recht hat, fertig zu werden.


    Wir haben gemacht:


    1. Implementieren Sie Prototypen mobiler und webbasierter Anwendungen, die Fotos aufnehmen (nur für die mobile Version), herunterladen und an den Server senden können. Senden Sie die Koordinaten auch erfolgreich an den Server.
    2. Der Server konnte zwei Skripts bereitstellen, die die Hauptlogik der Anwendung implementieren. Die Eingabedaten für diese Skripts und die Ausgabedaten wurden gesendet und an den Client-Teil gesendet.
    3. Implementieren Sie den echten "Prototyp" unserer Anwendung.

    Die Implementierung ist fehlgeschlagen, aber ich möchte die folgenden Probleme lösen und Funktionen hinzufügen (Elemente werden entsprechend der Priorität der Aufgabe festgelegt):


    1. Organisation der Aufzeichnung aller Koordinaten vom Datensatz in die Datenbank, um direkt mit der Datenbank zu interagieren.
    2. Um das automatische Hochladen einer neuen Datei von der NASA-Site zu organisieren, d. H. Automatische tägliche Aktualisierung der Koordinaten organisieren.
    3. Fügen Sie eine Benachrichtigung für Benutzer hinzu, die sich in der Zone in der Nähe des Feuers befinden.
    4. Registrierung hinzufügen (erforderlich für die Umsetzung des ersten Absatzes).
    5. Den Algorithmus zur Berechnung der Feuerzone neu schreiben.
    6. Designprobleme lösen - die mobile Version und die Webversion der Anwendung mit Schönheit versehen.

    Jetzt auch beliebt: