Firebase ML Kit “Custom Model” Kullanarak Realtime Android Object Recognition (Nesne Tanıma) Uygulaması

Uğurcan Durak
Doğuş Technology
Published in
9 min readDec 12, 2019

Bu yazımda Firebase’in kendi Text Recognition, Face Detection, Barcode Scanning vb. gibi hazır API’ ları dışında; kendi eğittiği modeli ya da eğitilmiş başka bir modeli kullanmak isteyenlerin modellerini cloud ortamında barındırabilmeleri ve Mobil uygulamalarda kolaylıkla kullanabilmeleri için geliştirmiş olduğu bir Mobil SDK’ dan bahsedeceğim.

Ne Yapacağız?

Realtime olarak kameradan anlık frame alıcaz ve bu frame’ deki objeleri eğitilmiş bir model yardımıyla tanıyıp tanınan objenin ismini anlık olarak ekranda göstereceğiz. Yapacağımız uygulama bir Android uygulaması olacak ve kullanacağımız modeli Firebase ortamında tutacağız. Haydi başlayalım :)

Firebase Nedir?

Google tarafından 2014 yılında satın alınan ve içerisinde Realtime Database, File Storage, Authentication gibi özellikler barındıran ve bu özellikleri sunucuya ve sunucu taraflı kod yazmaya gerek kalmadan halleden bir platform.

ML Kit Nedir?

Yazının girişinde de biraz bahsettim aslında. ML Kit, Google’ ın Machine Learning bilgisi gerektirmeden Firebase içerisinde barındırdığı Text Recognition, Face Detection, Image Labeling gibi bir çok hazır API’ ları ve bunların yanında bizim kullanacağımız Custom Model özelliğini bir kaç satır kod ile mobil uygulamalarda kullanılmasını sağlayan bir Mobil SDK.

Firebase Proje Oluşturma

Tanımlarımızı da yaptığımıza göre Firebase’de oturum açıp proje oluşturarak başlayalım. Öncelikle Firebase’de oturum açabilmek için bir Google hesabınızın olması gerekir eğer bir Google hesabına sahip değilseniz bir hesap açın. Hesabınız zaten varsa Google hesabınıza giriş yapın ve aşağıdaki linke tıklayarak Firebase penceresini açın: https://firebase.google.com

1

Yukarıdaki ekran geldikten sonra “Get started” butonuna tıklayarak ilk projemizi oluşturalım.

Bu ekranda ise “Create a project” butonuna tıklayarak devam ediyoruz.

3

Daha sonra projemize bir isim verip “Continue” butonu tıklıyoruz ve projemizin oluşmasını bekliyoruz.

4

Firebase Custom Model Ekleme

Projemiz oluştu şimdi ise Custom Model’ i Firebase’e ekleyelim. Bunun için sol taraftaki menüden “Develop” sekmesinin altındaki “ML Kit” seçeneğine geliyoruz.

5

“Get started” butonuna tıkladıktan sonra yukarıdaki sekmeden “Custom” sekmesine tıklıyoruz.

6

Daha sonra “add custom model” butonuna tıklıyoruz ve karşımıza gelen ekranda modele vereceğimiz ismi giriyoruz. Daha sonra bu ismi Android uygulamamızda da kullanacağız.

Modelimize isim verdikten sonra karşımıza çıkan “browse” butonu ile bilgisayarımızda mevcut olan eğitilmiş modeli seçiyoruz. Bu aşamada kendi eğittiğiniz modeli kullanabilirsiniz ben 1000 adet obje tanıyan örnek bir model kullanacağım kullanmak isteyenler için link paylaşıyorum: https://drive.google.com/drive/u/1/folders/109zzH2glE31u31iUzTPBKh0_guRTT4sV

Android Projemizi Firebase’e Ekleme

Evet modelimizi Firebase’e ekledik şimdi ise Android Studio ile “Empty Activty” diyerek bir Android projesi oluşturalım ve oluşturduğumuz projeyi Firebase’e ekleyelim.

