Menentukan jenis anjing: siklus pengembangan penuh, dari jaringan saraf dengan Python ke aplikasi di Google Play

Kemajuan di bidang jaringan saraf pada umumnya dan pengenalan pola pada khususnya telah mengarah pada fakta bahwa mungkin tampak seolah-olah membuat aplikasi jaringan saraf untuk bekerja dengan gambar adalah tugas rutin. Dalam arti tertentu, itu adalah - jika Anda menemukan ide yang berkaitan dengan pengenalan pola, jangan ragu bahwa seseorang sudah menulis sesuatu seperti itu. Yang Anda butuhkan hanyalah menemukan bagian kode yang sesuai di Google dan "kompilasi" dari penulis.

Namun, masih ada banyak detail yang membuat tugas ini tidak begitu sulit dipecahkan seperti ... membosankan, menurut saya. Butuh terlalu banyak waktu, terutama jika Anda seorang pemula yang membutuhkan kepemimpinan, selangkah demi selangkah, proyek yang dilakukan tepat di depan mata Anda, dan selesai dari awal hingga selesai. Tanpa biasa dalam kasus seperti itu, "lewati bagian yang jelas ini" alasan.

Dalam artikel ini, kami akan mempertimbangkan tugas membuat Dog Breed Identifier: kami akan membuat dan melatih jaringan saraf, dan kemudian port ke Java untuk Android dan mempublikasikannya di Google Play.

Jika Anda ingin melihat hasil akhirnya, ini dia: Aplikasi NeuroDog di Google Play.

Situs web dengan robotika saya (sedang berlangsung): robotics.snowcron.com .
Situs web dengan program itu sendiri, termasuk panduan: Panduan Pengguna NeuroDog .

Dan di sini adalah tangkapan layar program:

gambar



Pernyataan masalah



Kami akan menggunakan Keras: perpustakaan Google untuk bekerja dengan jaringan saraf. Ini adalah perpustakaan tingkat tinggi, yang berarti lebih mudah digunakan dibandingkan dengan alternatif yang saya tahu. Jika ada - ada banyak buku teks tentang Keras di jaringan, berkualitas tinggi.

Kami akan menggunakan CNN - Convolutional Neural Networks. CNN (dan lebih banyak konfigurasi lanjutan berdasarkan pada mereka) adalah standar de facto dalam pengenalan gambar. Pada saat yang sama, melatih jaringan seperti itu tidak selalu mudah: Anda harus memilih struktur jaringan yang tepat, parameter pelatihan (semua tingkat pembelajaran, momentum, L1 dan L2, dll.). Tugas ini membutuhkan sumber daya komputasi yang signifikan, dan karenanya, untuk menyelesaikannya hanya dengan melalui SEMUA parameter akan gagal.

Ini adalah salah satu dari beberapa alasan mengapa dalam kebanyakan kasus mereka menggunakan apa yang disebut “pengetahuan transfer”, alih-alih apa yang disebut pendekatan “vanila”. Transfer Knowlege menggunakan jaringan saraf yang dilatih oleh seseorang sebelum kita (misalnya, Google) dan biasanya untuk tugas yang serupa, tetapi masih berbeda. Kami mengambil layer awal darinya, mengganti layer terakhir dengan classifier kita sendiri - dan itu berfungsi, dan itu berfungsi dengan baik.

Pada awalnya, hasil seperti itu mungkin mengejutkan: bagaimana kita mengambil jaringan Google yang terlatih untuk membedakan kucing dari kursi, dan ia mengenali jenis anjing untuk kita? Untuk memahami bagaimana ini terjadi, Anda perlu memahami prinsip-prinsip dasar karya Deep Neural Networks, termasuk yang digunakan untuk pengenalan pola.

Kami “memberi makan” jaringan gambar (array angka, yaitu) sebagai input. Lapisan pertama menganalisis gambar untuk pola-pola sederhana, seperti "garis horizontal", "busur", dll. Lapisan berikutnya menerima pola-pola ini sebagai input, dan menghasilkan pola orde kedua, seperti "bulu", "sudut mata" ... Pada akhirnya, kita mendapatkan teka-teki dari mana kita dapat merekonstruksi anjing: wol, dua mata, dan tangan manusia bergigi.

Semua hal di atas dilakukan dengan bantuan lapisan pra-terlatih yang kami peroleh (misalnya, dari Google). Selanjutnya, kami menambahkan layer kami, dan mengajar mereka untuk mengekstrak informasi breed dari pola ini. Kedengarannya logis.

Sebagai rangkuman, dalam artikel ini kita akan membuat "vanilla" CNN dan beberapa varian "transfer learning" dari berbagai jenis jaringan. Adapun "vanilla": Saya akan membuatnya, tetapi saya tidak berencana untuk mengkonfigurasinya dengan memilih parameter, karena jauh lebih mudah untuk melatih dan mengonfigurasi jaringan "pra-terlatih".

Karena kami berencana untuk mengajarkan jaringan saraf kami untuk mengenali ras anjing, kami harus “menunjukkan” sampel dari berbagai ras. Untungnya, ada satu set foto yang dibuat di sini untuk tugas yang sama ( aslinya ada di sini ).

Kemudian saya berencana untuk port yang terbaik dari jaringan yang diterima untuk android. Porting jaringan Kerasov ke android relatif sederhana, diformalkan dengan baik dan kami akan melakukan semua langkah yang diperlukan, sehingga tidak akan sulit untuk mereproduksi bagian ini.

Kemudian kami akan menerbitkan semua ini di Google Play. Secara alami, Google akan menolak, jadi trik tambahan akan digunakan. Misalnya, ukuran aplikasi kita (karena jaringan saraf besar) akan lebih besar daripada ukuran yang diizinkan dari APK Android yang diterima oleh Google Play: kita harus menggunakan bundel. Selain itu, Google tidak akan menampilkan aplikasi kami di hasil pencarian, ini dapat diperbaiki dengan mendaftarkan tag pencarian di aplikasi, atau hanya menunggu ... satu atau dua minggu.

