Nachdem Sie Ihr eigenes Modell mit AutoML Vision Edge trainiert haben, können Sie es in Ihrer App verwenden, um Bilder mit Labels zu versehen.
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 Modellbündelung | |
---|---|
In Ihrer App gebündelt |
|
Mit Firebase gehostet |
|
Hinweis
Fügen Sie die Abhängigkeiten für die ML Kit-Android-Bibliotheken in die Gradle-Datei auf App-Ebene Ihres Moduls ein, die normalerweise
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-custom:16.3.1' }
Fügen Sie die Abhängigkeit
linkFirebase
hinzu, um ein Modell dynamisch aus Firebase herunterzuladen: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' }
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:
Extrahieren Sie das Modell und seine Metadaten aus dem ZIP-Archiv, das Sie aus der Firebase-Konsole heruntergeladen haben. Wir empfehlen, die Dateien unverändert (einschließlich der Dateinamen) zu verwenden.
Fügen Sie Ihr Modell und die zugehörigen Metadatendateien in Ihr App-Paket ein:
- 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. - Erstellen Sie einen Unterordner im Ordner „Assets“, der die Modelldateien enthält.
- Kopieren Sie die Dateien
model.tflite
,dict.txt
undmanifest.json
in den Unterordner. Alle drei Dateien müssen sich im selben Ordner befinden.
- Wenn Sie in Ihrem Projekt noch keinen Assets-Ordner haben, erstellen Sie einen. Klicken Sie dazu mit der rechten Maustaste auf den Ordner
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 wird in das App-Paket aufgenommen und ML Kit als Roh-Asset zur Verfügung gestellt.
Erstellen Sie das Objekt
LocalModel
und geben Sie den Pfad zur Manifestdatei des Modells 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()
Von Firebase gehostete Modellquelle konfigurieren
Wenn Sie das remote gehostete Modell verwenden möchten, erstellen Sie ein CustomRemoteModel
-Objekt und geben Sie den Namen an, den Sie dem Modell bei der Veröffentlichung 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 der Download zulässig sein soll. Wenn das Modell nicht auf dem Gerät installiert ist oder eine neuere Version des Modells verfügbar ist, wird das Modell durch die Aufgabe asynchron von Firebase heruntergeladen:
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 Downloadaufgabe in ihrem Initialisierungscode, Sie können dies aber auch jederzeit tun, bevor Sie das Modell verwenden müssen.
Labelersteller für Bilder 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 CustomImageLabelerOptions
-Objekt und konfigurieren Sie den gewünschten Grenzwert für den Konfidenzwert (siehe Modell bewerten):
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 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 Aufgabe zum Modelldownload mit der Methode isModelDownloaded()
des Modellmanagers prüfen.
Sie müssen dies zwar nur vor dem Ausführen des Labels bestätigen, wenn Sie aber 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.
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 ein extern gehostetes Modell haben, sollten Sie die modellbezogenen Funktionen deaktivieren, z. B. einen Teil der Benutzeroberfläche ausblenden oder ausblenden, bis Sie bestätigt haben, dass das Modell heruntergeladen wurde. Dazu fügen Sie der download()
-Methode des Modellmanagers einen Listener hinzu:
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. 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 eine InputImage
aus verschiedenen Quellen erstellen, die unten erläutert werden.
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 Bibliothek
CameraX 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 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+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 Wert für den Rotationsgrad an InputImage.fromMediaImage()
:
Kotlin+KTX
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Datei-URI verwenden
Wenn Sie ein InputImage
-Objekt aus einem Datei-URI erstellen möchten, übergeben Sie 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+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(); }
ByteBuffer
oder ByteArray
verwenden
Um ein InputImage
-Objekt aus einem ByteBuffer
- oder ByteArray
-Element zu erstellen, berechnen Sie zuerst den Bilddrehungsgrad, wie zuvor für die media.Image
-Eingabe beschrieben.
Erstellen Sie dann das InputImage
-Objekt mit dem Zwischenspeicher oder Array, zusammen mit Höhe, Breite, Farbcodierungsformat und Drehungsgrad:
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 );
Mit einem Bitmap
Um ein InputImage
-Objekt aus einem Bitmap
-Objekt zu erstellen, deklariere Folgendes:
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 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.
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. 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:
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 taggen möchten, beachten Sie die folgenden Richtlinien, um die beste Framerate zu erzielen:
- Aufrufe an den Bildbeschrifter drosseln 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 Ausgabe des Bildes-Labelers verwenden, um Grafiken auf das Eingabebild zu legen, rufen Sie zuerst das Ergebnis ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. So wird für jeden Eingabeframe nur einmal auf die Displayoberflä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 du die ältere Camera API verwendest, nimm Bilder im
ImageFormat.NV21
-Format auf.