Android でカスタム分類モデルを使用してオブジェクトを検出、追跡、分類する

ML Kit を使用すると、連続する動画フレーム内のオブジェクトを検出して追跡できます。

ML Kit に画像を渡すと、画像内で最大 5 つのオブジェクトが検出されます。 画像内の各オブジェクトの位置を確認できます対象物の検知時に 各オブジェクトには、そのオブジェクトを追跡するために使用できる 同じです

カスタム画像分類モデルを使用すると、 検出されます。詳しくは、ML Kit を使用したカスタムモデルをご覧ください。 モデルの互換性要件に関するガイダンス、事前トレーニング済みモデルの入手先、 独自のモデルをトレーニングする方法を 見ていきます

カスタムモデルを統合するには、2 つの方法があります。モデルは、次の方法でバンドルできます。 アプリのアセット フォルダに配置するか、動的に 使用できます。次の表は、2 つのオプションを比較したものです。

バンドルされたモデル ホストされているモデル
このモデルはアプリの APK の一部であり、サイズが増大します。 モデルは APK の一部ではありません。アップロードすることでホストされます。 Firebase ML
Android デバイスがオフラインの場合でも、モデルをすぐに利用できます。 モデルはオンデマンドでダウンロードされる
Firebase プロジェクトは不要 Firebase プロジェクトが必要
モデルを更新するには、アプリを再公開する必要があります アプリを再公開せずにモデルの更新を push する
A/B Testing が組み込まれていない Firebase Remote Config で簡単に A/B テストを実施

試してみる

始める前に

  1. プロジェクト レベルの build.gradle ファイルに、次の内容を含めます。 Google の Maven リポジトリを buildscriptallprojects セクション。

  2. ML Kit Android ライブラリの依存関係をモジュールの アプリレベルの Gradle ファイル(通常は app/build.gradle

    モデルをアプリにバンドルするには:

    dependencies {
      // ...
      // Object detection & tracking feature with custom bundled model
      implementation 'com.google.mlkit:object-detection-custom:17.0.2'
    }
    

    Firebase からモデルを動的にダウンロードする場合は、linkFirebase を追加します。 :

    dependencies {
      // ...
      // Object detection & tracking feature with model downloaded
      // from firebase
      implementation 'com.google.mlkit:object-detection-custom:17.0.2'
      implementation 'com.google.mlkit:linkfirebase:17.0.0'
    }
    
  3. モデルをダウンロードする場合は、 Android プロジェクトに Firebase を追加する まだ実施していない場合は 追加してくださいモデルをバンドルする場合は不要です。

1. モデルを読み込む

ローカルモデルソースを構成する

モデルをアプリにバンドルするには:

  1. モデルファイル(通常は末尾が .tflite または .lite)をアプリの assets/ フォルダです。(先に事前にフォルダを作成し、 app/ フォルダを右クリックして 新規 >フォルダ >Assets フォルダを参照してください)。

  2. 次に、アプリの build.gradle ファイルに以下を追加して、 Gradle は、アプリのビルド時にモデルファイルを圧縮しません。

    android {
        // ...
        aaptOptions {
            noCompress "tflite"
            // or noCompress "lite"
        }
    }
    

    モデルファイルはアプリ パッケージに含まれ、ML Kit で使用可能 未加工アセットとして扱われます

  3. モデルファイルのパスを指定して、LocalModel オブジェクトを作成します。

    Kotlin

    val localModel = LocalModel.Builder()
            .setAssetFilePath("model.tflite")
            // or .setAbsoluteFilePath(absolute file path to model file)
            // or .setUri(URI to model file)
            .build()

    Java

    LocalModel localModel =
        new LocalModel.Builder()
            .setAssetFilePath("model.tflite")
            // or .setAbsoluteFilePath(absolute file path to model file)
            // or .setUri(URI to model file)
            .build();

Firebase でホストされているモデルソースを構成する

リモートでホストされるモデルを使用するには、CustomRemoteModel オブジェクトを作成します。 FirebaseModelSource: モデルに割り当てた名前を指定します が公開されました。

Kotlin

// Specify the name you assigned in the Firebase console.
val remoteModel =
    CustomRemoteModel
        .Builder(FirebaseModelSource.Builder("your_model_name").build())
        .build()

Java

// Specify the name you assigned in the Firebase console.
CustomRemoteModel remoteModel =
    new CustomRemoteModel
        .Builder(new FirebaseModelSource.Builder("your_model_name").build())
        .build();

次に、実行する条件を指定してモデルのダウンロード タスクを開始します。 ダウンロードを許可する対象のモデルがデバイスに搭載されていない場合や、 利用可能な場合、タスクは非同期でモデルの 構築する方法を紹介します。

Kotlin

val downloadConditions = DownloadConditions.Builder()
    .requireWifi()
    .build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
    .addOnSuccessListener {
        // Success.
    }

Java

DownloadConditions downloadConditions = new DownloadConditions.Builder()
        .requireWifi()
        .build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(@NonNull Task task) {
                // Success.
            }
        });

