Timers Tech Blog

グローバルな家族アプリFammを運営するTimers inc (タイマーズ) の公式Tech Blogです。弊社のエンジニアリングを支える記事を随時公開。エンジニア絶賛採用中!→ https://timers-inc.com/engineering

ML Kit (Auto ML Vision Edge)で写真から家族を検出する #firebase #mlkit #automl #android

こんにちは。世界では異常事態が続き、フルリモートとなった今、猫とペットボトルのキャップでサッカーをして遊ぶことだけが生きがいになりました。AndroidエンジニアのTsutouです。

子供やペットの写真を、手軽にアップロードしてもらいたい

弊社のアプリ、Fammは家族アルバムアプリなので、写真/動画アップロード機能の対象となる写真は子供、ペットの写真になります。

しかし、アップロード画面を表示した時、大抵は料理だったり、スクショだったり、風景だったり、データフォルダには色々な画像がありますね。

そこで、子供やペットの写真をもっと手軽にアップロードしてもらいたい、との思いからiOS版ではCore MLを使ってアップロードレコメンドが実現されています。

techblog.timers-inc.com

今回は、Androidでも実装する運びとなり、その際にML Kitを使用した際の事例になります。

MLKit (Auto ML Vision Edge) って?

ML Kitは、Google機械学習にまつわる様々な機能を、Firebaseを通してモバイルで実行できる様にするSDK群です。(まだBeta版です 2020/4/23現在)

顔認証や、画像ラベリング、オブジェクト認識、バーコードスキャン、翻訳と様々なものが用意されています。

firebase.google.com

今回使用するAuto ML Vision Edge(以下、AutoML)は、Firebaseのコンソール上で独自の画像分類モデルを作成でき、形式を選んで簡単にアプリ上で使用できるものです。

今回はAutoMLで、独自の画像分類モデルを作っていきたいと思います。

Firebase


ML Kitをはじめる

さて、コンソール上から早速ML Kitをはじめてみましょう。 (※オーナーしかはじめるを押せないケースを確認したので注意)

f:id:tsutoutakehara:20200417122146p:plain:w400

APIの一覧が出てきたら、Auto MLを選択します。

f:id:tsutoutakehara:20200417122936p:plain:w400

データセットを用意する

早速、分類したい画像のデータセットを用意し、追加していきます。(単一ラベル、複数ラベルを選びます)

f:id:tsutoutakehara:20200417123706p:plain:w400

以下のような形式を圧縮してzip形式で登録します。(登録済みのデータセットは、エクスポートできるCSVを元にGCSからインポートできます。一回、1GB以下でないとエラーになってしまうので注意)

f:id:tsutoutakehara:20200420174336p:plain:w200

ここでは、年齢で区分けされた顔認証用の画像を学習させたいと思います。

基本的な年齢幅、人種を網羅したデータセットを4000枚ほど + アジア人に特化したデータなども追加で1000枚程登録しました。 犬猫は、豊富にデータがあるので適当なものを4500〜5000枚ほど そして、検出してほしくない風景や、食べ物、お花の写真などを、まとめてothersとして認識させます。(ここはもっと細分化してあげてもいいかもしれません)

f:id:tsutoutakehara:20200427124637j:plain:w500

じっくりコトコト学習させる

オプションを指定し、学習時間を指定します。

f:id:tsutoutakehara:20200417191456p:plain:w500

じっくりコトコト学習させていきます。データセットにより、時間は変わりますが画像15000枚ほど、1コンピューティング時間で、4~5時間で焼きあがりました。 コストと応相談で、学習時間やレイテンシを調整し、モデルのスタイルをユースケースに合わせて選んでいきます。

レイテンシ

  • 下限レイテンシ - 軽量、高速、低精度
  • 汎用 - 中間
  • 高精度 - 大きめ、低速、高精度

firebase.google.com

焼き上がりを待つ