Android projesi oluşturduğunuzu varsayarak devam ediyor olacağım. Tekrar Firebase ekranında soldaki menüden “Project Overview” seçeneği ile ilk ekrana geri dönelim. Buradan Firebase’e ekleyeceğimiz uygulamanın türünü seçerek devam edeceğiz (Android, IOS ve Web). Biz Android projesi yapacağımız için Android ikonuna tıklıyoruz.

Daha sonra karşımıza yukarıdaki gibi bir ekran gelecek. Şimdi sırayla bu alanları dolduralım. Öncelikle “Android package name” ile başlayalım. Bunun için oluşturduğumuz Android projesinde “build.gradle (Module: app)” dosyasına açıyoruz ve “applicationId” yazan yerin sağ tarafındaki tırnak içindeki yazıyı kopyalıyoruz. Daha sonra Firebase’ de “Android package name” alanına yapıştırıyoruz. (Aşağıdaki resimde gösterilmiştir)

Bir alttaki alanda ise eklediğimiz uygulamayı isimlendiriyoruz. Bu alanı boş geçebilirsiniz fakat Firebase’e eklediğiniz uygulamaları ayırt edebilmeniz için size fayda sağlayacaktır.

Üçüncü alanı boş bırakabilirsiniz şimdilik bu alana ihtiyacımız yok. İlk iki alanı doldurduktan sonra “Register app” butonuna tıklıyoruz.

Daha sonra karşımıza yukarıdaki gibi ikinci aşama olan “donload config file” ekranı gelecek ve buradan “Download google-services.json” butonuna basarak bir “json” dosyası indireceğiz. Daha sonra dosyamızı Android projemize ekleyeceğiz ve böylelikle ikinci aşama da tamamlanmış olacak. Aşağıdaki resim ile bunu anlatıyor olacağım.

İndirdiğimiz dosyayı Android projemize eklemek için projemizi açıyoruz ve sol taraftaki menünün listelenme biçimini “Android” den “Project” konumuna getiriyoruz. Daha sonra indirdiğmiz “json” dosyasını kopyalayıp “app” klasörüne yapıştırıyoruz. Dosyamız yapıştırdıktan sonra yukarıdaki resimdeki gibi görünecektir.

Bu işlemi de tamamladıktan sonra Firebase ekranında “next” butonuna tıklayarak üçüncü aşamaya geçelim.

Bu aşamada ise Android projemize istenen “dependency” leri ekleyeceğiz. Android projemizi açalım ve son olarak değiştirdiğimiz listeleme biçimini tekrar “Android” konumuna getirelim ve menüden “build.gradle (Project)” dosyasını seçelim.

Daha sonra “dependencies” yazan bloğun içerisine Firebase’deki kodu yapıştıralım.

Son olarak ise “build.gradle (Module: app)” dosyasına gelerek “dependencies” parantezi kapandıktan hemen sonra “apply plugin” kodumuzu ekliyoruz. Gerekli eklemeleri yaptıktan sonra sağ üst köşede “Sync Now” butonu belirecek butona basarak yaptığımız eklemeleri senkronize ediyoruz.

İşlemlerimiz bitti tekrar Firebase ekranında “next” diyerek bitiriyoruz. Uygulamamızı artık Firebase’e eklemiş olduk ve böylelikle Firebase ile olan işlemlerimizi bitirdik.

Android Uygulamamızı Oluşturalım

Senaryomuz şu şekilde olacak; uygulamamızın açıldığı ilk ekranda Firebase üzerinde bulunan modelimizi telefonun hafızasına indireceğiz ve indirme başarılı olduğu durumda ikinci ekrana geçeceğiz. İkinci ekranda kamerayı başlatıp tanıma işlemini gerçekleştireceğiz ve tanınan objeleri “TextView” ile ekrana anlık olarak yazdıracağız. Haydi başlayalım :)

Gerekli “dependecies” lerin Eklenmesi