多くのアプリは初期化コードでダウンロード タスクを開始しますが、 モデルを使用する必要がある前であれば、いつでも実行できます。

2. オブジェクト検出を構成する

モデルソースを設定したら、モデルのオブジェクト検出機能を CustomObjectDetectorOptions オブジェクトを使ってみましょう。[ 次の設定を行います。

オブジェクト検出の設定
検出モード STREAM_MODE(デフォルト)|SINGLE_IMAGE_MODE

STREAM_MODE(デフォルト)では、オブジェクト検出が実行されます。 レイテンシは短くなりますが、不完全な結果( 未指定の境界ボックスやカテゴリラベルなど)を最初の数行に配置 検出機能の呼び出し。また、STREAM_MODEには、 検出機能によってオブジェクトにトラッキング ID が割り当てられます。この ID を使用して、 フレーム間でオブジェクトを追跡できます。このモードは または低レイテンシが重要な場合(たとえばデータの処理や リアルタイムで分析できます

SINGLE_IMAGE_MODE では、オブジェクト検出によって以下が返されます。 オブジェクトの境界ボックスが決定した後の結果です。もし 分類を有効にすると、境界の後に結果が返されます。 ボックスとカテゴリラベルの両方を使用できます。その結果 レイテンシが高くなる可能性があります。また、 SINGLE_IMAGE_MODE、トラッキング ID は割り当てられていません。使用 このモードは、レイテンシが重要ではなく、 部分的な結果しか得られません。

複数のオブジェクトを検出して追跡する false(デフォルト)|true

最大 5 つのオブジェクトを検出して追跡するか、最も大きい 目立たせることができます(デフォルト)。

オブジェクトを分類する false(デフォルト)|true

検出されたオブジェクトを、指定された カスタム分類器モデルですカスタム分類を使用するには これを true に設定する必要があります。

分類信頼度のしきい値

検出されたラベルの最小信頼スコア。設定しない場合、 モデルのメタデータで指定された分類器のしきい値が使用されます。 モデルにメタデータが含まれていないか、 指定しない場合、デフォルトのしきい値は 0.0 になります。 分析できます

オブジェクトあたりの最大ラベル数

検出機能によって適用される、オブジェクトあたりのラベルの最大数 戻ります。設定しない場合、デフォルト値の 10 が使用されます。

Object Detection and Tracking API は、この 2 つの主な用途のために最適化されています。 ケース:

  • カメラで最も目立つオブジェクトをライブ検出してトラッキング ビューファインダーです。
  • 静止画像からの複数のオブジェクトの検出。

これらのユースケースに対して、ローカルにバンドルされたモデルを使用して API を構成するには:

Kotlin

// Live detection and tracking
val customObjectDetectorOptions =
        CustomObjectDetectorOptions.Builder(localModel)
        .setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE)
        .enableClassification()
        .setClassificationConfidenceThreshold(0.5f)
        .setMaxPerObjectLabelCount(3)
        .build()

// Multiple object detection in static images
val customObjectDetectorOptions =
        CustomObjectDetectorOptions.Builder(localModel)
        .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE)
        .enableMultipleObjects()
        .enableClassification()
        .setClassificationConfidenceThreshold(0.5f)
        .setMaxPerObjectLabelCount(3)
        .build()

