كما كتبنا التطبيق في hackathon NASA Space Apps Challenge

في 20-21 أكتوبر ، أقيمت هاكاثون الدولي لتحدي تطبيقات الفضاء لناسا في موسكو. كان منظموها في روسيا رجالًا من المجتمع الروسي . كجزء من الحدث ، طُلب من المشاركين حل 20 حالة حول مواضيع مختلفة: من تصوير فيلم عن الهاكاثون إلى تطوير تطبيقات المراقبة وتصميم الطائرات المستقلة. يمكن دراسة القائمة الكاملة للموضوعات بالرجوع إليها أو في مقال عن حبري .

قرر فريقنا "Space Monkeys" ، الذي شمل أوليغ بورودين (مطور أمامي في مختبر Singularis) ، وفلاديسلاف بلوتنيكوف (مهندس ضمان الجودة في مختبر Singularis) ، ويغور شفيتسوف ، وديمتري بتروف ، ويوري بديروف ، ونيكولاي دينيسينكو ، حل المشكلة تحت المشكلة الجذابة بعنوان "بقعة تلك النار!" ، والتي تمت صياغتها على النحو التالي: " تطبيق التعهيد الجماعي بحيث يمكن للناس المساهمة في الكشف عن حرائق الغابات وتأكيدها وتتبعها. يمكن أن يكون الحل تطبيق جوال أو تطبيق ويب. "

نظرًا لحقيقة أن الفريق جمع 5 مطورين من ذوي الخبرة في التطوير لمنصات مختلفة ، فقد تقرر على الفور أنه سيتم تنفيذ النموذج الأولي لتطبيقنا لمنصات الويب والجوال.

ما هي بيانات وكالة ناسا التي استخدمناها؟


ومع ذلك ، تم عقد هاكاثون تحت رعاية الإدارة الوطنية للملاحة الجوية والفضاء ، لذلك سيكون من الخطأ عدم استخدام البيانات المفتوحة من فناء ناسا. بالإضافة إلى ذلك ، وجدنا على الفور مجموعة بيانات Active Fire Data التي نحتاجها. تحتوي مجموعة البيانات هذه على معلومات حول إحداثيات الحرائق حول العالم (يمكنك تنزيل المعلومات في قارة معينة). يتم تحديث البيانات كل يوم (يمكنك تلقي البيانات لمدة 24 ساعة و 48 ساعة و 7 أيام).


يحتوي الملف على معلومات حول الحقول التالية: خط العرض ، خط الطول ، السطوع ، المسح الضوئي ، المسار ، acq_date ، acq_time ، القمر الصناعي ، الثقة ، الإصدار ، bright_t31 ، frp ، daynight ، والتي استخدمنا منها إحداثيات نقاط النار فقط (خط العرض وخط الطول).


مبدأ التطبيق


نظرًا لأن التطبيق يعتمد على مصادر خارجية ، فمن الأفضل استخدامه بواسطة عدد كبير من المستخدمين. مبدأ التطبيق كما يلي:


  1. يقوم المستخدم ، بعد أن اكتشف حريقًا ، بالتقاط صورة له (مع وضع العلامات الجغرافية) وتحميله باستخدام الخدمة. الصور ذات العلامات الجغرافية وإحداثيات الإرسال تذهب إلى خادم التطبيق. يمكن تنزيل الصور الفوتوغرافية من إصدار الويب أو الهاتف المحمول من التطبيق.

  2. تتم معالجة الصورة الناتجة على الخادم عن طريق شبكة عصبية مدربة للتأكد من أن الصورة مشتعلة بالفعل. نتيجة البرنامج النصي هي دقة التنبؤ ، إذا> 0.7 ، فإن الصورة تنطلق حقًا. وإلا فإننا لا نسجل هذه المعلومات ونطلب من المستخدم تحميل صورة أخرى.

  3. إذا أعطى النص البرمجي لتحليل الصورة نتيجة إيجابية ، فسيتم إضافة الإحداثيات من العلامة الجغرافية إلى مجموعة البيانات مع جميع الإحداثيات. بعد ذلك ، يتم حساب المسافات بين النقطة i من مجموعة بيانات NASA والنقطة من المستخدم. إذا كانت المسافة بين النقاط 3 كم ، فستتم إضافة النقطة من مجموعة NASA إلى القاموس. لذلك نذهب من خلال جميع النقاط. بعد ذلك ، قم بإرجاع json مع إحداثيات تفي بالشرط إلى جانب العميل من التطبيق. إذا لم يتم العثور على إحداثيات حسب الشرط المحدد ، فإننا نعود إلى النقطة الوحيدة التي تلقيناها من المستخدم.

  4. إذا قام الخادم بإرجاع مجموعة من النقاط ، فإن جزء العميل من التطبيق يرسم منطقة نار على الخريطة. في حالة إرجاع الخادم لنقطة واحدة ، يتم تمييزه على الخريطة بتسمية خاصة.


مكدس التكنولوجيا المستخدمة


الجزء الأمامي من تطبيق الويب


ركز تطبيق الويب ، الذي يمكن الوصول إليه من متصفح ، على شاشات الكمبيوتر ، ولم يكن قابلاً للتكيف ، ومع ذلك ، فإن التقنيات المستخدمة جعلت من السهل تحسين هذا الجانب للأجهزة المحمولة. استخدمنا مكدس التقنية التالي على جانب الويب:



سيناريو العمل


يفتح المستخدم التطبيق ويرى موقعه:




تهيئة الخريطة والمستخدم الجغرافي:


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 .


نتيجة لهذه الإجراءات ، يتم نقل البيانات التالية إلى الخادم:


  • إحداثيات المستخدم
  • إحداثيات الحريق المحدد
  • صورة النار

إخراج التطبيق هو نتيجة الاعتراف.



تطبيقات تطبيقات الجوال


التقنيات المستخدمة


  • رد فعل أصلي - إطار عمل لتطوير التطبيقات عبر الأنظمة الأساسية لنظامي التشغيل iOS و Android
  • Redux - التحكم في تدفق بيانات التطبيق
  • Redux-saga - مكتبة باستخدام الآثار الجانبية في Redux

سيناريو العمل


اختيار صورة النار


تعليق من المستخدم


علامة النار



الجزء الخلفي من التطبيق


  • لغة البرمجة - JAVA 8

  • منصة سحابية - مايكروسوفت أزور

  • إطار تطبيق الويب - إطار العمل

  • رسم الخرائط الكائنية - إطار Ebean


يحتوي الخادم على نصين مكتوبين بلغة Python: التنبؤ.py و getZone.py ، تم تثبيت مكتبات Python التالية لعملها:


  • الباندا - لمعالجة البيانات وتحليلها
  • geopandas - للعمل مع البيانات الجغرافية
  • numpy - للعمل مع المصفوفات متعددة الأبعاد
  • matplotlib - لتصور البيانات ثنائية الأبعاد (ثنائي الأبعاد) للبيانات (يتم دعم الرسومات ثلاثية الأبعاد أيضًا)
  • رشيق - لمعالجة وتحليل الأجسام الهندسية المسطحة.

واجهة برمجة تطبيقات الخادم: 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 النصي


الإدخال إلى البرنامج النصي هو إحداثيات النقطة التي جاءت من جانب العميل للتطبيق. يقوم البرنامج النصي بإحكام جميع الإحداثيات من وكالة ناسا ، ويضيف خط عرض وخط طول جديد إلى هذا الملف ، ويحل محل الملف الأصلي ويبدأ في البحث عن أقرب نقاط. يتم حساب المسافة بين النقاط باستخدام صيغة 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 كم) ونتيجة الحساب السابق.


تدريب نموذجي


لتحليل الصورة للحريق ، كنا بحاجة إلى مجموعة من الصور بالحرائق. تم جمع الصور من خلال نص برمجي من الموقع 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 DataSet هي بيانات جغرافية ، فقد أردنا استخدام مكتبة GeoPandas . هذه المكتبة هي امتداد لقدرات Pandas لتوفير الأساليب والعمليات المكانية على الأنواع الهندسية. يتم تنفيذ العمليات الهندسية من خلال المكتبة الرشيقة ، والعمل مع الملفات - فيونا ، الرسوم البيانية - matplotlib.


بعد قضاء ما يقرب من يوم ونصف في محاولة اكتشاف هذه المكتبة ، تخلينا عنها لأننا لم نتمكن من العثور على مكان يمكن أن يمنحنا ميزة حقيقية من العمل معها. كانت مهمتنا في حساب الإحداثيات صغيرة جدًا ، لذلك أدركنا جميعًا في النهاية.


ما هي الخطوة التالية؟


بطبيعة الحال ، كل ما حصلنا عليه نتيجة لذلك هو تطبيق غير مستقر وخشن للغاية ، والذي له الحق في الانتهاء منه.


لقد نجحنا:


  1. تنفيذ نماذج أولية لتطبيقات الجوال والويب التي كانت قادرة على التقاط الصور (إصدار الجوال فقط) ، وتحميلها وإرسالها إلى الخادم. أيضا ، تأتي إحداثيات الإرسال إلى الخادم بنجاح.
  2. على الخادم ، كان من الممكن نشر نصين تنفيذيين يقومان بتطبيق المنطق الرئيسي للتطبيق. تم ترتيب تدفق بيانات الإدخال إلى هذه البرامج النصية واستلام بيانات الإخراج مع الإرسال اللاحق إلى جزء العميل.
  3. تنفيذ "النموذج الأولي" الحقيقي لتطبيقنا.

لم ننجح في تنفيذه ، لكني أرغب في حل المشكلات التالية وإضافة ميزات (تسير العناصر وفقًا لأولوية المهمة):


  1. تنظيم تسجيل جميع الإحداثيات من مجموعة البيانات إلى قاعدة البيانات من أجل التفاعل مباشرة مع قاعدة البيانات.
  2. تنظيم التحميل التلقائي لملف جديد من موقع وكالة ناسا ، أي تنظيم تحديثات الإحداثيات اليومية التلقائية.
  3. أضف إشعارًا للمستخدمين الموجودين في المنطقة القريبة من النار.
  4. إضافة التسجيل (ضروري لتنفيذ الفقرة الأولى).
  5. أعد كتابة خوارزمية حساب منطقة الحريق.
  6. حل مهام التصميم - أضف الجمال إلى إصدارات الجوال والويب للتطبيق.

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


All Articles