Sebagai hasilnya, kami mendapatkan aplikasi "komersial" yang berfungsi penuh (dalam tanda kutip, sebagaimana dinyatakan gratis) untuk android dan menggunakan jaringan saraf.

Lingkungan pengembangan



Anda dapat memprogram untuk Keras secara berbeda, tergantung pada OS yang Anda gunakan (disarankan Ubuntu), ada atau tidak adanya kartu video, dan sebagainya. Tidak ada yang buruk dalam pengembangan di komputer lokal (dan, karenanya, konfigurasinya), kecuali bahwa ini bukan cara termudah.

Pertama, menginstal dan mengonfigurasi sejumlah besar alat dan pustaka membutuhkan waktu, dan kemudian ketika versi baru dirilis, Anda harus menghabiskan waktu lagi. Kedua, jaringan saraf membutuhkan daya komputasi yang besar untuk pelatihan. Anda dapat mempercepat (10 kali atau lebih) proses ini jika Anda menggunakan GPU ... pada saat menulis artikel ini, GPU teratas yang paling cocok untuk pekerjaan ini adalah $ 2.000 - $ 7.000. Dan ya, mereka juga perlu dikonfigurasi.

Jadi kita akan pergi ke arah lain. Faktanya adalah bahwa Google memungkinkan landak miskin seperti kita untuk menggunakan GPU dari cluster mereka - gratis, untuk perhitungan yang berkaitan dengan jaringan saraf, itu juga menyediakan lingkungan yang sepenuhnya dikonfigurasi, bersama-sama, ini disebut Google Colab. Layanan ini memberi Anda akses ke Jupiter Notebook dengan python, Keras dan sejumlah besar perpustakaan lain sudah dikonfigurasi. Yang harus Anda lakukan adalah mendapatkan akun Google (dapatkan akun Gmail dan ini akan memberi Anda akses ke yang lainnya).

Saat ini, Colab dapat disewa di sini , tetapi mengetahui Google, ini dapat berubah setiap saat. Hanya google Google Colab.

Masalah yang jelas dengan menggunakan Colab adalah itu adalah layanan WEB. Bagaimana kita mengakses data kita? Simpan jaringan saraf setelah pelatihan, misalnya, mengunduh data khusus untuk tugas kita dan seterusnya?

Ada beberapa (pada saat penulisan artikel ini - tiga) cara yang berbeda, kami menggunakan yang menurut saya paling nyaman - kami menggunakan Google Drive.

Google Drive adalah penyimpanan data berbasis cloud yang berfungsi seperti hard drive biasa, dan dapat dipetakan di Google Colab (lihat kode di bawah). Setelah itu, Anda dapat bekerja dengannya seperti halnya Anda bekerja dengan file pada disk lokal. Misalnya, untuk mengakses foto-foto anjing untuk melatih jaringan saraf kita, kita perlu mengunggahnya ke Google Drive, itu saja.

Membuat dan melatih jaringan saraf



Di bawah ini saya berikan kode dengan Python, blok demi blok (dari Notebook Jupiter). Anda dapat menyalin kode ini ke dalam Notebook Jupiter Anda dan menjalankannya, blok demi blok, juga, karena blok dapat dieksekusi secara independen (tentu saja, variabel yang ditentukan dalam blok awal mungkin diperlukan di bagian akhir, tetapi ini adalah ketergantungan yang jelas).

Inisialisasi



Pertama-tama, mari kita pasang Google Drive. Hanya dua baris. Kode ini harus dieksekusi hanya sekali dalam sesi Colab (katakanlah, setiap 6 jam sekali). Jika Anda menyebutnya kedua kalinya saat sesi masih "hidup", itu akan dilewati karena drive sudah terpasang.

from google.colab import drive drive.mount('/content/drive/') 


Pada awal pertama, Anda akan diminta untuk mengkonfirmasi niat Anda, tidak ada yang rumit. Begini tampilannya:

 >>> Go to this URL in a browser: ... >>> Enter your authorization code: >>> ·········· >>> Mounted at /content/drive/ 


Bagian termasuk sepenuhnya standar; mungkin beberapa file yang disertakan tidak diperlukan, well ... maaf. Juga, karena saya akan menguji jaringan saraf yang berbeda, Anda harus berkomentar / menghapus komentar beberapa modul yang disertakan untuk jenis jaringan saraf tertentu: misalnya, untuk menggunakan InceptionV3 NN, batalkan komentar pada inklusi InceptionV3, dan komentar, misalnya, ResNet50. Atau tidak: semua yang berubah dari ini adalah ukuran memori yang digunakan, dan itu tidak terlalu kuat.

 import datetime as dt import pandas as pd import seaborn as sns import matplotlib.pyplot as plt from tqdm import tqdm import cv2 import numpy as np import os import sys import random import warnings from sklearn.model_selection import train_test_split import keras from keras import backend as K from keras import regularizers from keras.models import Sequential from keras.models import Model from keras.layers import Dense, Dropout, Activation from keras.layers import Flatten, Conv2D from keras.layers import MaxPooling2D from keras.layers import BatchNormalization, Input from keras.layers import Dropout, GlobalAveragePooling2D from keras.callbacks import Callback, EarlyStopping from keras.callbacks import ReduceLROnPlateau from keras.callbacks import ModelCheckpoint import shutil from keras.applications.vgg16 import preprocess_input from keras.preprocessing import image from keras.preprocessing.image import ImageDataGenerator from keras.models import load_model from keras.applications.resnet50 import ResNet50 from keras.applications.resnet50 import preprocess_input from keras.applications.resnet50 import decode_predictions from keras.applications import inception_v3 from keras.applications.inception_v3 import InceptionV3 from keras.applications.inception_v3 import preprocess_input as inception_v3_preprocessor from keras.applications.mobilenetv2 import MobileNetV2 from keras.applications.nasnet import NASNetMobile 


Di Google Drive, kami membuat folder untuk file kami. Baris kedua menampilkan isinya:

 working_path = "/content/drive/My Drive/DeepDogBreed/data/" !ls "/content/drive/My Drive/DeepDogBreed/data" >>> all_images labels.csv models test train valid 