val objectDetector =
        ObjectDetection.getClient(customObjectDetectorOptions)

Java

// Live detection and tracking
CustomObjectDetectorOptions customObjectDetectorOptions =
        new CustomObjectDetectorOptions.Builder(localModel)
                .setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE)
                .enableClassification()
                .setClassificationConfidenceThreshold(0.5f)
                .setMaxPerObjectLabelCount(3)
                .build();

// Multiple object detection in static images
CustomObjectDetectorOptions customObjectDetectorOptions =
        new CustomObjectDetectorOptions.Builder(localModel)
                .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE)
                .enableMultipleObjects()
                .enableClassification()
                .setClassificationConfidenceThreshold(0.5f)
                .setMaxPerObjectLabelCount(3)
                .build();

ObjectDetector objectDetector =
    ObjectDetection.getClient(customObjectDetectorOptions);

リモートでホストされるモデルがある場合は、 ダウンロードされます。モデルのダウンロードのステータスを確認できます モデル マネージャーの isModelDownloaded() メソッドを使用して、タスクを実行できます。

確認が必要なのは検出機能を実行する前にのみですが、 リモートでホストされているモデルとローカルにバンドルされたモデルの両方がある場合は、 イメージ検出機能をインスタンス化する際にこのチェックを実行するには、次のコマンドを実行します。 ダウンロードされている場合はリモートモデルから、 必要があります。

Kotlin

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded ->
    val optionsBuilder =
        if (isDownloaded) {
            CustomObjectDetectorOptions.Builder(remoteModel)
        } else {
            CustomObjectDetectorOptions.Builder(localModel)
        }
    val customObjectDetectorOptions = optionsBuilder
            .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE)
            .enableClassification()
            .setClassificationConfidenceThreshold(0.5f)
            .setMaxPerObjectLabelCount(3)
            .build()
    val objectDetector =
        ObjectDetection.getClient(customObjectDetectorOptions)
}

Java

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener(new OnSuccessListener() {
        @Override
        public void onSuccess(Boolean isDownloaded) {
            CustomObjectDetectorOptions.Builder optionsBuilder;
            if (isDownloaded) {
                optionsBuilder = new CustomObjectDetectorOptions.Builder(remoteModel);
            } else {
                optionsBuilder = new CustomObjectDetectorOptions.Builder(localModel);
            }
            CustomObjectDetectorOptions customObjectDetectorOptions = optionsBuilder
                .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE)
                .enableClassification()
                .setClassificationConfidenceThreshold(0.5f)
                .setMaxPerObjectLabelCount(3)
                .build();
            ObjectDetector objectDetector =
                ObjectDetection.getClient(customObjectDetectorOptions);
        }
});

リモートでホストされるモデルのみがある場合は、モデル関連 機能(たとえば UI の一部をグレー表示または非表示にする)を モデルがダウンロードされたことを確認しますそのためには、リスナーをアタッチして これをモデル マネージャーの download() メソッドに追加します。

Kotlin

RemoteModelManager.getInstance().download(remoteModel, conditions)
    .addOnSuccessListener {
        // Download complete. Depending on your app, you could enable the ML
        // feature, or switch from the local model to the remote model, etc.
    }

Java

RemoteModelManager.getInstance().download(remoteModel, conditions)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(Void v) {
              // Download complete. Depending on your app, you could enable
              // the ML feature, or switch from the local model to the remote
              // model, etc.
            }
        });

3. 入力画像を準備する

