Autók detektálása mély neurális hálózat segítségével Android készüléken vizuális médiumon

Gróf Attila Bálint miután 2017 júniusában sikeresen megvédte BSc szakdolgozatát “Mély neurális hálózatok futtatása Android okostelefonon” témában, ősztől új, de kapcsolódó témában kezdett el az MSc önálló laboratórium keretein belül dolgozni: vezetést segítő funkciók okoseszközökön. Kutató és fejlesztőmunkájának első lépése számos technikai és elméleti kihívást tartalmazott, amiket sikeresen lekűzdött!

Attila ebben a cikkben foglalja össze eredményeit, tapasztalatit és forráskóddal együtt ismerteti a fejlesztés részleteit.

Bevezetés

Mára a mély tanulás paradigmán alapuló mély neurális hálózatok a gépi tanulás talán legaktívabban kutatott ágazatává váltak. A mély neuronhálók nagy mennyiségű adatot képesek hatékonyan feldolgozni és ezek alapján modellt építeni. A számítógépek (különösképpen a grafikus gyorsítókártyák) számítási kapacitás növekedésének köszönhetően egyre nagyobb és bonyolultabb modelleket vagyunk képesek tanítani belátható időn belül. A mély neurális hálózatok címkézésben, osztályozásban és regressziós feladatokban, bizonyos körülmények mellett már az emberi pontosságot is képesek elérni és akár megelőzni azt.

Napjainkban az okostelefonok behálózzák a mindennapjainkat, ezeket használjuk telefonos beszélgetésre, játékra, tájékozódásra és egyre nagyobb elvárásaink vannak a funkciókat illetően. A technológia gyors fejlődésének köszönhetően sok esetben már a PC-k és a laptopok feladatait képesek ellátni a mobiltelefonok. Sok Android alkalmazásban már olyan funkciók vannak, amelyek háttérében valamilyen mély neurális hálózat dolgozik. Ehhez azonban sok esetben szükség van internet kapcsolatra, mert ezek az algoritmusok nagy teljesítményű szervereken futnak, és onnan szolgáltatnak adatokat az alkalmazásnak. A képek osztályozásában, objektumok detektálásában és más képfeldolgozási feladatokban is jó eredményt lehet elérni velük.

Ebben a cikkben azt fogom bemutatni, hogy milyen módon lehet autókat detektálni Android készüléken mély neurális hálózat segítségével.

Mély neurális hálózatok áttekintés

A mély tanuló rendszerek (angolul: deep neural networks) olyan tanuló algoritmusok összesége, melyek sok rétegen keresztül jellemzőket próbálnak megtanulni az adatokból és egy lépésben modellezni azokat. A mély tanuló rendszerek kifejezést leginkább a mély neurális hálózatoknál használják. Először az architektúra megalkotása, majd annak tanítása következik. A tanítás igen erőforrás igényes feladat, így ezeket jellemzően nagy teljesítményű számítógépeken futtatják. A tanított hálózat ezután már képes új adatokra predikciót adni. A következtetés kiszámítása kevesebb erőforrást igényel, mint a tanítás. Korlátozott erőforrású eszközökön a nagy méretű hálózatok tanítása nem reális, memória és számítási kapacitás hiányában gyakorlatilag lehetetlen. A tanított modell predikciójának kiszámolásához viszont nincs szükség olyan nagyságú erőforrásra, mint a tanításnál.

(Az eredeti ábra az NVidia-tól származik)

A mély neurális hálózat modellek Androidon való futtatásához először el kell végezni néhány lépést, hogy a predikciók kiszámításához szükséges adatok egy un. protobuf (.pb) fájlban összeálljanak. A protobuf fájl tartalmazza a mély neurális hálózat topológiáját és a tanítás során kiszámolt súlyokat. A következtetés számítása közben a topológia határozza meg, hogy milyen műveleteket kell elvégezni, a súlyok pedig ennek a paramétereit adja meg.

Autó adatbázis

