Bilder mit einem von AutoML trainierten Modell unter Android mit Labels versehen

Nachdem Sie Ihr eigenes Modell mit AutoML Vision Edge trainiert haben, können Sie damit in Ihrer App Bilder mit Labels versehen. Es gibt zwei Möglichkeiten, mit AutoML Vision Edge trainierte Modelle zu integrieren: Sie können das Modell bündeln, indem Sie es im Asset-Ordner Ihrer App ablegen, oder es dynamisch von Firebase herunterladen.
Optionen für Modellbündelung
In deiner App gebündelt
  • Das Modell ist Teil des APK deiner App
  • Das Modell ist sofort verfügbar, auch wenn das Android-Gerät offline ist.
  • Kein Firebase-Projekt erforderlich
Mit Firebase gehostet
  • Hosten Sie das Modell, indem Sie es in Firebase Machine Learning hochladen.
  • Reduziert die APK-Größe
  • Das Modell wird bei Bedarf heruntergeladen
  • Modellaktualisierungen übertragen, ohne die App noch einmal zu veröffentlichen
  • Einfache A/B-Tests mit Firebase Remote Config
  • Erfordert ein Firebase-Projekt

Ausprobieren

  • Probieren Sie die Beispiel-App aus, um ein Beispiel für die Verwendung dieser API zu sehen.

Hinweis

1. In die Datei build.gradle auf Projektebene muss das Maven-Repository von Google in die Abschnitte buildscript und allprojects aufgenommen werden.

2. Fügen Sie die Abhängigkeiten für die ML Kit-Android-Bibliotheken der Gradle-Datei auf App-Ebene Ihres Moduls hinzu, die in der Regel app/build.gradle ist: So bündeln Sie ein Modell mit Ihrer App:
    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-automl:16.2.1'
    }
    
Fügen Sie die linkFirebase-Abhängigkeit hinzu, um ein Modell dynamisch aus Firebase herunterzuladen:
    dependencies {
      // ...
      // Image labeling feature with automl model downloaded
      // from firebase
      implementation 'com.google.mlkit:image-labeling-automl:16.2.1'
      implementation 'com.google.mlkit:linkfirebase:16.0.1'
    }
    
3. Wenn Sie ein Modell herunterladen möchten, müssen Sie Firebase zu Ihrem Android-Projekt hinzufügen, falls Sie dies noch nicht getan haben. Dies ist beim Bündeln des Modells nicht erforderlich.

1. Modell laden

Lokale Modellquelle konfigurieren

So bündeln Sie das Modell mit Ihrer Anwendung:

1. Extrahieren Sie das Modell und seine Metadaten aus dem ZIP-Archiv, das Sie aus der Firebase Console heruntergeladen haben. Wir empfehlen Ihnen, die Dateien so zu verwenden, wie Sie sie heruntergeladen haben, ohne Änderungen (einschließlich der Dateinamen).

2. Fügen Sie das Modell und die zugehörigen Metadatendateien in das Anwendungspaket ein:

a. Wenn Sie in Ihrem Projekt keinen Asset-Ordner haben, erstellen Sie einen. Klicken Sie dazu mit der rechten Maustaste auf den Ordner app/ und dann auf Neu > Ordner > Asset-Ordner.

b. Erstellen Sie unter dem Asset-Ordner einen Unterordner, der die Modelldateien enthalten soll.

c. Kopieren Sie die Dateien model.tflite, dict.txt und manifest.json in den Unterordner. Alle drei Dateien müssen sich im selben Ordner befinden.

3. Fügen Sie der Datei build.gradle Ihrer App Folgendes hinzu, damit Gradle die Modelldatei beim Erstellen der App nicht komprimiert:
    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    
Die Modelldatei ist im App-Paket enthalten und steht ML Kit als Roh-Asset zur Verfügung.

Hinweis: Ab Version 4.1 des Android-Gradle-Plug-ins wird .tflite standardmäßig der noCompress-Liste hinzugefügt und die oben genannten Schritte werden nicht mehr benötigt.

4. Erstellen Sie ein LocalModel-Objekt und geben Sie den Pfad zur Modellmanifestdatei an:

Kotlin

val localModel = AutoMLImageLabelerLocalModel.Builder()
        .setAssetFilePath("manifest.json")
        // or .setAbsoluteFilePath(absolute file path to manifest file)
        .build()

Java

AutoMLImageLabelerLocalModel localModel =
    new AutoMLImageLabelerLocalModel.Builder()
        .setAssetFilePath("manifest.json")
        // or .setAbsoluteFilePath(absolute file path to manifest file)
        .build();

Eine von Firebase gehostete Modellquelle konfigurieren

