Bilder mit einem mit AutoML trainierten Modell auf Android-Geräten mit Labels versehen

Nachdem Sie Ihr eigenes Modell mit AutoML Vision Edge trainiert haben, können Sie es in Ihrer App verwenden, um Bilder zu labeln. Es gibt zwei Möglichkeiten, mit AutoML Vision Edge trainierte Modelle zu integrieren: Sie können das Modell im Asset-Ordner Ihrer App bündeln oder es dynamisch von Firebase herunterladen.
Optionen für die Modellkombination
In Ihrer App gebündelt
  • Das Modell ist Teil des APK Ihrer 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.
  • Verringert die APK-Größe
  • Das Modell wird auf Anfrage heruntergeladen.
  • Modellupdates per Push senden, ohne Ihre App noch einmal zu veröffentlichen
  • Einfache A/B-Tests mit Firebase Remote Config
  • Ein Firebase-Projekt ist erforderlich

Jetzt ausprobieren

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 Ihres Moduls auf Anwendungsebene hinzu, die in der Regel app/build.gradle lautet: Wenn Sie ein Modell mit Ihrer App bündeln möchten:
    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-automl:16.2.1'
    }
    
Wenn Sie ein Modell dynamisch von Firebase herunterladen möchten, fügen Sie die Abhängigkeit linkFirebase hinzu:
    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 Ihrem Android-Projekt Firebase hinzufügen, falls Sie dies noch nicht getan haben. Das ist nicht erforderlich, wenn Sie das Modell bündeln.

1. Modell laden

Lokale Modellquelle konfigurieren

So bündeln Sie das Modell mit Ihrer App:

1. Extrahieren Sie das Modell und seine Metadaten aus dem ZIP-Archiv, das Sie aus der Firebase Console heruntergeladen haben. Wir empfehlen, die Dateien unverändert (einschließlich der Dateinamen) zu verwenden.

2. Fügen Sie Ihr Modell und die zugehörigen Metadatendateien in Ihr App-Paket ein:

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

b. Erstellen Sie einen Unterordner im Ordner „Assets“, der die Modelldateien enthält.

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 build.gradle-Datei Ihrer App Folgendes hinzu, damit Gradle die Modelldatei beim Erstellen der App nicht komprimiert:
    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    
Die Modelldatei wird in das App-Paket aufgenommen und ML Kit als Roh-Asset zur Verfügung gestellt.

Hinweis: Ab Version 4.1 des Android Gradle-Plug-ins wird „.tflite“ standardmäßig der Liste „noCompress“ hinzugefügt. Die oben genannten Schritte sind dann nicht mehr erforderlich.

4. Erstellen Sie ein LocalModel-Objekt und geben Sie den Pfad zur Modellmanifestdatei an:
val localModel = AutoMLImageLabelerLocalModel.Builder()
        .setAssetFilePath("manifest.json")
        // or .setAbsoluteFilePath(absolute file path to manifest file)
        .build()
AutoMLImageLabelerLocalModel localModel =
    new AutoMLImageLabelerLocalModel.Builder()
        .setAssetFilePath("manifest.json")
        // or .setAbsoluteFilePath(absolute file path to manifest file)
        .build();

Firebase-gehostete Modellquelle konfigurieren

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

// Specify the name you assigned in the Firebase console.
val remoteModel =
    AutoMLImageLabelerRemoteModel.Builder("your_model_name").build()
// 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 der Download zulässig sein soll. Wenn das Modell nicht auf dem Gerät vorhanden ist oder eine neuere Version des Modells verfügbar ist, wird es von der Aufgabe asynchron von Firebase heruntergeladen:

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

Viele Apps starten die Downloadaufgabe in ihrem Initialisierungscode, Sie können dies aber auch jederzeit tun, bevor Sie das Modell verwenden müssen.

Bildlabeler aus Ihrem Modell erstellen

Nachdem Sie Ihre Modellquellen konfiguriert haben, erstellen Sie ein ImageLabeler-Objekt aus einer der Quellen.

Wenn Sie nur ein lokal bereitgestelltes Modell haben, erstellen Sie einfach einen Labeler aus Ihrem AutoMLImageLabelerLocalModel-Objekt und konfigurieren Sie den gewünschten Grenzwert für den Konfidenzwert (siehe Modell bewerten):

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)
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 isModelDownloaded()-Methode des Modellmanagers prüfen.