画像から InputImage オブジェクトを作成します。 オブジェクト検出は、Bitmap、NV21 ByteBuffer、または YUV_420_888 media.Image。これらのソースから InputImage を構築すると、次のようになります。 これらのいずれかに直接アクセスできる場合はおすすめします。独自の InputImageの場合、Google が内部で変換を処理します。 効率が低下する可能性があります

InputImage を作成できます。 異なるソースからのオブジェクトについて、以下で説明します。

media.Image の使用

InputImage を作成するには: media.Image オブジェクトからオブジェクトをキャプチャします。たとえば、 渡すには、media.Image オブジェクトと画像の InputImage.fromMediaImage() に変更します。

「 <ph type="x-smartling-placeholder"></ph> CameraX ライブラリ、OnImageCapturedListenerImageAnalysis.Analyzer クラスが回転値を計算する できます。

Kotlin

private class YourImageAnalyzer : ImageAnalysis.Analyzer {

    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
}

Java

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        Image mediaImage = imageProxy.getImage();
        if (mediaImage != null) {
          InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
          // Pass image to an ML Kit Vision API
          // ...
        }
    }
}

画像の回転角度を取得するカメラ ライブラリを使用しない場合は、 デバイスの回転角度とカメラの向きから計算できます。 次の動作を行います。

Kotlin

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 0)
    ORIENTATIONS.append(Surface.ROTATION_90, 90)
    ORIENTATIONS.append(Surface.ROTATION_180, 180)
    ORIENTATIONS.append(Surface.ROTATION_270, 270)
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Throws(CameraAccessException::class)
private fun getRotationCompensation(cameraId: String, activity: Activity, isFrontFacing: Boolean): Int {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    val deviceRotation = activity.windowManager.defaultDisplay.rotation
    var rotationCompensation = ORIENTATIONS.get(deviceRotation)

    // Get the device's sensor orientation.
    val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360
    }
    return rotationCompensation
}

Java

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 0);
    ORIENTATIONS.append(Surface.ROTATION_90, 90);
    ORIENTATIONS.append(Surface.ROTATION_180, 180);
    ORIENTATIONS.append(Surface.ROTATION_270, 270);
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private int getRotationCompensation(String cameraId, Activity activity, boolean isFrontFacing)
        throws CameraAccessException {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    int rotationCompensation = ORIENTATIONS.get(deviceRotation);

    // Get the device's sensor orientation.
    CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360;
    }
    return rotationCompensation;
}

次に、media.Image オブジェクトと 回転角度の値を InputImage.fromMediaImage() に設定する:

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

ファイル URI の使用

InputImage を作成するには: 渡すことにより、アプリのコンテキストとファイルの URI を InputImage.fromFilePath()。これは、 ACTION_GET_CONTENT インテントを使用してユーザーに選択を求める ギャラリーアプリから画像を作成できます

Kotlin

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Java

InputImage image;
try {
    image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}

ByteBuffer または ByteArray の使用

InputImage を作成するには: 作成するには、まず画像を計算してByteBufferByteArray 前述の media.Image 入力に対する回転角度。 次に、バッファまたは配列を含む InputImage オブジェクトを、画像の 高さ、幅、カラー エンコード形式、回転角度:

Kotlin

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)
// Or:
val image = InputImage.fromByteArray(
        byteArray,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);
