1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AndroidでML Kitのベースモデルを使って画像にラベル付けする

Last updated at Posted at 2023-05-05
システム要件
Android API レベル 19 以降
言語
Java

ML Kitは、Googleの機械学習用のSDKだが、ここに書いてある通り、データをスマホのみで処理するSDKである。

ML Kit は、Google のオンデバイスの機械学習のエキスパティーズを Android と iOS のアプリに提供するモバイル SDK です。

以前は確かクラウド型のものもあったのだがと思ったら、移行ガイドに以下のような記述があり、ML Kitはオンデバイスのみになったと書かれている。

2020 年 6 月 3 日に、デバイス上の API とクラウドベースの API を区別しやすくするために、Firebase 向け ML Kit に一部変更を加えました。現在の API セットは、次の 2 つのプロダクトに分割されました。

と言うわけで、ML Kitを使えば、Googleに画像を送信することなく、ラベル付けができる。
ML Kitによる画像のラベル付けは、画像のラベル付けに従うだけなので、ここでまとめるまでも無いのだが、備忘録としてまとめた。
ML Kitには、ベースモデルとTensorFlow Liteによるカスタムモデルがあるのだが、ここでは、まずお手軽にベースモデルでのラベル付けを試した。

まず、ML Kitの依存関係をbuild.gradleに記述する。依存関係は、学習モデルをアプリにバンドルする場合とGoogle Play開発者サービスを使う場合とで異なる。後者の場合は、以下のように記述する。

app/build.gradle
dependencies {
...
    // Use this dependency to use the dynamically downloaded model in Google Play Services
    implementation 'com.google.android.gms:play-services-mlkit-image-labeling:16.0.8'
}

APIレベルを19以上に設定する。以下の例では24に設定している。後で使うのでviewBindingも設定している。

app/build.gradle
android {
...
    defaultConfig {
        minSdk 24
    }

    buildFeatures {
        viewBinding true
    }
}

モデルを自動ダウンロードするために、AndroidManifest.xmlに以下の記述を追加する。

AndroidManifest.xml
    <application>
        <meta-data
           android:name="com.google.mlkit.vision.DEPENDENCIES"
            android:value="ica" />
    </application>

もっとも簡単な例として、デバイス内の画像にラベル付けするコードを書いてみる。ML Kitでは、画像をInputImageオブジェクトにしなければならない。画像ファイルのURIからInputImageを作成するには以下のようにする。

InputImage image = InputImage.fromFilePath(this, uri);

ラベル付けするためには、画像ラベラーのインスタンスを作成する。

ImageLabeler labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS);

次に画像ラベラーインスタンスのprocessメソッドにInputImageを渡せば完了である。結果はOnSuccessListenerOnFailureListenerで処理する。
onCreateの中でregisterForActivityResultを使って画像ファイルをダイアローグで選択してファイルのURIを取得し、processメソッドに投げるコードは以下の通りである。結果はImageLabelのリストで返される。ImageLabelにはつけられたラベルのテキストとインデックス番号と確信値が格納されている。以下の例では、テキストと確信値をTextViewに表示している。

MainActivity.java
        binding = ActivityMainBinding.inflate(getLayoutInflater());

        mResultView = binding.resultView;

        ActivityResultLauncher<String> mGetContent = registerForActivityResult(
                new ActivityResultContracts.GetContent(),
                result -> {
                    if (result != null) {
                        try {
                            InputImage image = InputImage.fromFilePath(this, result);
                            labeler.process(image)
                                    .addOnSuccessListener(new OnSuccessListener<List<ImageLabel>>() {
                                        @Override
                                        public void onSuccess(List<ImageLabel> labels) {
                                            StringBuilder sb = new StringBuilder();
                                            if (labels.size() == 0)
                                                sb.append("no label found");
                                            else
                                                for (ImageLabel label : labels) {
                                                    String text = label.getText();
                                                    float confidence = label.getConfidence();
//                                                int index = label.getIndex();
                                                    sb.append(String.format("%s: %.3f%n", text, confidence));
                                                }
                                            mResultView.setText(sb.toString());
                                        }
                                    })
                                    .addOnFailureListener(new OnFailureListener() {
                                        @Override
                                        public void onFailure(@NonNull Exception e) {
                                            // Task failed with an exception
                                            mResultView.setText("failed to label");
                                        }
                                    });
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
        );

あとは、ボタンを押すと、このが起動するように設定すれば終了である。以下の例では、画像を選択したいので、URIには"image/*"を指定している。

MainActivity.java
        binding.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mGetContent.launch("image/*");
            }
        });

実行例を以下に示す。正しく 「猫」 と判定している。

ソース全体を以下に示す。ちなみに、アプリ情報によるとストレージ使用量は 22.06MB となった。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:text="Label Image"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:text="TextView"
        app:layout_constraintBottom_toTopOf="@+id/resultView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />

    <TextView
        android:id="@+id/resultView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.yoo6309.imagelabelerbymlkit;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;

import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.label.*;
import com.google.mlkit.vision.label.defaults.ImageLabelerOptions;
import com.yoo6309.imagelabelerbymlkit.databinding.ActivityMainBinding;

import java.io.IOException;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    TextView mResultView;

    private ActivityMainBinding binding;
    private ImageLabeler labeler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        mResultView = binding.resultView;
        labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS);
        ActivityResultLauncher<String> mGetContent = registerForActivityResult(
                new ActivityResultContracts.GetContent(),
                result -> {
                    if (result != null) {
                        binding.textView.setText(result.toString());
                        binding.imageView.setImageURI(result);
                        try {
                            InputImage image = InputImage.fromFilePath(this, result);
                            labeler.process(image)
                                    .addOnSuccessListener(new OnSuccessListener<List<ImageLabel>>() {
                                        @Override
                                        public void onSuccess(List<ImageLabel> labels) {
                                            StringBuilder sb = new StringBuilder();
                                            if (labels.size() == 0)
                                                sb.append("no label found");
                                            else
                                                for (ImageLabel label : labels) {
                                                    String text = label.getText();
                                                    float confidence = label.getConfidence();
//                                                int index = label.getIndex();
                                                    sb.append(String.format("%s: %.3f%n", text, confidence));
                                                }
                                            mResultView.setText(sb.toString());
                                        }
                                    })
                                    .addOnFailureListener(new OnFailureListener() {
                                        @Override
                                        public void onFailure(@NonNull Exception e) {
                                            // Task failed with an exception
                                            mResultView.setText("failed to label");
                                        }
                                    });
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
        );
        binding.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mGetContent.launch("image/*");
            }
        });
    }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?