Es gibt zwei Möglichkeiten, die Bildkennzeichnung in benutzerdefinierte Modelle zu integrieren: Sie können die Pipeline in Ihre App einbinden oder eine nicht gebündelte Pipeline verwenden, die von Google Play-Diensten abhängt. Wenn Sie die entkoppelte Pipeline auswählen, ist Ihre App kleiner. Weitere Informationen finden Sie in der folgenden Tabelle.
| Gebündelt | Entbündelt | |
|---|---|---|
| Bibliotheksname | com.google.mlkit:image-labeling-custom | com.google.android.gms:play-services-mlkit-image-labeling-custom |
Implementierung | Die Pipeline wird zur Build-Zeit statisch mit Ihrer App verknüpft. | Die Pipeline wird dynamisch über die Google Play-Dienste heruntergeladen. |
| App-Größe | Die Größe hat sich um etwa 3,8 MB erhöht. | Die Größe nimmt um etwa 200 KB zu. |
| Initialisierungszeit | Die Pipeline ist sofort verfügbar. | Möglicherweise müssen Sie warten, bis die Pipeline heruntergeladen wurde, bevor Sie sie zum ersten Mal verwenden können. |
| API-Lebenszyklusphase | General Availability (GA) | Beta |
Es gibt zwei Möglichkeiten, ein benutzerdefiniertes Modell einzubinden: Sie können das Modell in den Asset-Ordner Ihrer App einfügen oder es dynamisch von Firebase herunterladen. In der folgenden Tabelle werden die beiden Optionen verglichen.
| Gebündeltes Modell | Gehostetes Modell |
|---|---|
| Das Modell ist Teil des APKs Ihrer App, was die Größe erhöht. | Das Modell ist nicht Teil Ihres APKs. Es wird gehostet, indem es in Cloud Storage hochgeladen wird. Wir empfehlen die Verwendung von Cloud Storage for Firebase. |
| Das Modell ist sofort verfügbar, auch wenn das Android-Gerät offline ist. | Ihre App muss Code enthalten, mit dem das Modell bei Bedarf heruntergeladen wird. |
| Kein Firebase-Projekt erforderlich | Erfordert ein Firebase-Projekt (bei Verwendung von Cloud Storage for Firebase). |
| Sie müssen Ihre App neu veröffentlichen, um das Modell zu aktualisieren | Modellupdates per Push übertragen, ohne die App neu zu veröffentlichen |
| Keine integrierten A/B-Tests | A/B-Tests mit Firebase Remote Config |
Jetzt ausprobieren
- Ein Beispiel für die Verwendung des gebündelten Modells finden Sie in der Vision-Schnellstart-App und ein Beispiel für die Verwendung des gehosteten Modells in der AutoML-Schnellstart-App.
Hinweis
In die Datei
build.gradle.ktsauf Projektebene muss das Maven-Repository von Google in die Abschnittebuildscriptundallprojectsaufgenommen werden.Fügen Sie die Abhängigkeiten für die ML Kit-Android-Bibliotheken der Gradle-Datei auf App-Ebene Ihres Moduls hinzu, die in der Regel
app/build.gradle.ktsist. Wählen Sie je nach Bedarf eine der folgenden Abhängigkeiten aus:So bündeln Sie die Pipeline mit Ihrer App:
dependencies { // ... // Use this dependency to bundle the pipeline with your app implementation("com.google.mlkit:image-labeling-custom:17.0.3") }Für die Verwendung der Pipeline in den 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-beta5") }Wenn Sie die Pipeline in Google Play-Diensten verwenden, können Sie Ihre App so konfigurieren, dass die Pipeline automatisch auf das Gerät heruntergeladen wird, nachdem Ihre App aus dem Play Store installiert wurde. Fügen Sie dazu der Datei
AndroidManifest.xmlIhrer 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 Pipeline-Verfügbarkeit auch explizit prüfen und den Download über die ModuleInstallClient API der Google Play-Dienste anfordern.
Wenn Sie keine Pipeline-Downloads während der Installation aktivieren oder einen expliziten Download anfordern, wird die Pipeline beim ersten Ausführen des Labelers heruntergeladen. Anfragen, die Sie vor Abschluss des Downloads stellen, führen zu keinen Ergebnissen.
Wenn Sie ein Modell mit Cloud Storage for Firebase herunterladen möchten, müssen Sie Firebase Ihrem Android-Projekt hinzufügen, falls Sie dies noch nicht getan haben. Dies ist nicht erforderlich, wenn Sie das Modell bündeln.
1. Modell laden
Sie können das Modell aus einer lokal gebündelten Quelle oder einer Remotequelle laden.
Lokale Modellquelle konfigurieren
So bündeln Sie das Modell mit Ihrer App:
Kopieren Sie die Modelldatei (normalerweise mit der Endung
.tfliteoder.lite) in den Ordnerassets/Ihrer App. Möglicherweise müssen Sie den Ordner zuerst erstellen. Klicken Sie dazu mit der rechten Maustaste auf den Ordnerapp/und dann auf Neu > Ordner > Assets-Ordner.Erstellen Sie ein
LocalModel-Objekt und geben Sie den Pfad zur Modelldatei an:Kotlin
val localModel = LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute path to model file) // or .setUri(URI to model file) .build()
Java
LocalModel localModel = new LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute path to model file) // or .setUri(URI to model file) .build();
Remote gehostete Modellquelle konfigurieren
Wenn Sie das remote gehostete Modell verwenden möchten, müssen Sie die Modelldatei mit Ihrer eigenen App-Logik in den lokalen Speicher des Geräts herunterladen und dann als lokales Modell laden. Wir empfehlen, Cloud Storage for Firebase zum Hosten eines Modells zu verwenden. Implementierungsdetails finden Sie im Migrationsleitfaden für Firebase ML zu Cloud Storage.
Bildlabeler konfigurieren
Nachdem Sie die Modellquellen konfiguriert haben, erstellen Sie ein ImageLabeler-Objekt aus einer der Quellen.
Folgende Optionen sind verfügbar:
| Optionen | |
|---|---|
confidenceThreshold
|
Mindestkonfidenzwert für erkannte Labels. Wenn nicht festgelegt, wird ein beliebiger von den Metadaten des Modells angegebener Klassifikatorschwellenwert verwendet. Wenn das Modell keine Metadaten enthält oder in den Metadaten kein Klassifikatorschwellenwert angegeben ist, wird der Standardschwellenwert 0,0 verwendet. |
maxResultCount
|
Maximale Anzahl der zurückzugebenden Labels. Wenn nicht festgelegt, wird der Standardwert 10 verwendet. |
Wenn Sie nur ein lokal gebündeltes Modell haben, erstellen Sie einfach einen Labeler 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 Modell haben, das auf einem Remote-Server gehostet wird, müssen Sie prüfen, ob es heruntergeladen wurde, bevor Sie es ausführen.
Sie müssen dies zwar nur vor dem Ausführen des Labelers bestätigen, aber wenn Sie sowohl ein remote gehostetes als auch ein lokal gebündeltes Modell haben, kann es sinnvoll sein, diese Prüfung beim Instanziieren des Bildlabelers durchzuführen: Erstellen Sie einen Labeler aus dem Remote-Modell, wenn es heruntergeladen wurde, und andernfalls aus dem lokalen Modell.
Kotlin
val modelFile = File(context.cacheDir, "my_downloaded_model.tflite") val model = if (modelFile.exists()) { // Use the downloaded model if available LocalModel.Builder().setAbsoluteFilePath(modelFile.absolutePath).build() } else { // Fall back to the bundled model LocalModel.Builder().setAssetFilePath("model.tflite").build() } val options = CustomImageLabelerOptions.Builder(model) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build() val labeler = ImageLabeling.getClient(options)
Java
File modelFile = new File(context.getCacheDir(), "my_downloaded_model.tflite"); LocalModel model; if (modelFile.exists()) { // Use the downloaded model if available model = new LocalModel.Builder().setAbsoluteFilePath(modelFile.getAbsolutePath()).build(); } else { // Fall back to the bundled model model = new LocalModel.Builder().setAssetFilePath("model.tflite").build(); } CustomImageLabelerOptions options = new CustomImageLabelerOptions.Builder(model) .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 Teil der Benutzeroberfläche ausgrauen oder ausblenden, bis Sie bestätigen, dass das Modell heruntergeladen wurde.
Kotlin
val localFile = File(context.cacheDir, "my_remote_model.tflite") if (localFile.exists()) { initializeLabeler(localFile) } else { showLoadingUI() val storage = Firebase.storage val modelRef = storage.getReferenceFromUrl("gs://YOUR_BUCKET/path/to/model.tflite") modelRef.getFile(localFile) .addOnSuccessListener { hideLoadingUI() initializeLabeler(localFile) } .addOnFailureListener { showErrorUI() } } private fun initializeLabeler(modelFile: File) { val localModel = LocalModel.Builder().setAbsoluteFilePath(modelFile.absolutePath).build() val options = CustomImageLabelerOptions.Builder(localModel).build() val labeler = ImageLabeling.getClient(options) enableMLFeatures(labeler) }
Java
File localFile = new File(context.getCacheDir(), "my_remote_model.tflite"); if (localFile.exists()) { initializeLabeler(localFile); } else { showLoadingUI(); FirebaseStorage storage = FirebaseStorage.getInstance(); StorageReference modelRef = storage.getReferenceFromUrl("gs://YOUR_BUCKET/path/to/model.tflite"); modelRef.getFile(localFile) .addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() { @Override public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { hideLoadingUI(); initializeLabeler(localFile); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception exception) { showErrorUI(); } }); } private void initializeLabeler(File modelFile) { LocalModel localModel = new LocalModel.Builder().setAbsoluteFilePath(modelFile.getAbsolutePath()).build(); CustomImageLabelerOptions options = new CustomImageLabelerOptions.Builder(localModel).build(); ImageLabeler labeler = ImageLabeling.getClient(options); enableMLFeatures(labeler); }
2. Eingabebild vorbereiten
Erstellen Sie dann für jedes Bild, das Sie labeln möchten, einInputImage-Objekt aus dem Bild. Die Bildkennzeichnung erfolgt am schnellsten, wenn Sie ein Bitmap verwenden. Wenn Sie die Camera2 API verwenden, wird ein YUV_420_888-media.Image empfohlen.
Sie können ein InputImage-Objekt aus verschiedenen Quellen erstellen. Die einzelnen Quellen werden unten beschrieben.
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, 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 Sie keine Kamerabibliothek verwenden, die Ihnen den Drehwinkel des Bildes liefert, können Sie ihn aus dem Drehwinkel 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 Drehwinkel an InputImage.fromMediaImage():
Kotlin
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 einem 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(); }
ByteBuffer oder ByteArray verwenden
Wenn Sie ein InputImage-Objekt aus einem ByteBuffer oder einem ByteArray erstellen möchten, berechnen Sie zuerst den Bildrotationsgrad wie zuvor für die media.Image-Eingabe beschrieben. Erstellen Sie dann das InputImage-Objekt mit dem Puffer oder Array sowie der Höhe, Breite, Farbcodierungsformat und dem 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 zusammen mit den Rotationsgraden 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 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 gelabelten Entitäten abrufen
Wenn die Bildkennzeichnung erfolgreich ist, wird eine Liste vonImageLabel-Objekten an den Erfolgs-Listener übergeben. Jedes ImageLabel-Objekt stellt etwas dar, das im Bild gekennzeichnet wurde. Sie können die Textbeschreibung (falls in den Metadaten der LiteRT-Modelldatei verfügbar), den Konfidenzwert und den Index für jedes Label 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 labeln möchten, sollten Sie die folgenden Richtlinien beachten, um die besten Frameraten zu erzielen:
- Wenn Sie die
Camera- odercamera2-API verwenden, drosseln Sie die Aufrufe des Bildlabelers. Wenn ein neuer Videoframes verfügbar wird, während der Bildlabeler ausgeführt wird, verwerfen Sie den Frame. Ein Beispiel finden Sie in der KlasseVisionProcessorBasein der Beispielanwendung für die Kurzanleitung. - Wenn Sie die
CameraXAPI verwenden, muss die Backpressure-Strategie auf den StandardwertImageAnalysis.STRATEGY_KEEP_ONLY_LATESTfestgelegt sein. So wird sichergestellt, dass jeweils nur ein Bild zur Analyse bereitgestellt wird. Wenn mehr Bilder erzeugt werden, während der Analyzer beschäftigt ist, werden sie automatisch verworfen und nicht für die Übermittlung in die Warteschlange gestellt. Sobald das analysierte Bild durch Aufrufen von ImageProxy.close() geschlossen wird, wird das nächste aktuelle Bild bereitgestellt. - Wenn Sie die Ausgabe des Bildkennzeichners verwenden, um Grafiken auf dem Eingabebild zu überlagern, rufen Sie zuerst das Ergebnis von ML Kit ab und rendern Sie dann das Bild und die Überlagerung in einem einzigen Schritt. Dadurch wird für jeden Eingabeframe nur einmal auf der Anzeigefläche gerendert. Ein Beispiel finden Sie in der Kurzanleitung in den Klassen
CameraSourcePreviewundGraphicOverlay. - Wenn Sie die Camera2 API verwenden, nehmen Sie Bilder im
ImageFormat.YUV_420_888-Format auf. Wenn Sie die ältere Camera API verwenden, nehmen Sie Bilder imImageFormat.NV21-Format auf.