// Or:
InputImage image = InputImage.fromByteArray(
        byteArray,
        /* image width */480,
        /* image height */360,
        rotation,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

Bitmap の使用

InputImage を作成するには: Bitmap オブジェクトから呼び出す場合は、次のように宣言します。

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

画像は、Bitmap オブジェクトと回転角度で表されます。

4. オブジェクト検出を実行する

Kotlin

objectDetector
    .process(image)
    .addOnFailureListener(e -> {...})
    .addOnSuccessListener(results -> {
        for (detectedObject in results) {
          // ...
        }
    });

Java

objectDetector
    .process(image)
    .addOnFailureListener(e -> {...})
    .addOnSuccessListener(results -> {
        for (DetectedObject detectedObject : results) {
          // ...
        }
    });
<ph type="x-smartling-placeholder">

5. ラベル付きオブジェクトに関する情報を取得する

process() の呼び出しが成功すると、DetectedObject のリストが次に渡されます。 成功リスナー。

DetectedObject には次のプロパティが含まれています。

境界ボックス オブジェクトの位置を示す Rect。 説明します。
トラッキング ID 画像全体でオブジェクトを識別する整数。null イン SINGLE_IMAGE_MODE。
ラベル
ラベルの説明 ラベルのテキストの説明。TensorFlow Data Validation の Lite モデルのメタデータには、ラベルの説明が含まれています。
ラベル インデックス 分類器です。
ラベルの信頼度 オブジェクト分類の信頼値。

Kotlin

// The list of detected objects contains one item if multiple
// object detection wasn't enabled.
for (detectedObject in results) {
    val boundingBox = detectedObject.boundingBox
    val trackingId = detectedObject.trackingId
    for (label in detectedObject.labels) {
      val text = label.text
      val index = label.index
      val confidence = label.confidence
    }
}

Java

// The list of detected objects contains one item if multiple
// object detection wasn't enabled.
for (DetectedObject detectedObject : results) {
  Rect boundingBox = detectedObject.getBoundingBox();
  Integer trackingId = detectedObject.getTrackingId();
  for (Label label : detectedObject.getLabels()) {
    String text = label.getText();
    int index = label.getIndex();
    float confidence = label.getConfidence();
  }
}

優れたユーザー エクスペリエンスの確保

最適なユーザー エクスペリエンスを実現するには、アプリで次のガイドラインを遵守してください。

  • オブジェクトの検出が成功するかどうかは、オブジェクトの視覚的な複雑さによって決まります。イン 検出するには、対象物の視覚的な特徴の数が少ない場合、 大きな部分を占めるようにしますユーザーに 検出したい種類のオブジェクトに適した入力をキャプチャします。
  • 分類を使用するときに、落下しないオブジェクトを検出したい場合 サポートされているカテゴリに明確に分類し、不明点に対する特別な処理を実装 説明します。

また、 ML Kit マテリアル デザイン ショーケース アプリと マテリアル デザイン ML を活用した特徴の収集のパターン

パフォーマンスの向上

リアルタイム アプリケーションでオブジェクト検出を使用する場合は、 実現するためのガイドラインは次のとおりです。

  • リアルタイム アプリケーションでストリーミング モードを使用する場合は、 物体の検出に重点を置いているためです。

  • Camera または camera2 API、 スロットリングするように構成されています。新しい動画が フレームが使用可能になる場合は、そのフレームをドロップします。詳しくは、 <ph type="x-smartling-placeholder"></ph> VisionProcessorBase クラスをご覧ください。
  • CameraX API を使用する場合は、 バックプレッシャー戦略がデフォルト値に <ph type="x-smartling-placeholder"></ph> ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST。 これにより、分析のために一度に 1 つの画像のみが配信されるようになります。もしより多くの画像が 生成された場合、自動的に破棄され、 提供します。次の呼び出しによって分析中の画像を閉じたら、 ImageProxy.close() が呼び出されると、次に最新の画像が配信されます。
  • 検出機能の出力を使用して、ディスプレイにグラフィックをオーバーレイする場合、 まず ML Kit から結果を取得してから、画像をレンダリングする 1 ステップでオーバーレイできますこれにより、ディスプレイ サーフェスにレンダリングされます。 入力フレームごとに 1 回だけです。詳しくは、 <ph type="x-smartling-placeholder"></ph> CameraSourcePreview および <ph type="x-smartling-placeholder"></ph> GraphicOverlay クラスをご覧ください。
  • Camera2 API を使用する場合は、 ImageFormat.YUV_420_888 形式。古い Camera API を使用する場合は、 ImageFormat.NV21 形式。