Seperti yang Anda lihat, foto-foto anjing (disalin dari dataset Stanford (lihat di atas) di Google Drive) pertama kali disimpan di folder all_images . Nantinya, kami akan menyalinnya ke direktori kereta, valid, dan uji . Kami akan menyimpan model yang terlatih dalam folder model . Adapun file labels.csv, ini adalah bagian dari dataset dengan foto, ini berisi daftar korespondensi nama gambar dan trah anjing.

Ada banyak tes yang dapat Anda jalankan untuk memahami apa sebenarnya yang kami dapatkan untuk penggunaan sementara dari Google. Sebagai contoh:

 # Is GPU Working? import tensorflow as tf tf.test.gpu_device_name() >>> '/device:GPU:0' 


Seperti yang Anda lihat, GPU benar-benar terhubung, dan jika tidak, Anda perlu menemukan dan mengaktifkan opsi ini di pengaturan Notebook Jupiter.

Selanjutnya, kita perlu mendeklarasikan beberapa konstanta, seperti ukuran gambar, dll. Kami akan menggunakan gambar dengan ukuran 256x256 piksel, ini adalah gambar yang cukup besar agar tidak kehilangan detail, dan cukup kecil sehingga semuanya pas di memori. Namun, perlu diketahui bahwa beberapa jenis jaringan saraf yang akan kita gunakan mengharapkan gambar 224x224 piksel. Dalam kasus seperti itu, kami berkomentar 256 dan menghapus komentar 224.

Pendekatan yang sama (komentar satu - batalkan komentar) akan diterapkan pada nama model yang kita simpan, hanya karena kita tidak ingin menimpa file yang mungkin masih berguna.
 warnings.filterwarnings("ignore") os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' np.random.seed(7) start = dt.datetime.now() BATCH_SIZE = 16 EPOCHS = 15 TESTING_SPLIT=0.3 # 70/30 % NUM_CLASSES = 120 IMAGE_SIZE = 256 #strModelFileName = "models/ResNet50.h5" # strModelFileName = "models/InceptionV3.h5" strModelFileName = "models/InceptionV3_Sgd.h5" #IMAGE_SIZE = 224 #strModelFileName = "models/MobileNetV2.h5" #IMAGE_SIZE = 224 #strModelFileName = "models/NASNetMobileSgd.h5" 


Pemuatan data



Pertama-tama, mari kita unggah file labels.csv dan pisahkan ke bagian pelatihan dan validasi. Perhatikan bahwa belum ada bagian pengujian, karena saya akan menipu untuk mendapatkan lebih banyak data pelatihan.

 labels = pd.read_csv(working_path + 'labels.csv') print(labels.head()) train_ids, valid_ids = train_test_split(labels, test_size = TESTING_SPLIT) print(len(train_ids), 'train ids', len(valid_ids), 'validation ids') print('Total', len(labels), 'testing images') >>> id breed >>> 0 000bec180eb18c7604dcecc8fe0dba07 boston_bull >>> 1 001513dfcb2ffafc82cccf4d8bbaba97 dingo >>> 2 001cdf01b096e06d78e9e5112d419397 pekinese >>> 3 00214f311d5d2247d5dfe4fe24b2303d bluetick >>> 4 0021f9ceb3235effd7fcde7f7538ed62 golden_retriever >>> 7155 train ids 3067 validation ids >>> Total 10222 testing images 


Selanjutnya, salin file gambar ke folder pelatihan / validasi / pengujian, sesuai dengan nama file. Fungsi berikut menyalin file yang namanya kami transfer ke folder yang ditentukan.

 def copyFileSet(strDirFrom, strDirTo, arrFileNames): arrBreeds = np.asarray(arrFileNames['breed']) arrFileNames = np.asarray(arrFileNames['id']) if not os.path.exists(strDirTo): os.makedirs(strDirTo) for i in tqdm(range(len(arrFileNames))): strFileNameFrom = strDirFrom + arrFileNames[i] + ".jpg" strFileNameTo = strDirTo + arrBreeds[i] + "/" + arrFileNames[i] + ".jpg" if not os.path.exists(strDirTo + arrBreeds[i] + "/"): os.makedirs(strDirTo + arrBreeds[i] + "/") # As a new breed dir is created, copy 1st file # to "test" under name of that breed if not os.path.exists(working_path + "test/"): os.makedirs(working_path + "test/") strFileNameTo = working_path + "test/" + arrBreeds[i] + ".jpg" shutil.copy(strFileNameFrom, strFileNameTo) shutil.copy(strFileNameFrom, strFileNameTo) 


Seperti yang Anda lihat, kami hanya menyalin satu file untuk setiap trah anjing sebagai tes . Juga, saat menyalin, kami membuat subfolder, satu untuk setiap trah. Oleh karena itu, foto-foto disalin ke subfolder oleh breed.

Ini dilakukan karena Keras dapat bekerja dengan direktori dengan struktur yang sama, memuat file gambar sesuai kebutuhan, dan tidak sekaligus, yang menghemat memori. Mengunggah semua 15.000 gambar sekaligus adalah ide yang buruk.

Kami harus memanggil fungsi ini hanya sekali, karena ini menyalin gambar - dan tidak lagi diperlukan. Karenanya, untuk penggunaan di masa mendatang, kita harus mengomentarinya:

 # Move the data in subfolders so we can # use the Keras ImageDataGenerator. # This way we can also later use Keras # Data augmentation features. # --- Uncomment once, to copy files --- #copyFileSet(working_path + "all_images/", # working_path + "train/", train_ids) #copyFileSet(working_path + "all_images/", # working_path + "valid/", valid_ids) 


Dapatkan daftar trah anjing:

 breeds = np.unique(labels['breed']) map_characters = {} #{0:'none'} for i in range(len(breeds)): map_characters[i] = breeds[i] print("<item>" + breeds[i] + "</item>") >>> <item>affenpinscher</item> >>> <item>afghan_hound</item> >>> <item>african_hunting_dog</item> >>> <item>airedale</item> >>> <item>american_staffordshire_terrier</item> >>> <item>appenzeller</item> 