Wenn Sie das extern gehostete Modell verwenden möchten, erstellen Sie ein RemoteModel-Objekt und geben Sie dabei den Namen an, den Sie dem Modell bei der Veröffentlichung zugewiesen haben:

Kotlin

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

Java

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

Starten Sie dann die Aufgabe zum Herunterladen des Modells und geben Sie die Bedingungen an, unter denen Sie das Herunterladen zulassen möchten. Wenn sich das Modell nicht auf dem Gerät befindet oder eine neuere Version des Modells verfügbar ist, wird es von der Aufgabe asynchron von Firebase heruntergeladen:

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.
            }
        });

Viele Anwendungen starten die Downloadaufgabe im Initialisierungscode, aber Sie können dies jederzeit tun, bevor Sie das Modell verwenden müssen.

Bildlabelersteller aus dem Modell erstellen

Nachdem Sie die Modellquellen konfiguriert haben, erstellen Sie aus einer davon ein ImageLabeler-Objekt.

Wenn Sie nur ein lokal gebündeltes Modell haben, erstellen Sie einfach einen Labelersteller aus Ihrem AutoMLImageLabelerLocalModel-Objekt und konfigurieren Sie den erforderlichen Konfidenzgrenzwert (siehe Modell bewerten):

Kotlin

val autoMLImageLabelerOptions = AutoMLImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0)  // Evaluate your model in the Firebase console
                                // to determine an appropriate value.
    .build()
val labeler = ImageLabeling.getClient(autoMLImageLabelerOptions)

Java

AutoMLImageLabelerOptions autoMLImageLabelerOptions =
        new AutoMLImageLabelerOptions.Builder(localModel)
                .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                               // to determine an appropriate value.
                .build();
ImageLabeler labeler = ImageLabeling.getClient(autoMLImageLabelerOptions)

Wenn Sie ein extern gehostetes Modell haben, müssen Sie prüfen, ob es heruntergeladen wurde, bevor Sie es ausführen. Sie können den Status der Modelldownloadaufgabe mit der Methode isModelDownloaded() des Modellmanagers prüfen.

Sie müssen dies nur vor dem Ausführen des Labelerstellers prüfen. Wenn Sie jedoch sowohl ein extern gehostetes Modell als auch ein lokal gebündeltes Modell haben, kann es sinnvoll sein, diese Prüfung beim Instanziieren des Bildlabelerstellers durchzuführen: Erstellen Sie einen Labelersteller aus dem Remotemodell, wenn es heruntergeladen wurde, und andernfalls aus dem lokalen Modell.

Kotlin

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded -> 
    val optionsBuilder =
        if (isDownloaded) {
            AutoMLImageLabelerOptions.Builder(remoteModel)
        } else {
            AutoMLImageLabelerOptions.Builder(localModel)
        }
    // Evaluate your model in the Firebase console to determine an appropriate threshold.
    val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
    val labeler = ImageLabeling.getClient(options)
}

Java

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(Boolean isDownloaded) {
                AutoMLImageLabelerOptions.Builder optionsBuilder;
                if (isDownloaded) {
                    optionsBuilder = new AutoMLImageLabelerOptions.Builder(remoteModel);
                } else {
                    optionsBuilder = new AutoMLImageLabelerOptions.Builder(localModel);
                }
                AutoMLImageLabelerOptions options = optionsBuilder
                        .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                                       // to determine an appropriate threshold.
                        .build();

                ImageLabeler labeler = ImageLabeling.getClient(options);
            }
        });

Wenn Sie nur ein extern gehostetes Modell haben, sollten Sie die modellbezogenen Funktionen deaktivieren und z. B. einen Teil Ihrer UI ausblenden oder ausblenden, bis Sie bestätigt haben, dass das Modell heruntergeladen wurde. Dazu hängen Sie einen Listener an die Methode download() des Modellmanagers an:

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.
            }
        });

2. Eingabebild vorbereiten

Erstellen Sie dann für jedes Bild, das Sie mit einem Label versehen möchten, ein InputImage-Objekt aus Ihrem Bild. Der Bildlabelersteller wird am schnellsten ausgeführt, wenn Sie Bitmap oder, wenn Sie die Camera2 API verwenden, eine YUV_420_888-media.Image verwenden. Diese werden nach Möglichkeit empfohlen.

Sie können ein InputImage-Objekt aus verschiedenen Quellen erstellen. Diese werden unten jeweils erläutert.

Mit einem media.Image

Wenn Sie ein InputImage-Objekt aus einem media.Image-Objekt erstellen möchten, z. B. wenn Sie ein Bild mit der Kamera eines Geräts aufnehmen, übergeben Sie das media.Image-Objekt und die Bilddrehung an InputImage.fromMediaImage().

Wenn Sie die CameraX-Bibliothek verwenden, wird der Rotationswert von den Klassen OnImageCapturedListener und ImageAnalysis.Analyzer berechnet.

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
          // ...
        }
    }
}

