Bilder mit einem benutzerdefinierten Modell unter Android mit Labels versehen

Mit Sammlungen den Überblick behalten Sie können Inhalte basierend auf Ihren Einstellungen speichern und kategorisieren.

Mit ML Kit können Sie Entitäten in einem Bild erkennen und mit Labels versehen. Diese API unterstützt eine Vielzahl von benutzerdefinierten Bildklassifizierungsmodellen. Unter Benutzerdefinierte Modelle mit ML Kit finden Sie Informationen zu Anforderungen an die Modellkompatibilität, dazu, wo Sie vortrainierte Modelle finden und wie Sie Ihre eigenen Modelle trainieren können.

Es gibt zwei Möglichkeiten, Image-Labeling in benutzerdefinierte Modelle zu integrieren: durch das Bündeln der Pipeline als Teil Ihrer App oder durch Verwenden einer Pipeline-Bundles, die von den Google Play-Diensten abhängen. Wenn Sie die Pipeline ohne Bundle auswählen, ist Ihre Anwendung kleiner. Weitere Details finden Sie in der Tabelle unten.

GruppiertNicht gebündelt
Name der Bibliothekcom.google.mlkit:image-labeling-customcom.google.android.gms:play-services-mlkit-image-labeling-custom
ImplementierungDie Pipeline ist bei der Build-Erstellung statisch mit Ihrer App verknüpft.Die Pipeline wird dynamisch über die Google Play-Dienste heruntergeladen.
App-GrößeCa. 3,8 MBDie Größe wurde um ca. 200 KB erhöht.
InitialisierungszeitDie Pipeline ist sofort verfügbar.Möglicherweise muss auf den Download der Pipeline gewartet werden, bevor er verwendet werden kann.
API-LebenszyklusphaseGeneral Availability (GA)Beta

Es gibt zwei Möglichkeiten, ein benutzerdefiniertes Modell zu integrieren: bündeln Sie das Modell, indem Sie es in den Asset-Ordner Ihrer App verschieben oder dynamisch aus Firebase herunterladen. In der folgenden Tabelle werden diese beiden Optionen verglichen.

Gebündeltes Modell Gehostetes Modell
Das Modell ist Teil des APKs Ihrer App und vergrößert dadurch seine Größe. Das Modell ist nicht Teil Ihres APKs. Sie wird durch Hochladen in Firebase Machine Learning gehostet.
Das Modell ist sofort verfügbar, auch wenn das Android-Gerät offline ist Das Modell wird on demand heruntergeladen
Kein Firebase-Projekt erforderlich Firebase-Projekt erforderlich
Sie müssen Ihre App noch einmal veröffentlichen, um das Modell zu aktualisieren Modellaktualisierungen per Push übertragen, ohne die App neu zu veröffentlichen
Keine integrierten A/B-Tests Einfache A/B-Tests mit Firebase Remote Config

Hinweis

  1. Achten Sie darauf, dass Sie in Ihrer build.gradle-Datei auf Projektebene das Maven-Repository von Google in die Abschnitte buildscript und allprojects aufnehmen.

  2. Fügen Sie die Abhängigkeiten für die ML Kit-Android-Bibliotheken der Gradle-Datei auf App-Ebene hinzu, die normalerweise app/build.gradle ist. Wählen Sie je nach Ihren Anforderungen eine der folgenden Abhängigkeiten aus:

    So bündeln Sie die Pipeline mit Ihrer Anwendung:

    dependencies {
      // ...
      // Use this dependency to bundle the pipeline with your app
      implementation 'com.google.mlkit:image-labeling-custom:17.0.1'
    }
    

    So verwenden Sie die Pipeline in Google Play-Diensten:

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded pipeline in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta4'
    }
    
  3. Wenn Sie die Pipeline in den Google Play-Diensten verwenden, können Sie die App so konfigurieren, dass sie automatisch auf das Gerät heruntergeladen wird, nachdem sie aus dem Play Store installiert wurde. Fügen Sie dazu der Datei AndroidManifest.xml Ihrer App die folgende Deklaration hinzu:

    <application ...>
        ...
        <meta-data
            android:name="com.google.mlkit.vision.DEPENDENCIES"
            android:value="custom_ica" />
        <!-- To use multiple downloads: android:value="custom_ica,download2,download3" -->
    </application>
    

    Sie können die Pipelineverfügbarkeit auch explizit prüfen und den Download über die ModuleInstallClient API der Google Play-Dienste anfordern.

    Wenn Sie die Installationszeit der Pipeline nicht aktivieren oder einen expliziten Download anfordern, wird die Pipeline beim ersten Ausführen des Labelerstellers heruntergeladen. Anfragen, die Sie vor Abschluss des Downloads senden, führen zu keinem Ergebnis.

  4. Fügen Sie die Abhängigkeit linkFirebase hinzu, wenn Sie ein Modell dynamisch aus Firebase herunterladen möchten:

    Wenn Sie ein Modell dynamisch aus Firebase herunterladen möchten, fügen Sie die Abhängigkeit linkFirebase hinzu:

    dependencies {
      // ...
      // Image labeling feature with model downloaded from Firebase
      implementation 'com.google.mlkit:image-labeling-custom:17.0.1'
      // Or use the dynamically downloaded pipeline in Google Play Services
      // implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta4'
      implementation 'com.google.mlkit:linkfirebase:17.0.0'
    }
    
  5. 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 App:

  1. Kopieren Sie die Modelldatei (in der Regel auf .tflite oder .lite) in den Ordner assets/ Ihrer App. (Eventuell müssen Sie den Ordner erstellen, indem Sie mit der rechten Maustaste auf den Ordner app/ klicken und dann auf Neu > Ordner &Asset-Ordner klicken.)

  2. Fügen Sie dann der Datei build.gradle Ihrer App Folgendes hinzu, um zu verhindern, dass die Modelldatei beim Erstellen der Anwendung von Gradle komprimiert wird:

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

    Die Modelldatei ist im App-Paket enthalten und für ML Kit als Roh-Asset verfügbar.

  3. Erstellen Sie ein LocalModel-Objekt und geben Sie den Pfad zur Modelldatei an:

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