Pemrosesan gambar



Kita akan menggunakan fitur pustaka Keras yang disebut ImageDataGenerators. ImageDataGenerator dapat memproses gambar, skala, memutar, dan sebagainya. Itu juga dapat menerima fungsi pemrosesan yang dapat memproses gambar tambahan.

 def preprocess(img): img = cv2.resize(img, (IMAGE_SIZE, IMAGE_SIZE), interpolation = cv2.INTER_AREA) # or use ImageDataGenerator( rescale=1./255... img_1 = image.img_to_array(img) img_1 = cv2.resize(img_1, (IMAGE_SIZE, IMAGE_SIZE), interpolation = cv2.INTER_AREA) img_1 = np.expand_dims(img_1, axis=0) / 255. #img = cv2.blur(img,(5,5)) return img_1[0] 


Perhatikan kode berikut:

 # or use ImageDataGenerator( rescale=1./255... 


Kita dapat menormalkan (sub-data di bawah rentang 0-1 alih-alih yang asli 0-255) di ImageDataGenerator itu sendiri. Lalu mengapa kita membutuhkan preprosesor? Sebagai contoh, pertimbangkan panggilan kabur (dikomentari, saya tidak menggunakannya): ini adalah manipulasi gambar khusus yang sama yang dapat arbitrer. Apa pun yang kontras dengan HDR.

Kami akan menggunakan dua ImageDataGenerators yang berbeda, satu untuk pelatihan dan satu untuk validasi. Perbedaannya adalah bahwa untuk pelatihan kita perlu belokan dan penskalaan untuk meningkatkan "variasi" data, tetapi untuk validasi, kita tidak membutuhkannya, setidaknya tidak dalam tugas ini.

 train_datagen = ImageDataGenerator( preprocessing_function=preprocess, #rescale=1./255, # done in preprocess() # randomly rotate images (degrees, 0 to 30) rotation_range=30, # randomly shift images horizontally # (fraction of total width) width_shift_range=0.3, height_shift_range=0.3, # randomly flip images horizontal_flip=True, ,vertical_flip=False, zoom_range=0.3) val_datagen = ImageDataGenerator( preprocessing_function=preprocess) train_gen = train_datagen.flow_from_directory( working_path + "train/", batch_size=BATCH_SIZE, target_size=(IMAGE_SIZE, IMAGE_SIZE), shuffle=True, class_mode="categorical") val_gen = val_datagen.flow_from_directory( working_path + "valid/", batch_size=BATCH_SIZE, target_size=(IMAGE_SIZE, IMAGE_SIZE), shuffle=True, class_mode="categorical") 


Menciptakan jaringan saraf



Seperti yang telah disebutkan, kita akan membuat beberapa jenis jaringan saraf. Setiap kali kita memanggil fungsi lain untuk dibuat, sertakan file lain dan kadang-kadang menentukan ukuran gambar yang berbeda. Jadi, untuk beralih di antara berbagai jenis jaringan saraf, kita harus mengomentari / menghapus komentar kode yang sesuai.

Pertama-tama, buat CNN "vanilla". Itu tidak berfungsi dengan baik, karena saya memutuskan untuk tidak membuang waktu men-debug-nya, tetapi setidaknya itu memberikan dasar yang dapat dikembangkan jika ada keinginan (biasanya ini adalah ide yang buruk, karena jaringan pra-terlatih memberikan hasil terbaik).

 def createModelVanilla(): model = Sequential() # Note the (7, 7) here. This is one of technics # used to reduce memory use by the NN: we scan # the image in a larger steps. # Also note regularizers.l2: this technic is # used to prevent overfitting. The "0.001" here # is an empirical value and can be optimized. model.add(Conv2D(16, (7, 7), padding='same', use_bias=False, input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3), kernel_regularizer=regularizers.l2(0.001))) # Note the use of a standard CNN building blocks: # Conv2D - BatchNormalization - Activation # MaxPooling2D - Dropout # The last two are used to avoid overfitting, also, # MaxPooling2D reduces memory use. model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(16, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(32, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Conv2D(32, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(64, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Conv2D(64, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(128, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Conv2D(128, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(256, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Conv2D(256, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) # This is the end on "convolutional" part of CNN. # Now we need to transform multidementional # data into one-dim. array for a fully-connected # classifier: model.add(Flatten()) # And two layers of classifier itself (plus an # Activation layer in between): model.add(Dense(NUM_CLASSES, activation='softmax', kernel_regularizer=regularizers.l2(0.01))) model.add(Activation("relu")) model.add(Dense(NUM_CLASSES, activation='softmax', kernel_regularizer=regularizers.l2(0.01))) # We need to compile the resulting network. # Note that there are few parameters we can # try here: the best performing one is uncommented, # the rest is commented out for your reference. #model.compile(optimizer='rmsprop', # loss='categorical_crossentropy', # metrics=['accuracy']) #model.compile( # optimizer=keras.optimizers.RMSprop(lr=0.0005), # loss='categorical_crossentropy', # metrics=['accuracy']) model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) #model.compile(optimizer='adadelta', # loss='categorical_crossentropy', # metrics=['accuracy']) #opt = keras.optimizers.Adadelta(lr=1.0, # rho=0.95, epsilon=0.01, decay=0.01) #model.compile(optimizer=opt, # loss='categorical_crossentropy', # metrics=['accuracy']) #opt = keras.optimizers.RMSprop(lr=0.0005, # rho=0.9, epsilon=None, decay=0.0001) #model.compile(optimizer=opt, # loss='categorical_crossentropy', # metrics=['accuracy']) # model.summary() return(model) 


Saat kami membuat jaringan menggunakan pembelajaran transfer , prosedurnya berubah:

 def createModelMobileNetV2(): # First, create the NN and load pre-trained # weights for it ('imagenet') # Note that we are not loading last layers of # the network (include_top=False), as we are # going to add layers of our own: base_model = MobileNetV2(weights='imagenet', include_top=False, pooling='avg', input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)) # Then attach our layers at the end. These are # to build "classifier" that makes sense of # the patterns previous layers provide: x = base_model.output x = Dense(512)(x) x = Activation('relu')(x) x = Dropout(0.5)(x) predictions = Dense(NUM_CLASSES, activation='softmax')(x) # Create a model model = Model(inputs=base_model.input, outputs=predictions) # We need to make sure that pre-trained # layers are not changed when we train # our classifier: # Either this: #model.layers[0].trainable = False # or that: for layer in base_model.layers: layer.trainable = False # As always, there are different possible # settings, I tried few and chose the best: # model.compile(optimizer='adam', # loss='categorical_crossentropy', # metrics=['accuracy']) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) #model.summary() return(model) 


Membuat jenis jaringan lain mengikuti pola yang sama:

 def createModelResNet50(): base_model = ResNet50(weights='imagenet', include_top=False, pooling='avg', input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)) x = base_model.output x = Dense(512)(x) x = Activation('relu')(x) x = Dropout(0.5)(x) predictions = Dense(NUM_CLASSES, activation='softmax')(x) model = Model(inputs=base_model.input, outputs=predictions) #model.layers[0].trainable = False # model.compile(loss='categorical_crossentropy', # optimizer='adam', metrics=['accuracy']) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) #model.summary() return(model) 