Sie müssen dies zwar nur vor dem Ausführen des Labels bestätigen, wenn Sie jedoch sowohl ein remote gehostetes Modell als auch ein lokal gebündeltes Modell haben, kann es sinnvoll sein, diese Prüfung beim Instanziieren des Bild-Labelers durchzuführen: Erstellen Sie einen Labeler aus dem Remote-Modell, wenn es heruntergeladen wurde, andernfalls aus dem lokalen Modell.

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)
}
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 modellverwandte Funktionen deaktivieren, z. B. einen Teil der Benutzeroberfläche grau ausblenden oder ausblenden, bis Sie bestätigen, dass das Modell heruntergeladen wurde. Dazu fügen Sie der download()-Methode des Modellmanagers einen Listener hinzu:

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.
    }
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 beschriften möchten, ein InputImage-Objekt aus Ihrem Bild. Die Bildbeschriftung funktioniert am schnellsten, wenn Sie eine Bitmap oder, wenn Sie die camera2 API verwenden, eine YUV_420_888 media.Image verwenden. Wir empfehlen, nach Möglichkeit diese Formate zu verwenden.

Sie können ein InputImage-Objekt aus verschiedenen Quellen erstellen. Im Folgenden werden die einzelnen Quellen 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 Drehung des Bildes an InputImage.fromMediaImage().

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

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
            // ...
        }
    }
}
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 Drehwinkel des Bildes angibt, können Sie ihn anhand des Drehwinkels des Geräts und der Ausrichtung des Kamerasensors im Gerät berechnen:

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
}
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 Drehungsgrad an InputImage.fromMediaImage():

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

Datei-URI verwenden

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

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

Mit einem ByteBuffer oder ByteArray

Wenn Sie ein InputImage-Objekt aus einem ByteBuffer oder ByteArray erstellen möchten, berechnen Sie zuerst den Drehwinkel des Bildes, wie oben für die media.Image-Eingabe beschrieben. Erstellen Sie dann das InputImage-Objekt mit dem Puffer oder Array sowie der Höhe, Breite, Farbcodierung und dem Drehgrad des Bilds:

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

Wenn Sie ein InputImage-Objekt aus einem Bitmap-Objekt erstellen möchten, verwenden Sie die folgende Deklaration:

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

Das Bild wird durch ein Bitmap-Objekt zusammen mit den Drehgraden dargestellt.

3. Bildlabeler ausführen

Wenn Sie Objekte in einem Bild mit Labels versehen möchten, übergeben Sie das image-Objekt an die process()-Methode des ImageLabeler-Objekts.
labeler.process(image)
        .addOnSuccessListener { labels ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }
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 gekennzeichneten Objekten abrufen

Wenn der Vorgang zum Beschriften von Bildern erfolgreich war, wird dem Erfolgsempfänger eine Liste von ImageLabel-Objekten übergeben. Jedes ImageLabel-Objekt steht für etwas, das im Bild gekennzeichnet wurde. Sie können die Textbeschreibung jedes Labels, den Konfidenzwert der Übereinstimmung und den Index der Übereinstimmung abrufen. Beispiel:

for (label in labels) {
    val text = label.text
    val confidence = label.confidence
    val index = label.index
}
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 taggen möchten, beachten Sie die folgenden Richtlinien, um die beste Framerate zu erzielen:

  • Wenn Sie die Camera- oder camera2-API verwenden, begrenzen Sie die Aufrufe an den Bildlabeler. Wenn während der Ausführung des Bildestikkers ein neuer Videoframe verfügbar wird, legen Sie ihn ab. Ein Beispiel finden Sie in der Klasse VisionProcessorBase in der Beispiel-App für die Schnellstartanleitung.
  • Wenn Sie die CameraX API verwenden, muss die Backpressure-Strategie auf den Standardwert ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST festgelegt sein. So wird sichergestellt, dass immer nur ein Bild zur Analyse gesendet wird. Wenn mehr Bilder erstellt werden, während der Analyser beschäftigt ist, werden sie automatisch gelöscht und nicht für die Übermittlung in die Warteschlange gestellt. Sobald das analysierte Bild durch Aufrufen von ImageProxy.close() geschlossen wurde, wird das nächste aktuelle Bild gesendet.
  • Wenn Sie die Ausgabe des Bildes-Labelers verwenden, um Grafiken auf das Eingabebild zu legen, rufen Sie zuerst das Ergebnis von ML Kit ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. Dieser wird nur einmal pro Eingabeframe auf der Displayoberfläche gerendert. Eines dieser Beispiele finden Sie in der Beispiel-App für den Schnellstart in den Klassen CameraSourcePreview und GraphicOverlay.
  • Wenn Sie die Camera2 API verwenden, sollten Sie Bilder im ImageFormat.YUV_420_888-Format aufnehmen. Wenn Sie die ältere Camera API verwenden, nehmen Sie Bilder im ImageFormat.NV21-Format auf.