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

Nachdem Sie Ihr eigenes Modell mit AutoML Vision Edge trainiert haben , können Sie es in Ihrer App zum Beschriften von Bildern verwenden.

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 Sie können es dynamisch von Firebase herunterladen.

Optionen zur Modellbündelung
In Ihrer App gebündelt
  • Das Modell ist Teil der APK Ihrer App
  • Das Modell ist sofort verfügbar, auch wenn das Android-Gerät offline ist
  • Kein Firebase-Projekt erforderlich
Gehostet mit Firebase
  • Hosten Sie das Modell, indem Sie es auf Firebase Machine Learning hochladen
  • Reduziert die APK-Größe
  • Das Modell wird bei Bedarf heruntergeladen
  • Pushen Sie Modellaktualisierungen, ohne Ihre App erneut zu veröffentlichen
  • Einfaches A/B-Testen mit Firebase Remote Config
  • Erfordert ein Firebase-Projekt

Bevor Sie beginnen

  1. Fügen Sie die Abhängigkeiten für die ML Kit-Android-Bibliotheken zur Gradle-Datei auf App-Ebene Ihres Moduls hinzu, die normalerweise app/build.gradle lautet:

    So bündeln Sie ein Modell mit Ihrer App:

    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-custom:16.3.1'
    }
    

    Um ein Modell dynamisch von Firebase herunterzuladen, fügen Sie die linkFirebase Abhängigkeit hinzu:

    dependencies {
      // ...
      // Image labeling feature with automl model downloaded
      // from firebase
      implementation 'com.google.mlkit:image-labeling-custom:16.3.1'
      implementation 'com.google.mlkit:linkfirebase:16.1.0'
    }
    
  2. Wenn Sie ein Modell herunterladen möchten , stellen Sie sicher, dass Sie Firebase zu Ihrem Android-Projekt hinzufügen , sofern Sie dies noch nicht getan haben. Dies ist nicht erforderlich, wenn Sie das Modell bündeln.

1. Laden Sie das Modell

Konfigurieren Sie eine lokale Modellquelle

So bündeln Sie das Modell mit Ihrer App:

  1. Extrahieren Sie das Modell und seine Metadaten aus dem ZIP-Archiv, das Sie von der Firebase-Konsole 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 Ihr Modell und seine Metadatendateien in Ihr App-Paket ein:

    1. Wenn Ihr Projekt keinen Ordner „Assets“ enthält, erstellen Sie einen, indem Sie mit der rechten Maustaste auf den Ordner app/ klicken und dann auf „Neu > Ordner > Ordner „Assets““ klicken.
    2. Erstellen Sie unter dem Assets-Ordner einen Unterordner für die Modelldateien.
    3. 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, um sicherzustellen, dass Gradle die Modelldatei beim Erstellen der App nicht komprimiert:

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

    Die Modelldatei wird im App-Paket enthalten und steht ML Kit als Roh-Asset zur Verfügung.

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

    Java

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

    Kotlin

    val localModel = LocalModel.Builder()
        .setAssetManifestFilePath("manifest.json")
        // or .setAbsoluteManifestFilePath(absolute file path to manifest file)
        .build()
    

Konfigurieren Sie eine von Firebase gehostete Modellquelle

Um das remote gehostete Modell zu verwenden, erstellen Sie ein CustomRemoteModel Objekt und geben Sie dabei den Namen an, den Sie dem Modell beim Veröffentlichen zugewiesen haben:

Java

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

Kotlin

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

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

Java

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

Kotlin

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

Viele Apps starten die Download-Aufgabe in ihrem Initialisierungscode, Sie können dies jedoch jederzeit tun, bevor Sie das Modell verwenden müssen.

Erstellen Sie aus Ihrem Modell einen Bildbeschrifter

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

Wenn Sie nur über ein lokal gebündeltes Modell verfügen, erstellen Sie einfach einen Labeler aus Ihrem CustomImageLabelerOptions Objekt und konfigurieren Sie den Schwellenwert für die Konfidenzbewertung, den Sie benötigen möchten (siehe Bewerten Ihres Modells ):

