De 20 a 21 de outubro, o hackathon internacional do
NASA Space Apps Challenge foi realizado em Moscou. Seus organizadores na Rússia eram pessoas da comunidade
Russian.Hackers . Como parte do evento, os participantes foram convidados a resolver 20 casos sobre vários tópicos: desde a gravação de um filme sobre o hackathon até o desenvolvimento de aplicativos de monitoramento e o design de aeronaves autônomas. A lista completa de tópicos pode ser estudada por
referência ou em um
artigo sobre Habré .

Nossa equipe “Space Monkeys”, que incluiu Oleg Borodin (desenvolvedor front-end no laboratório Singularis), Vladislav Plotnikov (engenheiro de controle de qualidade no laboratório Singularis), Yegor Shvetsov, Dmitry Petrov, Yuri Bederov e Nikolai Denisenko, decidiram resolver o problema sob o ponto de vista cativante intitulado “Spot that fire!”, com a seguinte redação: “
Aplique crowdsourcing para que as pessoas possam contribuir para a detecção, confirmação e rastreamento de incêndios florestais. A solução pode ser um aplicativo móvel ou da web. "
Devido ao fato de a equipe reunir 5 desenvolvedores com experiência no desenvolvimento de várias plataformas, foi imediatamente decidido que o protótipo de nosso aplicativo seria implementado nas plataformas Web e Mobile.
Quais dados da NASA usamos?
Ainda assim, o hackathon foi realizado sob os auspícios da Administração Nacional de Aeronáutica e Espaço, por isso seria errado não usar dados abertos das despensas da NASA. Além disso, encontramos imediatamente o conjunto de dados Active Fire Data de que precisávamos. Este conjunto de dados contém informações sobre as coordenadas de incêndios em todo o mundo (você pode baixar informações em um continente específico). Os dados são atualizados todos os dias (você pode receber dados por 24 horas, 48 horas, 7 dias).
O arquivo contém informações sobre os seguintes campos: latitude, longitude, brilho, varredura, faixa, data_acq, horário_qq, satélite, confiança, versão, bright_t31, frp, noite do dia, das quais usamos apenas as coordenadas dos pontos de disparo (latitude e longitude).
O princípio da aplicação
Como o aplicativo é fornecido por crowdsourcing, idealmente, ele deve ser usado por um grande número de usuários. O princípio da aplicação é o seguinte:
O usuário, após detectar um incêndio, tira uma foto dele (com marcação geográfica) e carrega-o usando o serviço. Fotos com etiquetas geográficas e coordenadas de envio vão para o servidor de aplicativos. É possível fazer o download da fotografia na versão Web ou móvel do aplicativo.
A foto resultante é processada no servidor por uma rede neural treinada para confirmar que a foto está realmente pegando fogo. O resultado do script é a precisão da previsão, se> 0,7, a foto realmente dispara. Caso contrário, não registramos essas informações e solicitamos que o usuário envie outra foto.
Se o script de análise de imagem deu um resultado positivo, as coordenadas do geotag serão adicionadas ao conjunto de dados com todas as coordenadas. A seguir, são calculadas as distâncias entre o i- ésimo ponto do conjunto de dados da NASA e o ponto do usuário. Se a distância entre os pontos for ≤ 3 km, o ponto do conjunto da NASA será adicionado ao dicionário. Então, analisamos todos os pontos. Depois disso, retorne json com coordenadas que satisfazem a condição ao lado do cliente do aplicativo. Se não houver coordenadas encontradas pela condição especificada, retornaremos o único ponto que recebemos do usuário.
Se o servidor retornar uma matriz de pontos, a parte cliente do aplicativo desenhará uma zona de incêndio no mapa. Caso o servidor retorne um ponto, ele é marcado no mapa com uma etiqueta especial.
Pilha de tecnologia usada
Parte front-end do aplicativo Web
O aplicativo da web, acessível a partir de um navegador, focava nas telas dos computadores e não era adaptável; no entanto, as tecnologias utilizadas permitiram refinar esse aspecto para dispositivos móveis. Usamos a seguinte pilha de tecnologia no lado da Web:
Cenário de trabalho
O usuário abre o aplicativo e vê sua localização:

Inicialização do mapa e localização geográfica do usuário:
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();
Se houver um incêndio no raio n (variável personalizada) de quilômetros, ele será exibido na forma de um polígono com um resumo de informações adicionais:

O usuário seleciona um local de incêndio no mapa:

Configuração de etiqueta de incêndio:
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); });
Em seguida, o usuário envia uma foto do incêndio usando o ng2-file-upload .
Como resultado dessas ações, os seguintes dados são transferidos para o servidor:
- coordenadas do usuário
- coordenadas do fogo especificado
- foto de fogo
A saída do aplicativo é o resultado do reconhecimento.
Aplicativos para aplicativos móveis
Tecnologias usadas
- Reagir nativo - uma estrutura para o desenvolvimento de aplicativos de plataforma cruzada para iOS e Android
- Redux - controle de fluxo de dados do aplicativo
- Redux-saga - biblioteca usando efeitos colaterais no Redux
Cenário de trabalho
Seleção de fotos de fogo

| Comentário do usuário

| Marca de fogo

