Android 画像分類ガイド

MediaPipe 画像分類タスクを使用すると、画像の分類を実行できます。このタスクを使用して、トレーニング時に定義された一連のカテゴリの中から画像が何を表しているかを特定できます。以下では、Android アプリで画像分類器を使用する方法について説明します。この手順で説明するコードサンプルは GitHub で入手できます。

このタスクの実例は、ウェブデモで確認できます。このタスクの機能、モデル、構成オプションの詳細については、概要をご覧ください。

サンプルコード

MediaPipe Tasks のサンプルコードは、Android 用の画像分類アプリの単純な実装です。この例では、物理 Android デバイスのカメラを使用してオブジェクトを継続的に分類しています。また、デバイス ギャラリーの画像や動画を使用して、オブジェクトを静的に分類することもできます。

アプリは、独自の Android アプリの出発点として使用することも、既存のアプリを変更するときに参照することもできます。画像分類器のサンプルコードは GitHub でホストされています。

コードをダウンロードする

次の手順では、git コマンドライン ツールを使用してサンプルコードのローカルコピーを作成する方法を示します。

サンプルコードをダウンロードするには:

  1. 次のコマンドを使用して Git リポジトリのクローンを作成します。
    git clone https://github.com/google-ai-edge/mediapipe-samples
    
  2. 必要に応じて、スパース チェックアウトを使用するように Git インスタンスを構成します。これにより、画像分類器サンプルアプリのファイルのみになります。
    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/image_classification/android
    

ローカル バージョンのサンプルコードを作成したら、プロジェクトを Android Studio にインポートしてアプリを実行できます。手順については、Android のセットアップ ガイドをご覧ください。

主要コンポーネント

次のファイルには、この画像分類サンプル アプリケーションにとって重要なコードが含まれています。

セットアップ

このセクションでは、画像分類器を使用するための開発環境とコード プロジェクトを設定する主な手順について説明します。プラットフォーム バージョンの要件など、MediaPipe タスクを使用するための開発環境の設定に関する一般的な情報については、Android の設定ガイドをご覧ください。

依存関係

画像分類器は com.google.mediapipe:tasks-vision ライブラリを使用します。この依存関係を、Android アプリ開発プロジェクトの build.gradle ファイルに追加します。次のコードを使用して、必要な依存関係をインポートします。

dependencies {
    ...
    implementation 'com.google.mediapipe:tasks-vision:latest.release'
}

モデル

MediaPipe 画像分類タスクには、このタスクと互換性のあるトレーニング済みモデルが必要です。画像分類器で使用可能なトレーニング済みモデルの詳細については、タスクの概要のモデル セクションをご覧ください。

モデルを選択してダウンロードし、プロジェクト ディレクトリに保存します。

<dev-project-root>/src/main/assets

BaseOptions.Builder.setModelAssetPath() メソッドを使用して、モデルで使用するパスを指定します。このメソッドは、次のセクションのコードサンプルで参照されています。

画像分類器のサンプルコードでは、ImageClassifierHelper.kt ファイルでモデルが定義されています。

タスクを作成する

タスクを作成するには、createFromOptions 関数を使用します。createFromOptions 関数は、実行モード、表示名のロケール、結果の最大数、信頼度のしきい値、カテゴリの許可リストまたは拒否リストなどの構成オプションを受け入れます。構成オプションの詳細については、構成の概要をご覧ください。

画像分類タスクは、静止画像、動画ファイル、ライブ動画ストリームの 3 つの入力データ型をサポートしています。タスクの作成時に、入力データ型に対応する実行モードを指定する必要があります。入力データ型に対応するタブを選択して、タスクの作成方法と推論の実行方法を確認してください。

画像

ImageClassifierOptions options =
  ImageClassifierOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setRunningMode(RunningMode.IMAGE)
    .setMaxResults(5)
    .build();
imageClassifier = ImageClassifier.createFromOptions(context, options);
    

動画

