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 |
|
Mit Firebase gehostet |
|
Jetzt ausprobieren
- In der Beispielanwendung sehen Sie ein Beispiel für die Verwendung dieser API.
Hinweis
1. In die Dateibuild.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' }
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' }
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" } }
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:
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();
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:
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 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:
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 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):
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 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.
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 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:
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 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.
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 Drehwinkel des Bildes angibt, können Sie ihn anhand des Drehwinkels 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; }
Übergeben Sie dann das media.Image
-Objekt und den Wert für den Drehungsgrad an InputImage.fromMediaImage()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
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.
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 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 Drehwinkel des Bilds:
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
Wenn Sie ein InputImage
-Objekt aus einem Bitmap
-Objekt erstellen möchten, machen Sie die folgende Deklaration:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
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 dasimage
-Objekt an die process()
-Methode des ImageLabeler
-Objekts.
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 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:
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 taggen möchten, beachten Sie die folgenden Richtlinien, um die beste Framerate zu erzielen:
- Wenn Sie die
Camera
- odercamera2
-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 KlasseVisionProcessorBase
in der Beispiel-App für die Schnellstartanleitung. - Wenn Sie die
CameraX
API verwenden, muss die Backpressure-Strategie auf den StandardwertImageAnalysis.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, holen Sie zuerst das Ergebnis aus ML Kit ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. Dieser wird nur einmal pro Eingabeframe auf der Anzeigeoberfläche gerendert. Eines dieser Beispiele finden Sie in der Beispiel-App für den Schnellstart in den Klassen
CameraSourcePreview
undGraphicOverlay
. - 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 imImageFormat.NV21
-Format auf.