İlk olarak uygulamamızda kullanacağımız kütüphaneleri ekleyerek başlayalım. Projemize Firebase ve kamera olmak üzere iki ayrı kütüphane ekleyeceğiz. Bunun için “build.gradle (Module: app)” dosyamızı açıyoruz ve aşağıdaki kütüphaneleri ekliyoruz.

Firebase için

Kamera için

Dependencies’ leri ekledikten sonra “Sync Now” butonuna tıklıyoruz. Şimdi uygulama açıldığında ilk olarak görünecek olan ekranı kodlayarak devam edelim.

İlk Ekranın Kodlanması (MainActivity)

Bu Activity’ de modelimizi indireceğiz ve indirmenin başarılı olması durumunda diğer Activity’ ye geçiş yapacağız. Tasarıma herhangi bir component eklemiyorum.

activity_main.xml

Şimdi MainActivity’ imizi kodlamaya geçelim. İlk olarak “HOSTED_MODEL_NAME” ile Firebase’de Custom Model eklerken kullandığımız ismi tutacak bir değişken oluşturuyoruz. Daha sonra “onCreate()” metodunun içerisine az sonra kodlayacağımız “initFirebase()” fonksiyonumuzu ekliyoruz.

MainActivity.kt

“initFirebase()” fonksiyonumuzu oluşturalım ve içerisini kodlayalım. İlk olarak “FirebaseModelDownloadConditions” class’ ından bir conditons objesi oluşturuyoruz. Burada download için “requireWifi”, “requireCharging” ve “requireDeviceIdle” koşul belirtebilirsiniz. Ben herhangi bir şart belirtmeyeceğim. Daha sonra “FirebaseCustomRemoteModel” ile indireceğimiz modelin ismini belirtiyoruz.

Daha sonra “FirebaseModelManager” ile “conditions” ve “remoteModel” parametlerini geçerek modelimizi indiriyoruz. Başarılı ve başarısız olması durumundaki senaryoları ekliyoruz.

MainActivity.kt

Son olarak Firebase’ den gelen hataları yakalamak için “initFirebase()” fonksiyonundaki kodu “try-catch” içine alalım ve bitirelim. Son hali aşağıdaki gibidir:

MainActivity.kt

İkinci Ekranın Kodlanması (RecognitionActivity)

Evet MainActivity’ de modelimizi indirdik şimdi ikinci ekranda kamera ile realtime olarak tanıma yapalım. Bunun için yeni bir “EmptyActivity” oluşturalım ve kodlamaya geçelim.

Kamera için aşağıdaki xml kodunu ekliyoruz. Kameranın tüm ekranı kaplamasını istediğim için “width” ve “height” değerlerini “match_parent” yapıyorum.

Tasarımda layout olarak “FrameLayout” kullanıyorum ve kamerayı bu activity’ de kullanacağım için kamerayı da bu layout’ a ekliyorum. Modelden dönen sonuçlardan üç tanesini göstereceğim için ayrıca üç adet TextView ekliyorum.

Quantization Ve Floating Model

Kodlamaya geçmeden önce önemli bir bilgi vermek istedim. Kullanılan model formatı “quantization” ve “floating” olarak iki farklı türde olabilir ve bu tür farkından dolayı input-output formatları değişiklik göstereceğinden biz bu ayrıma göre farklı işlemler yapmak zorundayız. Burada bir “if “ bloğu ile kodu ikiye ayırarak işlem yapacağız. Yani modelin “quantization” olması durumunda farklı işlem “floating” olması durumunda farklı işlem şeklinde kodlayacağız. (Benim kullanacağım model formatı “quantization” )

Değişkenlerin Oluşturulması

İlk olarak kod içerisinde kullanacağımız global değişkenlerimizi tanımlayalım.

Model formatına göre işlem yapcağım için “isQuant” adında bir boolean değişken oluşturuyorum ve benim kullanacağım model “quantization” olduğundan dolayı default değerini “true” veriyorum.