Peringatan: pemenang! NN ini menunjukkan hasil terbaik:

 def createModelInceptionV3(): # model.layers[0].trainable = False # model.compile(optimizer='sgd', # loss='categorical_crossentropy', # metrics=['accuracy']) base_model = InceptionV3(weights = 'imagenet', include_top = False, input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)) x = base_model.output x = GlobalAveragePooling2D()(x) x = Dense(512, activation='relu')(x) predictions = Dense(NUM_CLASSES, activation='softmax')(x) model = Model(inputs = base_model.input, outputs = predictions) for layer in base_model.layers: layer.trainable = False # model.compile(optimizer='adam', # loss='categorical_crossentropy', # metrics=['accuracy']) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) #model.summary() return(model) 


Satu lagi:

 def createModelNASNetMobile(): # model.layers[0].trainable = False # model.compile(optimizer='sgd', # loss='categorical_crossentropy', # metrics=['accuracy']) base_model = NASNetMobile(weights = 'imagenet', include_top = False, input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)) x = base_model.output x = GlobalAveragePooling2D()(x) x = Dense(512, activation='relu')(x) predictions = Dense(NUM_CLASSES, activation='softmax')(x) model = Model(inputs = base_model.input, outputs = predictions) for layer in base_model.layers: layer.trainable = False # model.compile(optimizer='adam', # loss='categorical_crossentropy', # metrics=['accuracy']) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) #model.summary() return(model) 


Berbagai jenis jaringan saraf dapat digunakan untuk tugas yang berbeda. Jadi, selain persyaratan untuk akurasi prediksi, ukuran juga penting (NN seluler 5 kali lebih kecil dari Inception) dan kecepatan (jika kita membutuhkan pemrosesan streaming video secara real-time, maka akurasi harus dikorbankan).

Pelatihan jaringan saraf



Pertama-tama, kami bereksperimen , jadi kami harus dapat menghapus jaringan saraf yang telah kami simpan, tetapi tidak lagi digunakan. Fungsi berikut menghapus NN jika ada:

 # Make sure that previous "best network" is deleted. def deleteSavedNet(best_weights_filepath): if(os.path.isfile(best_weights_filepath)): os.remove(best_weights_filepath) print("deleteSavedNet():File removed") else: print("deleteSavedNet():No file to remove") 


Cara kita membuat dan menghapus jaringan saraf cukup sederhana dan mudah. Pertama, hapus. Saat memanggil delete (hanya), harus diingat bahwa Jupiter Notebook memiliki fungsi "run selection", pilih hanya apa yang ingin Anda gunakan, dan jalankan.

Kemudian kita membuat jaringan saraf jika file-nya tidak ada, atau memanggil beban jika ada: tentu saja, kita tidak bisa memanggil "delete" dan kemudian mengharapkan NN ada, jadi untuk menggunakan jaringan saraf yang disimpan, jangan panggil delete .

Dengan kata lain, kita dapat membuat NN baru, atau menggunakan NN yang sudah ada, tergantung pada situasinya dan pada apa yang sedang kita eksperimenkan. Skenario sederhana: kami melatih jaringan saraf, lalu pergi berlibur. Mereka kembali, dan Google memakukan sesi, jadi kita perlu memuat yang disimpan sebelumnya: komentar "hapus" dan hapus komentar "muat".

 deleteSavedNet(working_path + strModelFileName) #if not os.path.exists(working_path + "models"): # os.makedirs(working_path + "models") # #if not os.path.exists(working_path + # strModelFileName): # model = createModelResNet50() model = createModelInceptionV3() # model = createModelMobileNetV2() # model = createModelNASNetMobile() #else: # model = load_model(working_path + strModelFileName) 


Pos pemeriksaan adalah elemen yang sangat penting dari program kami. Kita dapat membuat berbagai fungsi yang harus dipanggil di akhir setiap era pelatihan, dan meneruskannya ke pos pemeriksaan. Misalnya, Anda dapat menyimpan jaringan saraf jika itu menunjukkan hasil yang lebih baik daripada yang sudah disimpan.

 checkpoint = ModelCheckpoint(working_path + strModelFileName, monitor='val_acc', verbose=1, save_best_only=True, mode='auto', save_weights_only=False) callbacks_list = [ checkpoint ] 