Java

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

Kotlin

val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0.0f)  // Evaluate your model in the Cloud console
                                   // to determine an appropriate value.
    .build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)

Wenn Sie über ein remote gehostetes Modell verfügen, müssen Sie überprüfen, ob es heruntergeladen wurde, bevor Sie es ausführen. Sie können den Status der Modell-Download-Aufgabe mit der Methode isModelDownloaded() des Modellmanagers überprüfen.

Obwohl Sie dies nur bestätigen müssen, bevor Sie den Labeler ausführen, kann es sinnvoll sein, diese Prüfung bei der Instanziierung des Bild-Labelers durchzuführen, wenn Sie sowohl über ein remote gehostetes Modell als auch über ein lokal gebündeltes Modell verfügen: Erstellen Sie einen Labeler aus dem Remote-Modell, wenn Es wurde heruntergeladen, andernfalls vom lokalen Modell.

Java

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener<Boolean>() {
            @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.0f)  // Evaluate your model in the Cloud console
                                                       // to determine an appropriate threshold.
                        .build();

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

Kotlin

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

Wenn Sie nur über ein remote gehostetes Modell verfügen, sollten Sie modellbezogene Funktionen deaktivieren (z. B. einen Teil Ihrer Benutzeroberfläche ausgrauen oder ausblenden), bis Sie bestätigen, dass das Modell heruntergeladen wurde. Sie können dies tun, indem Sie einen Listener an download() Methode des Modellmanagers anhängen:

Java

RemoteModelManager.getInstance().download(remoteModel, conditions)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @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.
            }
        });

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

2. Bereiten Sie das Eingabebild vor

Erstellen Sie dann für jedes Bild, das Sie beschriften möchten, ein InputImage Objekt aus Ihrem Bild. Der Bildbeschrifter läuft am schnellsten, wenn Sie eine Bitmap oder, wenn Sie die camera2-API verwenden, ein YUV_420_888 media.Image verwenden, was nach Möglichkeit empfohlen wird.

Sie können ein InputImage aus verschiedenen Quellen erstellen, die jeweils unten erläutert werden.

Verwenden eines media.Image

Um ein InputImage Objekt aus einem media.Image Objekt zu erstellen, beispielsweise wenn Sie ein Bild von 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, berechnen die Klassen OnImageCapturedListener und ImageAnalysis.Analyzer den Rotationswert für Sie.

Kotlin+KTX

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) {
        if (imageProxy == null || imageProxy.getImage() == null) {
            return;
        }
        Image mediaImage = imageProxy.getImage();
        InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees);
        // Pass image to an ML Kit Vision API
        // ...
    }
}

Wenn Sie keine Kamerabibliothek verwenden, die Ihnen den Rotationsgrad des Bildes liefert, können Sie ihn aus dem Rotationsgrad des Geräts und der Ausrichtung des Kamerasensors im Gerät berechnen:

Kotlin+KTX

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 90)
    ORIENTATIONS.append(Surface.ROTATION_90, 0)
    ORIENTATIONS.append(Surface.ROTATION_180, 270)
    ORIENTATIONS.append(Surface.ROTATION_270, 180)
}
/**
 * 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, context: Context): 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)

    // On most devices, the sensor orientation is 90 degrees, but for some
    // devices it is 270 degrees. For devices with a sensor orientation of
    // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
    val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
        .getCameraCharacteristics(cameraId)
        .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
    rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360

    // Return the corresponding FirebaseVisionImageMetadata rotation value.
    val result: Int
    when (rotationCompensation) {
        0 -> result = FirebaseVisionImageMetadata.ROTATION_0
        90 -> result = FirebaseVisionImageMetadata.ROTATION_90
        180 -> result = FirebaseVisionImageMetadata.ROTATION_180
        270 -> result = FirebaseVisionImageMetadata.ROTATION_270
        else -> {
            result = FirebaseVisionImageMetadata.ROTATION_0
            Log.e(TAG, "Bad rotation value: $rotationCompensation")
        }
    }
    return result
}

Java

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

/**
 * 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, Context context)
        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);

    // On most devices, the sensor orientation is 90 degrees, but for some
    // devices it is 270 degrees. For devices with a sensor orientation of
    // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
    CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);
    rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;

    // Return the corresponding FirebaseVisionImageMetadata rotation value.
    int result;
    switch (rotationCompensation) {
        case 0:
            result = FirebaseVisionImageMetadata.ROTATION_0;
            break;
        case 90:
            result = FirebaseVisionImageMetadata.ROTATION_90;
            break;
        case 180:
            result = FirebaseVisionImageMetadata.ROTATION_180;
            break;
        case 270:
            result = FirebaseVisionImageMetadata.ROTATION_270;
            break;
        default:
            result = FirebaseVisionImageMetadata.ROTATION_0;
            Log.e(TAG, "Bad rotation value: " + rotationCompensation);
    }
    return result;
}

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

Kotlin+KTX

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Verwendung eines Datei-URI

Um ein InputImage Objekt aus einem Datei-URI zu erstellen, übergeben Sie den App-Kontext und den Datei-URI an InputImage.fromFilePath() . Dies ist nützlich, wenn Sie eine ACTION_GET_CONTENT Absicht verwenden, um den Benutzer aufzufordern, ein Bild aus seiner Galerie-App auszuwählen.

Kotlin+KTX

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

Verwenden eines ByteBuffer oder ByteArray

Um ein InputImage Objekt aus einem ByteBuffer oder einem ByteArray zu erstellen, berechnen Sie zunächst den Grad der Bilddrehung wie zuvor für media.Image Eingabe beschrieben. Erstellen Sie dann das InputImage Objekt mit dem Puffer oder Array zusammen mit der Höhe, Breite, dem Farbcodierungsformat und dem Rotationsgrad des Bildes:

Kotlin+KTX

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* 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
);

Verwenden einer Bitmap

Um ein InputImage Objekt aus einem Bitmap Objekt zu erstellen, geben Sie die folgende Deklaration ab:

Kotlin+KTX

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

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

3. Führen Sie den Bildbeschrifter aus

Um Objekte in einem Bild zu beschriften, übergeben Sie das image an die Methode process() von ImageLabeler .

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

Kotlin

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

4. Informieren Sie sich über gekennzeichnete Objekte

Wenn der Bildbeschriftungsvorgang erfolgreich ist, wird eine Liste von ImageLabel Objekten an den Erfolgslistener übergeben. Jedes ImageLabel Objekt stellt etwas dar, das im Bild beschriftet wurde. Sie können die Textbeschreibung jedes Labels, den Konfidenzwert der Übereinstimmung und den Index der Übereinstimmung abrufen. Zum Beispiel:

Java

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

Kotlin

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

Tipps zur Verbesserung der Echtzeitleistung

Wenn Sie Bilder in einer Echtzeitanwendung beschriften möchten, befolgen Sie diese Richtlinien, um die besten Frameraten zu erzielen:

  • Drosseln Sie Aufrufe an den Bildbezeichner. Wenn ein neues Videobild verfügbar wird, während der Bildbeschrifter ausgeführt wird, löschen Sie das Bild. Ein Beispiel finden Sie in der VisionProcessorBase Klasse in der Schnellstart-Beispiel-App.
  • Wenn Sie die Ausgabe des Bildbeschrifters verwenden, um Grafiken auf dem Eingabebild zu überlagern, rufen Sie zuerst das Ergebnis ab und rendern Sie dann das Bild und überlagern Sie es in einem einzigen Schritt. Auf diese Weise rendern Sie für jeden Eingaberahmen nur einmal auf der Anzeigeoberfläche. Ein Beispiel finden Sie in den Klassen CameraSourcePreview und GraphicOverlay in der Schnellstart-Beispiel-App.
  • Wenn Sie die Camera2-API verwenden, erfassen Sie Bilder im Format ImageFormat.YUV_420_888 .

    Wenn Sie die ältere Kamera-API verwenden, erfassen Sie Bilder im ImageFormat.NV21 Format.