Modelin “quantization” ve “floating” olması durumda işlem yapacağımız default değerler.

Burada modelin tanıdığı objeleri string olarak tutan txt uzantılı dosyayı Android projemize ekleyeceğiz. (Yukarıda model için vermiş olduğum linkte “labels” dosyası mevcuttur.)

Daha sonra global bir değişkene dosyamızın ismini initiliaze ediyoruz.

Kameradan anlık olarak alıp modele göndereceğimiz resmi uygun boyutlara dönüştürmek için gerekli default değerleri belirliyoruz.

Gösterilecek olan result sayısı:

Android projemize eklediğimiz “labels” dosyamız ile tanınan objeleri kıyaslayıp sonuç oluşturmak için “sortedLabels” objesi oluşturuyoruz.

“Labels” dosyamızdaki isimleri tutacağımız bir list oluşturuyoruz.

Son olarak recognize işlemi için “FirebaseModelInterpreter” ve input-output formatını belirlemek için “FirebaseModelInputOutputOptions” objelerini oluşturuyoruz.

onCreate()

Şimdi “onCreate()” metodumuzun içerisine kamerayı ve recognize için kullanacağımız değerleri “initFirebase()” fonksiyonu ile set edelim.

Öncelik olarak “initFirebase()” fonksiyonumuzu çağırıyoruz. Ardından kamerayı set edip “classifyFrame()” fonksiyonumuza anlık olarak frame gönderip tanıma işlemini gerçekleştiriyoruz. Daha sonra gelen sonuçları textView’ larımıza yazdırıyoruz.

initFirebase()

Sırasıyla “inirFirebase()” fonksiyonumuzu yazalım. Öncelikle “loadLabelList()” fonksiyonu ile projemize eklediğimiz label dosyasındaki isimleri labelList’ imize atıyoruz.

loadLabelList()

Daha sonra model ismimizi belirleyip “modelOptions” oluşturuyoruz ve “interpreter” mızı initialize ediyoruz.

Sonrasında data options için input-outputlarımızı oluşturuyoruz ve ayrıca “if” bloğu ile modelimizin formatına göre bir data type belirliyoruz. Burası önemli bir kısım model formatınız “quantization” ise data type “BYTE” “floating” ise “FLOAT32” olması gerekiyor.

Daha sonra “dataOptions” objemizi initialize ediyoruz.

Son olarak “try-catch” içerisine alıyoruz ve “initFirebase()” fonksiyonumuzu tamamlıyoruz.

classifyFrame()

Şimdi asıl işlemlerin olduğu fonksiyona geldik. Burada anlık olarak frame alıcaz ve frame’ i işleyip recognize için modele göndericez. Öncelikle aldığımız frame’ i “getVisionImageFromeFrame“ fonksiyonu ile FirebaseVisionImage‘ e dönüştürelim ve “bitmap” olarak başka bir değişkene alalım.

getVisionImageFromFrame()

“bitmap” olarak aldıktan sonra “interpreter” mızın null olup olmadığını kontrol edelim ve null gelmiş ise null için initialize edelim.

Daha sonra “bitmap” imizi “convertBitmapToByteBuffer” fonksiyonumuz ile ByteBuffer’ a çevirelim ve recognize için modele göndereceğimiz input’ u oluşturalım.

convertBitmapToByteBuffer()

İlk olarak modelin “quantization” ya da “floating” olması durumunu kontrol edip ona göre önceden belirlemiş olduğum değeri “value” değişkenine alıyorum.

Daha sonra belirlediğimiz değer ile dönüştürme işlemlerine devam ediyoruz.

Burada tekrar modelin formatına göre işlemleri belirleyip dönüştürmeyi tamamlıyoruz ve “ByteBuffer” değerimizi return ediyoruz.

“convertBitmapToByteBuffer()” fonksiyonumuzun son hali:

Recognition İşlemi