Akhirnya, kami mengajarkan jaringan saraf pada set pelatihan:

 # Calculate sizes of training and validation sets STEP_SIZE_TRAIN=train_gen.n//train_gen.batch_size STEP_SIZE_VALID=val_gen.n//val_gen.batch_size # Set to False if we are experimenting with # some other part of code, use history that # was calculated before (and is still in # memory bDoTraining = True if bDoTraining == True: # model.fit_generator does the actual training # Note the use of generators and callbacks # that were defined earlier history = model.fit_generator(generator=train_gen, steps_per_epoch=STEP_SIZE_TRAIN, validation_data=val_gen, validation_steps=STEP_SIZE_VALID, epochs=EPOCHS, callbacks=callbacks_list) # --- After fitting, load the best model # This is important as otherwise we'll # have the LAST model loaded, not necessarily # the best one. model.load_weights(working_path + strModelFileName) # --- Presentation part # summarize history for accuracy plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['acc', 'val_acc'], loc='upper left') plt.show() # summarize history for loss plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['loss', 'val_loss'], loc='upper left') plt.show() # As grid optimization of NN would take too long, # I did just few tests with different parameters. # Below I keep results, commented out, in the same # code. As you can see, Inception shows the best # results: # Inception: # adam: val_acc 0.79393 # sgd: val_acc 0.80892 # Mobile: # adam: val_acc 0.65290 # sgd: Epoch 00015: val_acc improved from 0.67584 to 0.68469 # sgd-30 epochs: 0.68 # NASNetMobile, adam: val_acc did not improve from 0.78335 # NASNetMobile, sgd: 0.8 


Grafik untuk akurasi dan kehilangan untuk yang terbaik dari konfigurasi adalah sebagai berikut:




Seperti yang Anda lihat, jaringan saraf sedang belajar, dan tidak buruk.

Pengujian jaringan saraf



Setelah pelatihan selesai, kita harus menguji hasilnya; untuk ini, NN menyajikan gambar-gambar yang belum pernah dilihatnya - foto-foto yang kami salin ke dalam folder pengujian - satu untuk setiap jenis anjing.

 # --- Test j = 0 # Final cycle performs testing on the entire # testing set. for file_name in os.listdir( working_path + "test/"): img = image.load_img(working_path + "test/" + file_name); img_1 = image.img_to_array(img) img_1 = cv2.resize(img_1, (IMAGE_SIZE, IMAGE_SIZE), interpolation = cv2.INTER_AREA) img_1 = np.expand_dims(img_1, axis=0) / 255. y_pred = model.predict_on_batch(img_1) # get 5 best predictions y_pred_ids = y_pred[0].argsort()[-5:][::-1] print(file_name) for i in range(len(y_pred_ids)): print("\n\t" + map_characters[y_pred_ids[i]] + " (" + str(y_pred[0][y_pred_ids[i]]) + ")") print("--------------------\n") j = j + 1 


Ekspor jaringan saraf ke aplikasi Java



Pertama-tama, kita perlu mengatur pemuatan jaringan saraf dari disk. Alasannya jelas: ekspor terjadi di blok kode lain, jadi kemungkinan besar kita akan memulai ekspor secara terpisah - ketika jaringan saraf dibawa ke keadaan optimal. Artinya, segera sebelum ekspor, dalam menjalankan program yang sama, kami tidak akan melatih jaringan. Jika Anda menggunakan kode yang ditunjukkan di sini, maka tidak ada perbedaan, jaringan optimal telah dipilih untuk Anda. Tetapi jika Anda mempelajari sesuatu dari Anda sendiri, maka untuk melatih segala sesuatu yang baru sebelum menabung adalah buang-buang waktu, jika sebelumnya Anda menyimpan semuanya.

 # Test: load and run model = load_model(working_path + strModelFileName) 


Untuk alasan yang sama - jangan melompati kode - saya menyertakan file yang diperlukan untuk ekspor di sini. Tidak ada yang mengganggu Anda untuk memindahkan mereka ke awal program jika selera kecantikan Anda mengharuskannya:

 from keras.models import Model from keras.models import load_model from keras.layers import * import os import sys import tensorflow as tf 


Pengujian kecil setelah memuat jaringan saraf, hanya untuk memastikan semuanya dimuat - berfungsi:

 img = image.load_img(working_path + "test/affenpinscher.jpg") #basset.jpg") img_1 = image.img_to_array(img) img_1 = cv2.resize(img_1, (IMAGE_SIZE, IMAGE_SIZE), interpolation = cv2.INTER_AREA) img_1 = np.expand_dims(img_1, axis=0) / 255. y_pred = model.predict(img_1) Y_pred_classes = np.argmax(y_pred,axis = 1) # print(y_pred) fig, ax = plt.subplots() ax.imshow(img) ax.axis('off') ax.set_title(map_characters[Y_pred_classes[0]]) plt.show() 


gambar

Selanjutnya, kita perlu mendapatkan nama-nama lapisan input dan output dari jaringan (baik ini atau fungsi pembuatan, kita harus secara eksplisit "memberi nama" lapisan, yang tidak kita lakukan).

 model.summary() >>> Layer (type) >>> ====================== >>> input_7 (InputLayer) >>> ______________________ >>> conv2d_283 (Conv2D) >>> ______________________ >>> ... >>> dense_14 (Dense) >>> ====================== >>> Total params: 22,913,432 >>> Trainable params: 1,110,648 >>> Non-trainable params: 21,802,784 


Kami akan menggunakan nama-nama lapisan input dan output nanti ketika kami mengimpor jaringan saraf ke aplikasi Java.

Kode lain berkeliaran di jaringan untuk mendapatkan data ini:

 def print_graph_nodes(filename): g = tf.GraphDef() g.ParseFromString(open(filename, 'rb').read()) print() print(filename) print("=======================INPUT===================") print([n for n in g.node if n.name.find('input') != -1]) print("=======================OUTPUT==================") print([n for n in g.node if n.name.find('output') != -1]) print("===================KERAS_LEARNING==============") print([n for n in g.node if n.name.find('keras_learning_phase') != -1]) print("===============================================") print() #def get_script_path(): # return os.path.dirname(os.path.realpath(sys.argv[0])) 


Tetapi saya tidak menyukainya dan saya tidak merekomendasikannya.

