当我们在NASA太空应用程序挑战黑客马拉松上编写应用程序时

10月20日至21日,“ 美国国家航空航天局(NASA)太空应用挑战赛”国际黑客马拉松在莫斯科举行。 它在俄罗斯的组织者来自Russian.Hackers社区。 作为活动的一部分,参与者被要求解决20个不同主题的案例:从拍摄有关黑客马拉松的电影到开发监控应用程序和设计自动飞机。 主题的完整列表可以通过参考或在Habré上文章中进行研究。

我们的“ Space Monkeys”团队包括Oleg Borodin(Singularis实验室的前端开发人员),Vladislav Plotnikov(Singularis实验室的QA工程师),Yegor Shvetsov,Dmitry Petrov,Yuri Bederov和Nikolai Denisenko在内,决定解决这个棘手的问题标题为“火灾现场!”,其措辞如下:“ 应用众包,使人们能够为森林火灾的发现,确认和跟踪做出贡献。 该解决方案可以是移动应用程序或Web应用程序。

由于该团队聚集了5位具有针对各种平台开发经验的开发人员,因此立即决定将我们的应用程序原型应用于Web和Mobile平台。

我们使用了哪些NASA数据?


尽管如此,这次黑客马拉松还是在美国国家航空航天局的主持下举行的,因此不使用来自NASA储藏室的开放数据是错误的。 此外,我们立即找到了所需的Active Fire Data数据集。 该数据集包含有关世界各地火灾坐标的信息(您可以下载特定大陆上的信息)。 数据每天更新(您可以在24小时,48小时,7天之内收到数据)。


该文件包含以下字段的信息:纬度,经度,亮度,扫描,跟踪,acq_date,acq_time,卫星,置信度,版本,bright_t31,frp,白天,其中我们仅使用着火点的坐标(纬度和经度)。


应用原理


由于应用程序是众包的,因此理想情况下,应由大量用户使用。 该应用程序的原理如下:


  1. 检测到火灾的用户为火灾拍照(带有地理标记)并使用该服务加载火灾。 具有地理标签和发送坐标的照片将转到应用程序服务器。 可以从Web或移动版本的应用程序下载摄影。

  2. 得到的照片由训练有素的神经网络在服务器上进行处理,以确认照片确实着火了。 脚本的结果是预测的准确性,如果> 0.7,则照片确实会发光。 否则,我们不会记录此信息,并要求用户上传另一张照片。

  3. 如果图像分析脚本给出了肯定的结果,则来自地理标签的坐标将与所有坐标一起添加到数据集中。 接下来,计算来自NASA数据集的第i个点与来自用户的点之间的距离。 如果点之间的距离≤3 km,则将来自NASA集合的点添加到字典中。 因此,我们遍历了所有要点。 之后,将坐标满足条件的json返回到应用程序的客户端。 如果在给定条件下找不到坐标,那么我们将返回从用户那里收到的唯一点。

  4. 如果服务器返回一个点数组,则该应用程序的客户端部分将在地图上绘制一个防火区域。 如果服务器返回了一个点,则会在地图上标记一个特殊标签。


二手技术栈


Web应用程序的前端部分


可从浏览器访问的Web应用程序专注于计算机屏幕,并且不具有自适应性,但是,易于使用的技术使针对移动设备改进此方面成为可能。 我们在网络端使用了以下技术堆栈:



工作场景


用户打开应用程序并查看其位置:




地图和用户地理位置的初始化:


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

如果方圆n (自定义变量)的半径处着火,那么它将以多边形的形式显示,并带有其他信息的摘要:




用户在地图上选择一个火灾地点:




消防标签设置:


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

接下来,用户使用ng2-file-upload上传火的照片。


这些操作的结果是,以下数据被传输到服务器:


  • 用户坐标
  • 指定火的坐标
  • 火照片

应用程序的输出是识别结果。



手机应用程序


二手技术


  • React native-用于为iOS和Android开发跨平台应用程序的框架
  • Redux-应用程序数据流控制
  • Redux-Saga-在Redux中使用副作用的库

工作场景


消防照片选择


用户的评论


火标