Evet input’ umuzu da oluşturduğumuza göre artık recognition işlemine girişelim. Oluşturduğumuz “classifyFrame()” fonksiyonunu “onCreate()” metodunda çağırmıştık. Dönen sonucu yine “onCreate()” metodunda textView’ lar ile ekrana basmamız gerekir. Şimdi recognition işlemimizi yapıcaz ve sonucu “classifyFrame()” fonksiyonunun return’ ü ile “onCreate()” metodumuza göndereceğiz.

Daha öncesinden tanıma işlemi için “interpreter” nesnesi oluşturmuştuk ve “initFirebase()” fonksiyonunda initialize etmiştik. Şimdi “input” değerimiz ile daha önceden input ve output formatlarımızı initialize ettiğimiz “dataOptions” nesnelerimizi “interpreter” ın “run” fonksyonuna parametre olarak geçiyoruz. Beklenmedik bir durum oluştuğunda “addOnFailureListener” ile hatamızı yakalıyoruz hata yok ise “continueWith” diyerek işlemlerimizi gerçekleştiriyoruz.

Burada tanıma işlemini gerçekleştirmiş olduk. Modelin formatını kontrol ederek sonucumuzun türünü belirliyoruz. Eğer “quantization” ise sonucumuzu “ByteArray” olarak , “floating” ise “FloatArray” olarak “labelProbArray” değişkenimize alıyoruz. Daha sonra gelen sonucu “labels” dosyamız ile eşleştirip anlamlı hale getirmek için “getTopLabels()” fonksiyonu oluşturuyoruz ve “labelProbArray” değişkenimizi paremetre olarak geçiyoruz. “getTopLabels()” fonksiyonumuz da model formatına göre ayrı işlem yapacağı için “FloatArray” paramtersi alan ve “ByteArray” parametresi alan iki farklı türde oluşturuyoruz. (Not: Fonksiyonlar arasında küçük bir işlem farkı olacağı için tek fonksiyon haline getirilebilir.)

getTopLabels() FloatArray

Şimdi burada “FloatArray” olarak aldığımız sonucu “labels” dosyamız ile eşleştirip anlamlı hale getircez ve tekrar “List<String>” olarak return edeceğiz.

En fazla üç sonuç göstermek istediğimiz için ona göre işlem yapıyoruz ve sonuçları “sortedLabels” içine ekliyoruz.

Daha sonra “String” türünde birde “ArrayList” oluşturup sonuçları “key” ve “value” olarak yani tanınan objenin ismi “:” tanıma olasılığı şeklinde listeye ekliyoruz ve return ediyoruz.

Son hali aşağıdaki gibi olacaktır :

getTopLabels() ByteArray

Modelden dönen sonucu “ByteArray” olarak aldığımız için “labelProbArray” i “0xff.toByte()) / 255.0f” ile “and” işlemine sokuyoruz. “FloatArray” den farklı olan tek kısım burası. Diğer işlemler yine “getTopLabels FloatArray” ile aynı mantıkta ilerliyor.

Son hali :

onCreate()

Artık sonucumuzu “getTopLabels()” ile anlamlandırdık ve anlamlanmış sonucu “classifyFrame()” fonksiyonuna, oradan da “onCreate()” e return ettik. Şimdi sonuçları alıp “textView” lara yazdıralım.

SONUÇ

Uygulamanın ekran çıktısı:

“Custom Model” ile ilgili daha fazla bilgiye ulaşmak için Firebase’ in Custom Model dökümanından yararlanabilirsiniz: https://firebase.google.com/docs/ml-kit/use-custom-models

Zaman ayırıp okuduğunuz için teşekkürler. Elimden geldiğince açıklamaya çalıştım, umarım faydalı bir yazı olmuştur. Projenin kaynak koduna bu linkten ulaşabilirsiniz: https://github.com/ugurcandrk/RealtimeObjectRecognition

Konu ile ilgi sorunuz olduğunda mail yoluyla ulaşabilirsiniz: ugurcandurakk@gmail.com

--

--