Kode berikut akan mengekspor Keras Neural Network ke format pb , yang akan kami tangkap dari Android.

 def keras_to_tensorflow(keras_model, output_dir, model_name,out_prefix="output_", log_tensorboard=True): if os.path.exists(output_dir) == False: os.mkdir(output_dir) out_nodes = [] for i in range(len(keras_model.outputs)): out_nodes.append(out_prefix + str(i + 1)) tf.identity(keras_model.output[i], out_prefix + str(i + 1)) sess = K.get_session() from tensorflow.python.framework import graph_util from tensorflow.python.framework graph_io init_graph = sess.graph.as_graph_def() main_graph = graph_util.convert_variables_to_constants( sess, init_graph, out_nodes) graph_io.write_graph(main_graph, output_dir, name=model_name, as_text=False) if log_tensorboard: from tensorflow.python.tools import import_pb_to_tensorboard import_pb_to_tensorboard.import_to_tensorboard( os.path.join(output_dir, model_name), output_dir) 


Memanggil fungsi-fungsi ini untuk mengekspor jaringan saraf:

 model = load_model(working_path + strModelFileName) keras_to_tensorflow(model, output_dir=working_path + strModelFileName, model_name=working_path + "models/dogs.pb") print_graph_nodes(working_path + "models/dogs.pb") 


Baris terakhir mencetak struktur jaringan saraf yang dihasilkan.

Membuat aplikasi Android menggunakan jaringan saraf



Ekspor jaringan saraf di Android diformalkan dengan baik dan seharusnya tidak menyebabkan kesulitan. Ada, seperti biasa, beberapa cara, kami menggunakan yang paling (pada saat penulisan) populer.

Pertama-tama, kami menggunakan Android Studio untuk membuat proyek baru. Kami akan “berhemat” karena tugas kami bukan tutorial android. Jadi aplikasi hanya akan berisi satu aktivitas.

gambar

Seperti yang Anda lihat, kami menambahkan folder "aset" dan menyalin jaringan saraf kami ke dalamnya (yang sebelumnya kami ekspor).

File Gradle



Dalam file ini, Anda perlu membuat beberapa perubahan. Pertama-tama, kita perlu mengimpor perpustakaan tensorflow-android . Ini digunakan untuk bekerja dengan Tensorflow (dan, karenanya, Keras) dari Jawa:

gambar

Satu lagi penghalang yang tidak terlihat: versionCode dan versionName . Saat aplikasi berubah, Anda harus mengunggah versi baru di Google Play. Tanpa mengubah versi di gdadle (misalnya, 1 -> 2 -> 3 ...) Anda tidak dapat melakukan ini, Google akan memberikan kesalahan "versi ini sudah ada."

Terwujud



Pertama-tama, aplikasi kita akan “berat” - 100 Mb Neural Network akan dengan mudah masuk ke dalam memori ponsel modern, tetapi membuka contoh terpisah untuk setiap foto yang “dibagikan” dari Facebook jelas merupakan ide yang buruk.

Jadi kami melarang untuk membuat lebih dari satu contoh aplikasi kami:

 <activity android:name=".MainActivity" android:launchMode="singleTask"> 


Dengan menambahkan android: launchMode = "singleTask" ke MainActivity, kami memberi tahu Android untuk membuka (mengaktifkan) salinan aplikasi yang ada, alih-alih membuat instance lain.

Maka kita perlu memasukkan aplikasi kita dalam daftar, yang ditampilkan oleh sistem ketika seseorang “membagikan” gambar:

 <intent-filter> <!-- Send action required to display activity in share list --> <action android:name="android.intent.action.SEND" /> <!-- Make activity default to launch --> <category android:name="android.intent.category.DEFAULT" /> <!-- Mime type ie what can be shared with this activity only image and text --> <data android:mimeType="image/*" /> </intent-filter> 


Terakhir, kita perlu meminta fitur dan izin yang akan digunakan aplikasi kita:

 <uses-feature android:name="android.hardware.camera" android:required="true" /> <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" /> 


Jika Anda terbiasa dengan pemrograman untuk Android, bagian ini seharusnya tidak menimbulkan pertanyaan.

Aplikasi tata letak.



Kami akan membuat dua tata letak, satu untuk potret dan satu untuk lanskap. Seperti inilah tampilan Portrait .

Apa yang akan kami tambahkan: bidang besar (tampilan) untuk menampilkan gambar, daftar iklan yang mengganggu (ditampilkan ketika tombol dengan tulang ditekan), tombol Bantuan, tombol untuk mengunduh gambar dari File / Galeri dan mengambil dari kamera, dan akhirnya (awalnya tersembunyi) tombol "Proses" untuk pemrosesan gambar.

gambar

Aktivitas itu sendiri berisi semua logika menampilkan dan menyembunyikan, serta mengaktifkan / menonaktifkan tombol, tergantung pada keadaan aplikasi.

Mainaktivitas



Aktivitas ini mewarisi (meluas) Aktivitas Android standar:

 public class MainActivity extends Activity 


Pertimbangkan kode yang bertanggung jawab untuk pengoperasian jaringan saraf.

Pertama-tama, jaringan saraf menerima Bitmap. Awalnya, ini adalah Bitmap besar (ukuran sewenang-wenang) dari kamera atau dari file (m_bitmap), kemudian kami mengubahnya, mengarah ke 256x256 piksel standar (m_bitmapForNn). Kami juga menyimpan ukuran bitmap (256) dalam sebuah konstanta:

 static Bitmap m_bitmap = null; static Bitmap m_bitmapForNn = null; private int m_nImageSize = 256; 


Kita harus memberi tahu jaringan saraf nama dari lapisan input dan output; kami menerimanya lebih awal (lihat daftar), tetapi perlu diingat bahwa dalam kasus Anda mereka mungkin berbeda:

 private String INPUT_NAME = "input_7_1"; private String OUTPUT_NAME = "output_1"; 


Kemudian kami mendeklarasikan variabel untuk memegang objek TensofFlow. Juga, kami menyimpan path ke file jaringan saraf (yang terletak pada aset):

private TensorFlowInferenceInterface tf;
private String MODEL_PATH = 
	"file: ///android_asset/dogs.pb";