ImageClassifierOptions options =
  ImageClassifierOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setRunningMode(RunningMode.VIDEO)
    .setMaxResults(5)
    .build();
imageClassifier = ImageClassifier.createFromOptions(context, options);
    

ライブ配信

ImageClassifierOptions options =
  ImageClassifierOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setRunningMode(RunningMode.LIVE_STREAM)
    .setMaxResults(5)
    .setResultListener((result, inputImage) -> {
         // Process the classification result here.
    })
    .setErrorListener((result, inputImage) -> {
         // Process the classification errors here.
    })
    .build()
imageClassifier = ImageClassifier.createFromOptions(context, options)
    

画像分類器のサンプルコード実装を使用すると、ユーザーは処理モードを切り替えることができます。この方法では、タスク作成のコードが複雑になるため、ユースケースには適さない場合があります。このコードは ImageClassifierHelper.kt ファイルの setupImageClassifier() 関数で確認できます。

構成オプション

このタスクには、Android アプリ用に次の構成オプションがあります。

オプション名 説明 値の範囲 デフォルト値
runningMode タスクの実行モードを設定します。モードには次の 3 つがあります。

IMAGE: 単一画像入力のモード。

VIDEO: 動画のデコードされたフレームのモード。

LIVE_STREAM: カメラなどの入力データのライブ ストリームのモード。このモードでは resultListener を呼び出して、結果を非同期で受け取るリスナーをセットアップする必要があります。
{IMAGE, VIDEO, LIVE_STREAM} IMAGE
displayNamesLocale タスクのモデルのメタデータに指定された表示名に使用するラベルの言語を設定します(使用可能な場合)。英語の場合、デフォルトは en です。TensorFlow Lite Metadata Writer API を使用して、カスタムモデルのメタデータにローカライズされたラベルを追加できます。 言語 / 地域コード en
maxResults 返される上位スコアの分類結果の最大数をオプションで設定します。0 未満の場合、利用可能なすべての結果が返されます。 正の数 -1
scoreThreshold モデル メタデータ(存在する場合)で指定された値をオーバーライドする予測スコアのしきい値を設定します。この値を下回る結果は拒否されます。 任意の浮動小数点数 未設定
categoryAllowlist 許可するカテゴリ名のオプション リストを設定します。空でない場合、このセットにカテゴリ名がない分類結果は除外されます。重複または不明なカテゴリ名は無視されます。 このオプションは categoryDenylist と相互に排他的であり、両方を使用するとエラーになります。 任意の文字列 未設定
categoryDenylist 許可されていないカテゴリ名のオプション リストを設定します。空でない場合、このセット内にカテゴリ名が含まれる分類結果は除外されます。重複または不明なカテゴリ名は無視されます。このオプションは categoryAllowlist と相互に排他的であり、両方を使用するとエラーになります。 任意の文字列 未設定
resultListener 画像分類器がライブ ストリーム モードのときに分類結果を非同期で受信するように結果リスナーを設定します。実行モードが LIVE_STREAM に設定されている場合にのみ使用できます なし 未設定
errorListener オプションのエラーリスナーを設定します。 なし 未設定

データの準備

画像分類器は、画像、動画ファイル、ライブ ストリーム動画に対応しています。このタスクは、サイズ変更、回転、値の正規化など、データ入力の前処理を処理します。

入力画像またはフレームを画像分類器に渡す前に、com.google.mediapipe.framework.image.MPImage オブジェクトに変換する必要があります。

画像

import com.google.mediapipe.framework.image.BitmapImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Load an image on the user’s device as a Bitmap object using BitmapFactory.

// Convert an Android’s Bitmap object to a MediaPipe’s Image object.
Image mpImage = new BitmapImageBuilder(bitmap).build();
    

動画

import com.google.mediapipe.framework.image.BitmapImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Load a video file on the user's device using MediaMetadataRetriever