|
Parte de back-end do aplicativo
Linguagem de Programação - JAVA 8
Plataforma em nuvem - Microsoft Azure
Estrutura de aplicativo da Web - Play Framework
Mapeamento Objeto-Relacional - Estrutura Ebean
O servidor possui 2 scripts escritos em Python: predict.py e getZone.py, as seguintes bibliotecas Python foram instaladas para seu trabalho:
- pandas - para processamento e análise de dados
- geopandas - para trabalhar com dados geográficos
- numpy - para trabalhar com matrizes multidimensionais
- matplotlib - para visualização de dados gráficos bidimensionais (2D) (gráficos 3D também são suportados)
- bem torneado - para manipulação e análise de objetos geométricos planos.
API do servidor: fire.iconx.app/api
- coordenadas de carregamento
post /pictures {} return { id }
post /pictures/:id
Script predict.py
Um script de entrada recebeu uma figura, ocorreu um pré-processamento simples da figura (mais sobre isso na seção "Treinamento de modelo") e, com base no arquivo salvo com pesos, que também está localizado no servidor, foi emitida uma previsão. Se o modelo produzir uma precisão> 0,7, o fogo será corrigido, caso contrário - não.
O script é executado de maneira clássica.
$ python predict.py image.jpg
Listagem de código: 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
A entrada para o script são as coordenadas do ponto que veio do lado do cliente do aplicativo. O script aperta todas as coordenadas da NASA, adiciona uma nova latitude e longitude a esse arquivo, substitui o arquivo original e começa a procurar os pontos mais próximos. A distância entre os pontos é calculada usando a fórmula Haversine .
Para fazer isso, a latitude e longitude dos pontos são convertidas em radianos:
pt1_lon, pt1_lat, pt2_lon, pt2_lat = map(radians, [pt1_lon, pt1_lat, pt2_lon, pt2_lat])
Existem diferenças entre latitude e longitude para cada um dos pontos:
d_lon = pt2_lon - pt1_lon d_lat = pt2_lat - pt1_lat
Tudo isso é substituído na fórmula do haversine:
a = sin(d_lat/2)**2 + cos(pt1_lat) * cos(pt2_lat) * sin(d_lon/2)**2
Tomamos a raiz do resultado do cálculo, calculamos o arco-seno e multiplicamos o resultado por 2.
c = 2 * asin(sqrt(a))
A distância será o produto do raio da Terra (6371 km) e o resultado do cálculo anterior.
Modelo de treinamento
Para analisar a imagem em busca de um incêndio, precisávamos de um conjunto de fotos com fogos. As fotos foram coletadas por um script do site https://www.flickr.com/ e marcadas manualmente.
O download ocorreu usando o FlikerAPI. O script executava operações padrão de pré-processamento com figuras: recorte - quadrado com centralização (proporção 1: 1) e redimensionando para o formato 256 × 256.
Listagem de código: 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
Naturalmente, a arquitetura convolucional da rede neural, na qual o modelo pré-treinado foi usado, foi usada para trabalhar com imagens. A escolha caiu (esperado) na MobileNet , porque:
- Leve - É importante que o tempo de resposta do aplicativo seja mínimo.
- Rápido - É importante que o tempo de resposta do aplicativo seja mínimo.
- Exatamente - o MobileNet prevê com a precisão necessária.
Após o treinamento, a rede produziu uma precisão de ~ 0,85.
Para construir o modelo, treinamento e previsão, um monte de Keras + Tensorflow foi usado . O trabalho com dados foi realizado através do Pandas .
Como o DataSet da NASA é um dado geográfico, queremos usar a biblioteca GeoPandas . Esta biblioteca é uma extensão dos recursos do Pandas para fornecer métodos e operações espaciais em tipos geométricos. As operações geométricas são implementadas através da biblioteca bem torneada, trabalham com arquivos - fiona, gráficos - matplotlib.
Depois de passar quase um dia e meio para descobrir essa biblioteca, a abandonamos porque não conseguimos encontrar onde ela poderia nos dar uma vantagem real ao trabalhar com ela. Nossa tarefa de calcular as coordenadas era muito pequena, então, no final, todos percebemos de forma nativa.
O que vem a seguir?
Naturalmente, tudo o que obtivemos como resultado é uma aplicação extremamente instável e grosseira, que tem o direito de ser finalizada.
Conseguimos:
- Implemente protótipos de aplicativos móveis e da Web capazes de tirar fotos (apenas na versão móvel), fazer upload e enviá-las ao servidor. Além disso, as coordenadas de envio com êxito chegam ao servidor.
- No servidor, foi possível implantar 2 scripts que implementam a lógica principal do aplicativo. O fluxo de dados de entrada para esses scripts e o recebimento de dados de saída com o subsequente envio para a parte do cliente foram organizados.
- Implemente o verdadeiro “protótipo” de nossa aplicação.
Não conseguimos implementá-lo, mas gostaria de resolver os seguintes problemas e adicionar recursos (os itens vão de acordo com a prioridade da tarefa):
- Organize a gravação de todas as coordenadas do conjunto de dados no banco de dados para interagir diretamente com o banco de dados.
- Organize o upload automático de um novo arquivo no site da NASA, ou seja, organizar atualizações automáticas de coordenadas diárias.
- Adicione notificação aos usuários localizados na área próxima ao incêndio.
- Adicione registro (necessário para implementar o primeiro parágrafo).
- Reescreva o algoritmo de cálculo da zona de incêndio.
- Resolver tarefas de design - leve beleza às versões para celular e web do aplicativo.