A mély neurális hálózatnak szüksége van tanító és tesztelő adatokra. Ezen adatok segítségével képes a hálózat a jellemzőket megtanulni. Egyre több nyílt adatbázis érhető el, ezek közül az autók témakörében a GTI (Grupo de Tratamiento de Imágenes) által létrehozott adatbázist választottam [https://www.gti.ssr.upm.es/data/Vehicle_database.html]. Az autók több szemszögből, különböző időjárási és megvilágítási helyzetekben vannak megörökítve. Autó képek az adatbázisban:

Nem autós képek az adatbázisban:

Mély neurális hálózat architektúra, tanítás és a modell mentése

Ennek a neurális hálózatnak a feladata, hogy egy adott képre megmondja, hogy azon szerepel-e autó. Ezt a mély neurális hálózatot Keras [https://keras.io/] könyvtár segítségével Python nyelven írtam. A hálózat bemenete egy 64×64-es kép RGB színcsatornákkal. A kimeneten az 0 -1 jelenik meg abban az esetben, ha a képen nem szerepel autó és 1–0 ha szerepel (One-Hot kódolás).

A mély neurális hálózat konvolúciós (4 db), előre csatolt (2 db), max pool (3 db) és dropout (4 db) rétegekből állt össze. Az alábbi kódrészletben a hálózat létrehozása látható.

def mely_neuralis_halozat_init(input_shape = (64,64,3), filename="weights.h5"):
# Modell létrehozása
model = Sequential()
model.add(Conv2D(4, (2, 2), input_shape=input_shape, padding='same', activation='relu'))
model.add(Dropout(0.2))
model.add(Conv2D(8, (2, 2), activation='relu', padding='same'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Conv2D(16, (2, 2), activation='relu', padding='same'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Conv2D(32, (2, 2), activation='relu', padding='same'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1, activation='softmax')
return model

Az adatbázis letöltése után a képeket az OpenCV könyvtár segítségével be lehet olvasni és a megfelelő kimenettel (autó vagy nem autó) eltárolni.

# Adatábzis betöltése
raw_data_car = glob.glob("./Database/vehicles/*/*.png")
raw_data_non_car = glob.glob("./Database/non-vehicles/*/*.png")
# Adatoknak lista
all_data_input = []
all_data_output = []
# Képek betöltése
for pic in raw_data_car:
all_data_input.append(cv2.cvtColor(cv2.imread(pic), cv2.COLOR_BGR2RGB))
all_data_output.append((1,0)) # Autó
for pic in raw_data_non_car:
all_data_input.append(cv2.cvtColor(cv2.imread(pic), cv2.COLOR_BGR2RGB))
all_data_output.append((0,1)) # Nme autó

Az adatokat a feldolgozás után olyan struktúrába kell szervezni, hogy az a hálózat bemenetének megfeleljen, ami ebben az esetben:

  • Tanító adatok bemenet: (15984, 64, 64, 3)
  • Tanító adatok kimenet: (15984, 2)
  • Tesztelő adatok bemenet: (1775, 64, 64, 3)
  • Tesztelő adatok kimenet: (1775, 2)

A hálózatot 50 ciklusig tanítottam, egy tanítási ciklusban a képek 256-os kötegekben kerültek a hálózat bemenetére. A tanításhoz kereszt entrópiát és adadelta (Adaptive Learning Rate Method) optimalizálót használtam. A tanítás lefutása után a mély neurális hálózat modellt és a hálózat súlyait el kell menteni, hogy később az Android alkalmazásban felhasználható legyen. A kimentett modell fájl ebben az eseteben olyan ~ 2 MB nagyságú lesz.

# Modell létrehozása
model = build_conv_model(input_shape)
model.compile(loss='mean_squared_error',optimizer='sgd',metrics=['accuracy'])
# Modell tanítása
model.fit(train_data_input, train_data_output, batch_size=256, epochs=25, verbose=1, validation_data=(test_data_input, test_data_output2))
# Tesztelés
score = model.evaluate(test_data_input, test_data_output, verbose=0)
# Modell és súlyok metése
model.save("model.h5")
model.save_weights("weights.h5")

A tanított és elmentett modell fájlt át kell alakítani Protobuf formátumra. Ez Keras-ban könnyen megvalósítható. A Protobuf fájl mérete a modell nagyságától függ, egy nagyobb modell esetén ez több 100 MB is lehet.

from keras.models import load_model
import tensorflow as tf
import os
import os.path as osp
from keras import backend as K
from tensorflow.python.framework import graph_util
from tensorflow.python.framework import graph_io
# Kezdeti értékek beállítása
weight_file = 'model.h5'
num_output = 1
write_graph_def_ascii_flag = True
prefix_output_node_names_of_final_network = 'output'
output_graph_name = 'car.pb'
output_fld=""
#Mentés helye
output_fld = 'tensorflow_model/'
if not os.path.isdir(output_fld):
os.mkdir(output_fld)
# Súlyok betöltése
K.set_learning_phase(0)
net_model = load_model(weight_file)
pred = [None]*num_output
pred_node_names = [None]*num_output
for i in range(num_output):
pred_node_names[i] = prefix_output_node_names_of_final_network+str(i)
pred[i] = tf.identity(net_model.output[i], name=pred_node_names[i])
# Kimeneti csomópont neve
print('Kimeneti csomópontok neve: ', pred_node_names)
sess = K.get_session()
# Gráf definíció
if write_graph_def_ascii_flag:
f = 'only_the_graph_def.pb.ascii'
tf.train.write_graph(sess.graph.as_graph_def(), output_fld, f, as_text=True)
print('Mentett ascii fájl neve: ', osp.join(output_fld, f))
# Protobuf fájl előállítása
constant_graph = graph_util.convert_variables_to_constants(sess, sess.graph.as_graph_def(), pred_node_names)
graph_io.write_graph(constant_graph, output_fld, output_graph_name, as_text=False)
print('Mentett protobuf fájl: ', osp.join(output_fld, output_graph_name))

Android alkalmazás

Az alábbi kép az alkalmazás rendszertervét mutatja.

Az alkalmazás legfontosabb osztályai/csomagjai:

  • assets: Ebben a mappában található meg a mély neurális hálózat modell.
  • CameraActivity: A kamera modul életciklusának kezelése (indítása, leállítása).
  • ConvNet: A mély neurális hálózat adatait tároló osztály, a modell betöltése itt történik és a predikció számolását is végzi.
  • ImageProcessing: A kamera képét dolgozza fel a mély neurális hálózatnak megfelelő formátumra konvertálja át.
  • Függőségek: TensorFlow futásához szükséges előre lefordított C++ és Java kód függőségek.
  • Manifest: A szükséges engedélyek deklarálása.

Mély neurális hálózat futtatásáshoz szükséges függőségek beállítása az app/build.gradle fájlban:

dependencies {

implementation 'org.tensorflow:tensorflow-android:1.4.0'

}

Python-ban Keras-t használtam a hálózat elkészítésére viszont a Keras a TensorFlow könyvtárra épült. A TensorFlow-nak van hivatalos Java támogatása, így a modelleket könnyen lehet futtatni Android környezetben is.

Az elmentett mély neurális hálózat modell fájlt az app/src/main/assets/ -be kell elhelyezni. Később a nevével tudunk majd hivatkozni a modellre. Ebben az alkalmazásban a kamera képét adom a hálózat bemenetére így szükség van az AndroidManifest.xml-ben deklarálni az szükséges engedélyeket:


<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

A mély neurális hálózat futtatásához szükség van még néhány lépésre:

  • Modell fájl betöltése a memóriába
  • Modell be és kimenet dimenziójának meghatározása
  • Modell be és kimenet csomópont nevének meghatározása (Protobuf fájlból ovasható ki)
  • TensorFlow natív függvények betöltése

Ezeket a funkciókat ConvNet osztály valósítja meg:

public class ConvNet extends TensorFlowInferenceInterface {
public static final int INPUT_WIDTH = 64;
public static final int INPUT_HEIGHT = 64;
public static final int INPUT_DEEP = 3;
private static final String MODEL_FILE = "file:///android_asset/modell.pb";
private static final String INPUT_NODE = "conv2d_56_input_1";
private static final String OUTPUT_NODE = "output0";
private static final long[] INPUT_SIZE = {1,64,64,3};
private float[] OUTPUT_TENSOR = new float[2];

// TensorFlow natív függvények betöltése
static {
System.loadLibrary("tensorflow_inference");
}
// Konstruktor
public ConvNet(Context context) {
super(context.getAssets(), MODEL_FILE);
}
// Predikció futtatása
public float[] runInference(float[] pic) {
OUTPUT_TENSOR = new float[2];
// Hálózat bemenetére adatok betöltése
feed(ConvNet.INPUT_NODE, pic, INPUT_SIZE);

// Predikció futtatása
run(new String[]{ConvNet.OUTPUT_NODE}, true);

// A predikció eredményének elkérése a hálózattól
fetch(ConvNet.OUTPUT_NODE, OUTPUT_TENSOR);
// Eredmények kiírása konzolra
System.out.println("Result: " + OUTPUT_TENSOR[0] + " - " + OUTPUT_TENSOR[1]);
return OUTPUT_TENSOR;
}
}

Ahhoz, hogy a mély neurális hálózat eredményeit vizuálisan is tudjuk tesztelni, szükség van még néhány funkcióra az alkalmazásban:

  • Kamera kezelése (camera2 API)
  • Képek transzformálása 64×64-es méretre
  • Predikciók futtatása külön szálon

Eredmények

Az alkalmazás Xiaomi Redmi Note 3 SE-n futás közben látható az alábbi képeken:

Az alkalmazáshoz egy demó videó is elérhető:

Összefoglalás

Ahogyan a cikkből is látható mély neurális hálózatokat be lehet építeni Android alkalmazásba és új funkciókat lehet ezzel a felhasználóknak nyújtani. Van némi hátránya is, a telepítendő alkalmazás mérete erőteljesen függ a hálózat modelljének méretétől és a készüléket is erősen tudja terhelni a predikciók folytonos számolása.

Linkek

Konvolúciós neurális hálózatok: http://cs231n.github.io/convolutional-networks/

GTI autó adatbázis: https://www.gti.ssr.upm.es/data/Vehicle_database.html

Demó video: https://www.youtube.com/watch?v=2JxDT8c2Zg0

GitHub forrás: https://github.com/BME-SmartLab-Education/driver-assistant

Source: Deep Learning on Medium