// From the video’s metadata, load the METADATA_KEY_DURATION and
// METADATA_KEY_VIDEO_FRAME_COUNT value. You’ll need them
// to calculate the timestamp of each frame later.

// Loop through the video and load each frame as a Bitmap object.

// Convert the Android’s Bitmap object to a MediaPipe’s Image object.
Image mpImage = new BitmapImageBuilder(frame).build();
    

ライブ配信

import com.google.mediapipe.framework.image.MediaImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Create a CameraX’s ImageAnalysis to continuously receive frames 
// from the device’s camera. Configure it to output frames in RGBA_8888
// format to match with what is required by the model.

// For each Android’s ImageProxy object received from the ImageAnalysis, 
// extract the encapsulated Android’s Image object and convert it to 
// a MediaPipe’s Image object.
android.media.Image mediaImage = imageProxy.getImage()
Image mpImage = new MediaImageBuilder(mediaImage).build();
    

画像分類器のサンプルコードでは、データの準備は ImageClassifierHelper.kt ファイルで処理されます。

タスクを実行する

実行モードに対応する classify 関数を呼び出して、推論をトリガーできます。Image Classifier API は、入力画像またはフレーム内のオブジェクトの可能なカテゴリを返します。

画像

ImageClassifierResult classifierResult = imageClassifier.classify(image);
    

動画

// Calculate the timestamp in milliseconds of the current frame.
long frame_timestamp_ms = 1000 * video_duration * frame_index / frame_count;

// Run inference on the frame.
ImageClassifierResult classifierResult =
    imageClassifier.classifyForVideo(image, frameTimestampMs);
    

ライブ配信


// Run inference on the frame. The classifications results will be available 
// via the `resultListener` provided in the `ImageClassifierOptions` when 
// the image classifier was created.
imageClassifier.classifyAsync(image, frameTimestampMs);
    

次の点にご留意ください。

  • 動画モードまたはライブ ストリーム モードで実行する場合は、入力フレームのタイムスタンプを画像分類タスクに指定する必要があります。
  • 画像モードまたは動画モードで実行する場合、画像分類タスクは、入力画像またはフレームの処理が完了するまで現在のスレッドをブロックします。ユーザー インターフェースがブロックされないようにするには、バックグラウンド スレッドで処理を実行します。
  • ライブ ストリーム モードで実行している場合、画像分類タスクは現在のスレッドをブロックせず、すぐに戻ります。入力フレームの処理が完了するたびに、検出結果とともに結果リスナーを呼び出します。画像分類タスクが別のフレームの処理でビジー状態になっているときに classifyAsync 関数が呼び出されると、タスクは新しい入力フレームを無視します。

画像分類器のサンプルコードでは、ImageClassifierHelper.kt ファイルで classify 関数が定義されています。

結果の処理と表示

推論を実行すると、画像分類タスクは、入力画像またはフレーム内のオブジェクトの可能なカテゴリのリストを含む ImageClassifierResult オブジェクトを返します。

このタスクからの出力データの例を次に示します。

ImageClassifierResult:
 Classifications #0 (single classification head):
  head index: 0
  category #0:
   category name: "/m/01bwb9"
   display name: "Passer domesticus"
   score: 0.91406
   index: 671
  category #1:
   category name: "/m/01bwbt"
   display name: "Passer montanus"
   score: 0.00391
   index: 670

この結果は、以下に対して Bird Classifier を実行することで取得されました。

画像分類器のサンプルコードでは、ClassificationResultsAdapter.kt ファイルの ClassificationResultsAdapter クラスが結果を処理しています。

fun updateResults(imageClassifierResult: ImageClassifierResult? = null) {
    categories = MutableList(adapterSize) { null }
    if (imageClassifierResult != null) {
        val sortedCategories = imageClassifierResult.classificationResult()
            .classifications()[0].categories().sortedBy { it.index() }
        val min = kotlin.math.min(sortedCategories.size, categories.size)
        for (i in 0 until min) {
            categories[i] = sortedCategories[i]
        }
    }
}