应用程序的后端部分


  • 编程语言-JAVA 8

  • 云平台-Microsoft Azure

  • Web应用程序框架-Play框架

  • 对象关系映射-Ebean框架


该服务器有2个用Python编写的脚本:predict.py和getZone.py,安装了以下Python库以完成其工作:


  • 熊猫-用于数据处理和分析
  • geopandas-用于处理地理数据
  • numpy-用于多维数组
  • matplotlib-用于可视化数据二维(2D)图形(也支持3D图形)
  • 匀称-用于处理和分析平面几何对象。

服务器API: fire.iconx.app/api


  • 加载坐标

 post /pictures {} return { id } 

  • 上传图片

 post /pictures/:id 

脚本预报


输入脚本接收到一张图片,然后对该图片进行了简单的预处理(有关更多信息,请参见“模型训练”部分),并基于保存的文件也具有权重,该文件也位于服务器上,发出了预测。 如果模型产生的准确度> 0.7,则说明火灾是固定的,否则-不。


该脚本以经典方式运行。

 $ python predict.py image.jpg 

代码清单:
 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


脚本的输入是来自应用程序客户端的点的坐标。 该脚本会收紧来自NASA的所有坐标,为此文件添加新的纬度和经度,覆盖原始文件并开始寻找最近的点。 点之间的距离是使用Haversine公式计算的


为此,将点的经度和纬度转换为弧度:


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

每个点的经度和纬度有所不同:


 d_lon = pt2_lon - pt1_lon d_lat = pt2_lat - pt1_lat 

所有这些都被替换为haversine公式:


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

我们取计算结果的根,计算反正弦值并将结果乘以2。


 c = 2 * asin(sqrt(a)) 

该距离将是地球半径(6371 km)与先前计算结果的乘积。


模型训练


要分析有火的图片,我们需要一组训练有火的照片。 照片是通过脚本从https://www.flickr.com/网站收集的,并进行了手动标记。


下载使用FlikerAPI进行。 该脚本对图片执行了标准的预处理操作:裁剪-居中对齐的正方形(比率1:1),并调整为256×256格式。


代码清单:
 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 


自然地,使用预训练模型的神经网络的卷积架构可用于处理图片。 选择落在了(预期的) MobileNet上 ,因为:


  • 轻量级-重要的是应用程序的响应时间应尽可能短。
  • 快速-最小化应用程序响应时间非常重要。
  • 确实-MobileNet可以进行必要的预测。

经过培训,该网络产生的准确度约为0.85。


为了构建模型,训练和预测,使用了一堆Keras + Tensorflow 。 通过Pandas进行数据处理。


由于NASA数据集是地理数据,因此我们想使用GeoPandas库。 该库是熊猫提供几何类型空间方法和操作功能的扩展。 几何运算是通过图形库实现的,并与文件-fiona,图形-matplotlib一起使用。


我们花了将近一天半的时间来弄清楚这个库,所以我们放弃了它,因为我们找不到可以从中使用它的真正优势的地方。 我们计算坐标的任务非常小,因此最后,所有操作都是本地实现的。


接下来是什么?


自然,我们得到的结果是极其不稳定且原始的应用程序,该应用程序有待最终确定。


我们已经成功:


  1. 实现能够拍照的移动和Web应用程序的原型(仅限移动版),然后将其上传并发送到服务器。 另外,发送成功的坐标到达服务器。
  2. 在服务器上,可以部署2个脚本来实现应用程序的主要逻辑。 安排了输入数据到这些脚本的流程以及接收输出数据并随后将其发送到客户端的过程。
  3. 实现我们应用程序的真正“原型”。

我们没有设法实现它,但是我想解决以下问题并添加功能(各项根据任务的优先级而定):


  1. 组织从数据集到数据库的所有坐标的记录,以便直接与数据库进行交互。
  2. 组织从NASA网站自动上传新文件,即 安排自动的每日坐标更新。
  3. 向靠近火区的用户添加通知。
  4. 添加注册(实现第一段所必需)。
  5. 重写火区计算算法。
  6. 解决设计任务-为应用程序的移动版和Web版带来美感。

Source: https://habr.com/ru/post/zh-CN430480/


All Articles