Kami menyimpan trah anjing dalam daftar, sehingga nanti akan ditampilkan kepada pengguna, bukan indeks array:
 private String[] m_arrBreedsArray; 


Awalnya, kami mengunduh Bitmap. Namun, jaringan saraf mengharapkan array nilai RGB, dan outputnya adalah array probabilitas yang berkembang biak ini adalah apa yang ditunjukkan pada gambar. Oleh karena itu, kita perlu menambahkan dua array lagi (perhatikan bahwa 120 adalah jumlah ras anjing yang ada dalam data pelatihan kami):

 private float[] m_arrPrediction = new float[120]; private float[] m_arrInput = null; 


Unduh perpustakaan inferensi tensorflow:

 static { System.loadLibrary("tensorflow_inference"); } 


Karena operasi jaringan saraf membutuhkan waktu, kita perlu menjalankannya di utas terpisah, jika tidak ada kemungkinan bahwa kita akan menerima pesan sistem "aplikasi tidak merespons", belum lagi pengguna yang tidak puas.

 class PredictionTask extends AsyncTask<Void, Void, Void> { @Override protected void onPreExecute() { super.onPreExecute(); } // --- @Override protected Void doInBackground(Void... params) { try { # We get RGB values packed in integers # from the Bitmap, then break those # integers into individual triplets m_arrInput = new float[ m_nImageSize * m_nImageSize * 3]; int[] intValues = new int[ m_nImageSize * m_nImageSize]; m_bitmapForNn.getPixels(intValues, 0, m_nImageSize, 0, 0, m_nImageSize, m_nImageSize); for (int i = 0; i < intValues.length; i++) { int val = intValues[i]; m_arrInput[i * 3 + 0] = ((val >> 16) & 0xFF) / 255f; m_arrInput[i * 3 + 1] = ((val >> 8) & 0xFF) / 255f; m_arrInput[i * 3 + 2] = (val & 0xFF) / 255f; } // --- tf = new TensorFlowInferenceInterface( getAssets(), MODEL_PATH); //Pass input into the tensorflow tf.feed(INPUT_NAME, m_arrInput, 1, m_nImageSize, m_nImageSize, 3); //compute predictions tf.run(new String[]{OUTPUT_NAME}, false); //copy output into PREDICTIONS array tf.fetch(OUTPUT_NAME, m_arrPrediction); } catch (Exception e) { e.getMessage(); } return null; } // --- @Override protected void onPostExecute(Void result) { super.onPostExecute(result); // --- enableControls(true); // --- tf = null; m_arrInput = null; # strResult contains 5 lines of text # with most probable dog breeds and # their probabilities m_strResult = ""; # What we do below is sorting the array # by probabilities (using map) # and getting in reverse order) the # first five entries TreeMap<Float, Integer> map = new TreeMap<Float, Integer>( Collections.reverseOrder()); for(int i = 0; i < m_arrPrediction.length; i++) map.put(m_arrPrediction[i], i); int i = 0; for (TreeMap.Entry<Float, Integer> pair : map.entrySet()) { float key = pair.getKey(); int idx = pair.getValue(); String strBreed = m_arrBreedsArray[idx]; m_strResult += strBreed + ": " + String.format("%.6f", key) + "\n"; i++; if (i > 5) break; } m_txtViewBreed.setVisibility(View.VISIBLE); m_txtViewBreed.setText(m_strResult); } } 


Di onCreate () dari MainActivity, kita perlu menambahkan onClickListener untuk tombol "Proses":

 m_btn_process.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { processImage(); } }); 


Di sini processImage () hanya memanggil utas yang kami jelaskan di atas:

 private void processImage() { try { enableControls(false); // --- PredictionTask prediction_task = new PredictionTask(); prediction_task.execute(); } catch (Exception e) { e.printStackTrace(); } } 


Catatan tambahan



Kami tidak berencana untuk membahas rincian pemrograman UI untuk Android, karena ini tentu saja tidak berlaku untuk tugas porting jaringan saraf. Namun, satu hal masih layak disebut.

Ketika kami mencegah pembuatan instance tambahan dari aplikasi kami, kami juga melanggar urutan normal pembuatan dan penghapusan aktivitas (aliran kontrol): jika Anda "berbagi" gambar dari Facebook, dan kemudian membagikan yang lain, maka aplikasi tidak akan memulai ulang. Ini berarti bahwa cara "tradisional" untuk menangkap data yang ditransfer di onCreate tidak akan cukup, karena onCreate tidak akan dipanggil.

Berikut cara mengatasi masalah ini:

1. Di onCreate di MainActivity, panggil fungsi onSharedIntent:

 protected void onCreate( Bundle savedInstanceState) { super.onCreate(savedInstanceState); .... onSharedIntent(); .... 


Kami juga menambahkan handler untuk onNewIntent:

 @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); onSharedIntent(); } 


Ini adalah fungsi onSharedIntent itu sendiri:
 private void onSharedIntent() { Intent receivedIntent = getIntent(); String receivedAction = receivedIntent.getAction(); String receivedType = receivedIntent.getType(); if (receivedAction.equals(Intent.ACTION_SEND)) { // If mime type is equal to image if (receivedType.startsWith("image/")) { m_txtViewBreed.setText(""); m_strResult = ""; Uri receivedUri = receivedIntent.getParcelableExtra( Intent.EXTRA_STREAM); if (receivedUri != null) { try { Bitmap bitmap = MediaStore.Images.Media.getBitmap( this.getContentResolver(), receivedUri); if(bitmap != null) { m_bitmap = bitmap; m_picView.setImageBitmap(m_bitmap); storeBitmap(); enableControls(true); } } catch (Exception e) { e.printStackTrace(); } } } } } 


Sekarang kami memproses data yang ditransfer di onCreate (jika aplikasi tidak ada dalam memori) atau di onNewIntent (jika diluncurkan sebelumnya).




Semoga beruntungJika Anda menyukai artikel ini, silakan “suka” dengan semua cara yang mungkin, ada juga tombol “sosial” di situs .

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


All Articles