座して待ちましょう。出来上がるとFirebaseからメールが送られてきます。 完了すると、以下のようにテスト、使用ができるようになります。

f:id:tsutoutakehara:20200423150144p:plain:w500

適合率/再現率について

f:id:tsutoutakehara:20200421103732p:plain:w500

  • 適合率 : 高ければ高いほど、猫っぽいものが写っている画像を猫だと認識してしまうことが減る (以下のようなパターンが減る)

f:id:tsutoutakehara:20200423145758p:plain:w500

  • 再現率 : 高ければ高いほど、本当は猫なのに、猫以外(犬とか物体)と認識してしまうことが減る (以下のようなパターンが減る)

f:id:tsutoutakehara:20200423143944p:plain:w500

平たい理解ですが、トレードオフな関係です。ここの閾値もコンソールで調節できるので、どういった機能に使うのかに合わせて、適宜調節いただくのがいいと思います。

結果

中々の精度です!

f:id:tsutoutakehara:20200427124719p:plain:w500 f:id:tsutoutakehara:20200424104216p:plain:w500 f:id:tsutoutakehara:20200424104246p:plain:w500 f:id:tsutoutakehara:20200427124730p:plain:w500

アプリ


運用方法を決める(ローカルモデル/リモートモデル)

モデルの運用方法を決めます。

ローカル

  • メリット 
    • アプリをダウンロードするだけで使える
  • デメリット 
    • アプリサイズ増
    • モデルの更新にリリースが必要

リモート

  • メリット 
    • アプリサイズ変わらない 
    • リリースせずにモデルを更新可能
    • リモートコンフィグなどと組み合わせて、ABテストなどもコントロールできる
  • デメリット
    • モデルがダウンロードできるまで機能は使えない
    • 通信が行われる前提なので、コード量が増える

ローカル/リモート両刀(リモートの取得に失敗したらローカルを使用)

  • メリット 
    • アプリをダウンロードするだけで使える
    • リリースせずにモデルを更新可能
    • リモートコンフィグなどと組み合わせて、ABテストなどもコントロールできる
  • デメリット
    • アプリサイズ増
    • 通信が行われる前提なので、コード量が増える

依存関係の追加

難読化回避(ローカルモデル使用時のみ)

aaptOptions {
    noCompress "tflite"
}
implementation 'com.google.firebase:firebase-ml-vision:24.0.2'
implementation 'com.google.firebase:firebase-ml-vision-automl:18.0.4'

モデルの公開 or ダウンロード

ローカルのモデル

assetsディレクトリを用意し、コンソールからダウンロードしたモデルを配置します。

  • /assets/automl/dict.txt
  • /assets/automl/manifest.json
  • /assets/automl/model.tflite
val localModel = FirebaseAutoMLLocalModel
        .Builder()
        .setAssetFilePath("automl/manifest.json")
        .build()

リモートのモデル

コンソールから、モデルを公開し、モデル名をアプリ側で指定します。ダウンロードや、フラグの取得はTasks APIになっているので、非同期処理のハンドリングが必要になります。

developers.google.com

val remoteModel = FirebaseAutoMLRemoteModel
        .Builder(BuildConfig.AUTO_ML_MODEL_NAME)
        .build()
val conditions = FirebaseModelDownloadConditions.Builder().build()
val downloadTask = FirebaseModelManager
        .getInstance()
        .download(remoteModel, conditions)
        .addOnSuccessListener {
            //ダウンロード成功時にしたい処理 
        }
fun isModelDownloaded() = Tasks.await(
            FirebaseModelManager
                    .getInstance().isModelDownloaded(
                            remoteModel
                    )
)

画像を認識させる

設定したラベルを用意します。

const val CATS = "cats"
const val KIDS = "kids"
const val ADULT = "adults"
const val DOGS = "dogs"
const val OTHERS = "others"

モデルを用意し、オプションを設定して、画像のラベラーを用意します。今回、モデルは両刀で使っていますが、片方だけでの利用ももちろんOKです。(FirebaseVisionOnDeviceAutoMLImageLabelerOptionsでは、主に、信頼度が調整できます。デフォルトは50%)