Von Firebase gehostete Modellquelle konfigurieren

Wenn Sie das remote gehostete Modell verwenden möchten, erstellen Sie bis zum FirebaseModelSource ein RemoteModel-Objekt und geben Sie 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 =
    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();

Starten Sie dann die Modelldownloadaufgabe und geben Sie die Bedingungen an, unter denen Sie das Herunterladen zulassen möchten. Wenn das Modell nicht auf dem Gerät installiert ist oder eine neuere Version des Modells verfügbar ist, wird das Modell 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 Apps starten die Downloadaufgabe im Initialisierungscode. Sie können dies jedoch jederzeit tun, bevor Sie das Modell verwenden müssen.

Image Labeler konfigurieren

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

Folgende Optionen sind verfügbar:

Optionen
confidenceThreshold

Minimaler Konfidenzwert der erkannten Labels. Wenn nicht festgelegt, wird jeder Klassifikatorgrenzwert verwendet, der in den Metadaten des Modells angegeben ist. Wenn das Modell keine Metadaten enthält oder die Metadaten keinen Klassifikatorgrenzwert angeben, wird der Standardgrenzwert 0,0 verwendet.

maxResultCount

Maximale Anzahl der Labels, die zurückgegeben werden sollen. Wenn nicht festgelegt, wird der Standardwert 10 verwendet.

Wenn Sie nur ein lokal gebündeltes Modell haben, erstellen Sie einfach einen Labelersteller aus Ihrem LocalModel-Objekt:

Kotlin

val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0.5f)
    .setMaxResultCount(5)
    .build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)

Java

CustomImageLabelerOptions customImageLabelerOptions =
        new CustomImageLabelerOptions.Builder(localModel)
            .setConfidenceThreshold(0.5f)
            .setMaxResultCount(5)
            .build();
ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);

Wenn Sie ein remote 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.

Obwohl Sie dies nur vor der Ausführung des Labelerstellers überprüfen müssen, wenn Sie sowohl ein remote gehostetes Modell als auch ein lokal gebündeltes Modell haben, kann es sinnvoll sein, diese Prüfung beim Instanziieren des Bild-Labelerstellers durchzuführen: Erstellen Sie einen Labelersteller für das Remote-Modell, wenn es heruntergeladen wurde, oder aus dem lokalen Modell.

Kotlin

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded ->
    val optionsBuilder =
        if (isDownloaded) {
            CustomImageLabelerOptions.Builder(remoteModel)
        } else {
            CustomImageLabelerOptions.Builder(localModel)
        }
    val options = optionsBuilder
                  .setConfidenceThreshold(0.5f)
                  .setMaxResultCount(5)
                  .build()
    val labeler = ImageLabeling.getClient(options)
}