Wenn Sie keine Kamerabibliothek verwenden, die den Drehgrad des Bildes angibt, können Sie ihn anhand des Gerätedrehungsgrads und der Ausrichtung des Kamerasensors im Gerät berechnen:

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;
}

Übergeben Sie dann das media.Image-Objekt und den Wert für den Rotationsgrad an InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Datei-URI verwenden

Übergeben Sie den App-Kontext und den Datei-URI an InputImage.fromFilePath(), um ein InputImage-Objekt aus einem Datei-URI zu erstellen. Das ist nützlich, wenn du mit einem ACTION_GET_CONTENT-Intent den Nutzer auffordern möchtest, ein Bild aus seiner Galerie-App auszuwählen.

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();
}

Mithilfe von ByteBuffer oder ByteArray

Zum Erstellen eines InputImage-Objekts aus einem ByteBuffer- oder ByteArray-Objekt müssen Sie zuerst den Grad der Bilddrehung berechnen, wie zuvor für die media.Image-Eingabe beschrieben. Erstellen Sie dann das InputImage-Objekt mit dem Zwischenspeicher oder Array sowie der Höhe, Breite, Farbcodierungsformat und Rotationsgrad des Bildes:

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
);

Mit einem Bitmap

Mit der folgenden Deklaration kannst du ein InputImage-Objekt aus einem Bitmap-Objekt erstellen:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

Das Bild wird durch ein Bitmap-Objekt zusammen mit Grad der Drehung dargestellt.

3. Labelersteller für Bilder ausführen

Wenn Sie Objekte in einem Bild mit Labels versehen möchten, übergeben Sie das image-Objekt an die Methode process() der ImageLabeler.

Kotlin

labeler.process(image)
        .addOnSuccessListener { labels ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }

Java

labeler.process(image)
        .addOnSuccessListener(new OnSuccessListener<List<ImageLabel>>() {
            @Override
            public void onSuccess(List<ImageLabel> labels) {
                // Task completed successfully
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

4. Informationen zu mit Labels versehenen Objekten abrufen

Wenn der Vorgang zum Beschriften von Bildern erfolgreich ist, wird eine Liste von ImageLabel-Objekten an den Erfolgs-Listener übergeben. Jedes ImageLabel-Objekt steht für etwas, das im Bild mit einem Label versehen wurde. Sie können die Textbeschreibung jedes Labels, den Konfidenzwert der Übereinstimmung und den Index der Übereinstimmung abrufen. Beispiel:

Kotlin

for (label in labels) {
    val text = label.text
    val confidence = label.confidence
    val index = label.index
}

Java

for (ImageLabel label : labels) {
    String text = label.getText();
    float confidence = label.getConfidence();
    int index = label.getIndex();
}

Tipps zur Verbesserung der Echtzeitleistung

Wenn Sie Bilder in einer Echtzeitanwendung mit Labels versehen möchten, beachten Sie die folgenden Richtlinien, um die besten Framerates zu erzielen:

  • Wenn Sie die Camera oder camera2 API verwenden, drosseln Sie Aufrufe an den Bildlabelersteller. Wenn ein neuer Videoframe verfügbar wird, während der Bildlabelersteller ausgeführt wird, löschen Sie ihn. Ein Beispiel hierfür finden Sie in der Beispiel-App der Kurzanleitung in der Klasse VisionProcessorBase.
  • Wenn Sie die CameraX API verwenden, muss die Rückdruckstrategie auf den Standardwert ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST festgelegt sein. Dadurch wird sichergestellt, dass jeweils nur ein Bild zur Analyse geliefert wird. Werden weitere Bilder erstellt, während das Analysetool ausgelastet ist, werden diese automatisch gelöscht und nicht in die Warteschlange gestellt. Nachdem das zu analysierende Bild durch Aufrufen von ImageProxy.close() geschlossen wurde, wird das jeweils neueste Bild bereitgestellt.
  • Wenn Sie die Ausgabe des Bildlabelerstellers verwenden, um Grafiken auf dem Eingabebild einzublenden, rufen Sie zuerst das Ergebnis aus dem ML Kit ab und rendern dann das Bild und das Overlay in einem einzigen Schritt. Dies wird für jeden Eingabeframe nur einmal auf der Anzeigeoberfläche gerendert. Ein entsprechendes Beispiel finden Sie in der Beispiel-App der Kurzanleitung in den Klassen CameraSourcePreview und GraphicOverlay.
  • Wenn du die Camera2 API verwendest, nimm Bilder im ImageFormat.YUV_420_888-Format auf. Wenn du die ältere Camera API verwendest, nimm Bilder im ImageFormat.NV21-Format auf.