val mlModel = if (isModelDownloaded()) {
    remoteModel
} else {
    localModel
}

val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(
        FirebaseVisionOnDeviceAutoMLImageLabelerOptions
                .Builder(mlModel)
                .setConfidenceThreshold(0.7f)
                .build()
)

そうしたら、画像から取得できるラベルのテキストを元に、子供、大人、犬、猫、その他を判別していきます。

suspend fun detectKidsOrPet(context: Context, uri: Uri): Boolean = withContext(Dispatchers.Default) {
    
    val targetBitmap = getTargetBitmap(context, uri)
    
    val image = try {
        FirebaseVisionImage.fromBitmap(targetBitmap)
    } catch (e: IOException) {
        Timber.e(":$e")
        return@withContext false
    }

    return@withContext try {
        val labels = synchronized(this) {
            Tasks.await(labeler.processImage(image))
        }
        labeler.close()
        labels.forEach {
            when (it.text) {
                ADULT, OTHERS -> return@withContext false
                CATS, KIDS, DOGS -> return@withContext true
            }
        }
        false
    } catch (e: ExecutionException) {
        Timber.e("$e")
        false
    } catch (e: InterruptedException) {
        Timber.e("$e")
        false
    }
}

今回は、多くの画像からパパパっと読み込んで欲しかったので、URIからBitmapに、更に出来るだけ縮小して読み込ませましたが、FirebaseVisionImageは、他にもUriからでも、色々な作成方法あるので、アプリによって画像の取り扱いは変えていくのがいいと思います。

fun getFirebaseVisionImage(bitmap: Bitmap): FirebaseVisionImage? = try {
    FirebaseVisionImage.fromBitmap(bitmap)
} catch (e: IOException) {
    Timber.e("$TAG:$e")
    null
}

これで、コンソールで出た精度そのまま、アプリ状の機能に機械学習を導入できます。

結果

犬猫子供と、大人やその他を区別してくれるようになりました。

f:id:tsutoutakehara:20200424141814g:plain

余談 : デフォルトの画像分類SDK

MLKitの画像分類SDKの方をバンドルすれば、高精度に犬猫、赤ちゃんは認識可能かつ、FoodやScreenshot、Paper なども出来合いのままで認識でき、対象からいち早く不要な画像を弾けるので、カスタムモデルと組み合わせてより精緻な画像分類ができると思いました。実際に、犬猫、赤ちゃんまではとても精度よく認識してくれました。

子供か、大人かを判定出来ない事や、アプリサイズの増加を懸念して、運用は断念しましたが、この機能がプロダクトに及ぼすインパクトが大きいのであれば、基本的な部分をデフォルトのものにお願いし、大事な部分の判定だけAuto MLに担ってもらう、ユースケースによっては、そんな両刀の運用もありなのかなと思いました。

まだBeta版ですし、オーバーかもしれませんがそんな運用も今後の進化によって可能となってくるかもしれません。

firebase.google.com

まとめ

最初は、機械学習と聞くと少し身構えてしまいましたが、高精度のモデルを作成し、ユーザーニーズの高い機能を、結果とても簡単にアプリに導入することができました。 Firebaseを通してモデルの更新をかけて育てていけるし、APIの柔軟性もとても高く、今後の進化がとても楽しみです。

スライド

こちらのイベントでお話しさせて頂く用のスライドです

potatotips.connpass.com

積極採用中!!

子育て家族アプリFammを運営するTimers inc.では、現在エンジニアを積極採用中!
急成長中のサービスの技術の話を少しでも聞いてみたい方、スタートアップで働きたい方など、是非お気軽にご連絡ください!
採用HP: http://timers-inc.com/engineerings

Timersでは各職種を積極採用中!

急成長スタートアップで、最高のものづくりをしよう。

募集の詳細をみる