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 |
|
Gehostet mit Firebase |
|
Bevor Sie beginnen
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' }
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:
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).
Fügen Sie Ihr Modell und seine Metadatendateien in Ihr App-Paket ein:
- 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. - Erstellen Sie unter dem Assets-Ordner einen Unterordner für die Modelldateien.
- Kopieren Sie die Dateien
model.tflite
,dict.txt
undmanifest.json
in den Unterordner (alle drei Dateien müssen sich im selben Ordner befinden).
- Wenn Ihr Projekt keinen Ordner „Assets“ enthält, erstellen Sie einen, indem Sie mit der rechten Maustaste auf den Ordner
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.
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
undGraphicOverlay
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.