Java

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(Boolean isDownloaded) {
                CustomImageLabelerOptions.Builder optionsBuilder;
                if (isDownloaded) {
                    optionsBuilder = new CustomImageLabelerOptions.Builder(remoteModel);
                } else {
                    optionsBuilder = new CustomImageLabelerOptions.Builder(localModel);
                }
                CustomImageLabelerOptions options = optionsBuilder
                    .setConfidenceThreshold(0.5f)
                    .setMaxResultCount(5)
                    .build();
                ImageLabeler labeler = ImageLabeling.getClient(options);
            }
        });

Wenn Sie nur ein remote gehostetes Modell haben, sollten Sie modellbezogene Funktionen deaktivieren (z. B. einen grauen Bereich ausblenden oder einen Teil Ihrer UI 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 dem Bild. Der Labelersteller von Bildern wird am schnellsten ausgeführt, wenn Sie eine Bitmap oder, wenn Sie die Camera2 API verwenden, eine YUV_420_888-media.Image verwenden. Diese wird nach Möglichkeit empfohlen.

Sie können ein InputImage-Objekt aus verschiedenen Quellen erstellen. Dies wird unten 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 von der Kamera eines Geräts aufnehmen, übergeben Sie das Objekt media.Image und die Bilddrehung an InputImage.fromMediaImage().

Wenn Sie die KameraX-Bibliothek verwenden, berechnen die Klassen OnImageCapturedListener und ImageAnalysis.Analyzer den Rotationswert für Sie.

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 du keine Kamerabibliothek verwendest, die dir den Grad der Drehung des Bildes liefert, kannst du ihn aus dem Rotationsgrad des Geräts 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;
}

Übergib dann das media.Image-Objekt und den Rotationsgradwert an InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Datei-URI verwenden

Übergeben Sie den Anwendungskontext und den Datei-URI an InputImage.fromFilePath(), um ein InputImage-Objekt aus einem Datei-URI zu erstellen. Dies ist nützlich, wenn Sie den Intent ACTION_GET_CONTENT verwenden, um den Nutzer aufzufordern, 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();
}

Mit ByteBuffer oder ByteArray

Um ein InputImage-Objekt aus einem ByteBuffer oder einem ByteArray zu erstellen, musst du 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 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

So erstellen Sie ein InputImage-Objekt aus einem Bitmap-Objekt:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

Das Bild wird durch ein Bitmap-Objekt mit Rotationsgrad dargestellt.

3. Image Labeler ausführen

Wenn Sie Objekte in einem Bild mit Labels kennzeichnen möchten, übergeben Sie das Objekt image an die Methode process() von 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 Elementen mit Label abrufen

Wenn der Vorgang zur Labelerstellung für Bilder erfolgreich ist, wird eine Liste der ImageLabel-Objekte an den Erfolgs-Listener übergeben. Jedes ImageLabel-Objekt stellt etwas dar, das im Bild mit einem Label versehen wurde. Sie können die Textbeschreibung jedes Labels (sofern in den Metadaten der TensorFlow Lite-Modelldatei verfügbar), den Konfidenzwert und den Index 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 du Bilder in einer Echtzeitanwendung mit Labels versehen möchtest, beachte die folgenden Richtlinien, um die besten Framerates zu erzielen:

  • Wenn Sie die API Camera oder camera2 verwenden, drosseln Sie Aufrufe an den Labelersteller von Bildern. Wenn während der Ausführung des Image Labeler ein neuer Videoframe verfügbar wird, lassen Sie den Frame los. Ein Beispiel findest du in der Kurzanleitungs-Beispielanwendung in der Klasse VisionProcessorBase.
  • Wenn Sie die CameraX API verwenden, achten Sie darauf, dass die Gegendruckstrategie auf den Standardwert ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST festgelegt ist. So wird sichergestellt, dass jeweils nur ein Bild zur Analyse übermittelt wird. Wenn bei der Ausarbeitung des Analysetools mehr Bilder erstellt werden, werden diese automatisch gelöscht und nicht in die Warteschlange gestellt. Sobald das analysierte Bild durch Aufrufen von ImageProxy.close() geschlossen wird, wird das nächste neueste Bild bereitgestellt.
  • Wenn Sie die Ausgabe des Image Labeler verwenden, um Grafiken auf dem Eingabebild einzublenden, rufen Sie zuerst das Ergebnis aus ML Kit ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. Dies wird für jeden Eingabeframe nur einmal auf der Anzeigeoberfläche gerendert. Ein Beispiel finden Sie in den Beispielkursen CameraSourcePreview und GraphicOverlay.
  • Wenn Sie die Camera2 API verwenden, nehmen Sie Bilder im Format ImageFormat.YUV_420_888 auf. Wenn du die ältere Camera API verwendest, solltest du Bilder im Format ImageFormat.NV21 aufnehmen.