Oznaczanie obrazów etykietami za pomocą modelu wytrenowanego w AutoML na Androidzie

Po wytrenowaniu własnego modelu za pomocą AutoML Vision Edge, możesz używać go w aplikacji do oznaczania obrazów etykietami. Modele wytrenowane w AutoML Vision Edge można zintegrować na 2 sposoby: możesz dołączyć model, umieszczając go w folderze zasobów aplikacji, lub pobrać go dynamicznie z Firebase.
Opcje dołączania modelu
Dołączony do aplikacji
  • Model jest częścią pliku APK aplikacji.
  • Model jest dostępny od razu, nawet gdy urządzenie z Androidem jest offline.
  • Nie musisz mieć projektu w Firebase.
Hostowany w Firebase
  • Model możesz hostować, przesyłając go do Firebase Machine Learning
  • Zmniejsza rozmiar pliku APK.
  • Model jest pobierany na żądanie.
  • Możesz przesyłać aktualizacje modelu bez ponownego publikowania aplikacji.
  • Łatwe testy A/B za pomocą Zdalnej konfiguracji Firebase.
  • Wymaga projektu w Firebase.

Wypróbuj

Zanim zaczniesz

1. W pliku build.gradle na poziomie projektu dodaj repozytorium Google Maven do sekcji buildscript i allprojects.

2. Dodaj zależności bibliotek ML Kit na Androida do pliku Gradle na poziomie modułu aplikacji który zwykle znajduje się w app/build.gradle: Aby dołączyć model do aplikacji:
    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-automl:16.2.1'
    }
    
Aby dynamicznie pobrać model z Firebase, dodaj linkFirebase zależność:
    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'
    }
    
3. Jeśli chcesz pobrać model, upewnij się, że dodasz Firebase do projektu aplikacji na Androida, jeśli jeszcze tego nie zrobisz. Nie jest to wymagane, gdy dołączasz model.

1. Wczytaj model

Skonfiguruj lokalne źródło modelu

Aby dołączyć model do aplikacji:

1. Wyodrębnij model i jego metadane z archiwum ZIP pobranego z konsoli Firebase. Zalecamy używanie plików w postaci, w jakiej zostały pobrane bez modyfikacji (w tym nazw plików).

2. Dołącz model i jego metadane do pakietu aplikacji:

a. Jeśli w projekcie nie masz folderu assets, utwórz go, klikając prawym przyciskiem myszy folder app/, a następnie Nowy > Folder > Folder zasobów.

b. Utwórz podfolder w folderze assets, w którym będą przechowywane pliki modelu plików.

c. Skopiuj pliki model.tflite, dict.txt, i manifest.json do podfolderu (wszystkie 3 pliki muszą znajdować się w tym samym folderze).

3. Aby Gradle nie kompresował pliku modelu podczas tworzenia aplikacji, dodaj do pliku build.gradle aplikacji ten ciąg:
    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    
Plik modelu zostanie dołączony do pakietu aplikacji i będzie dostępny dla ML Kit jako surowy zasób.

Uwaga: od wersji 4.1 wtyczki Androida do obsługi Gradle plik .tflite jest domyślnie dodawany do listy noCompress, więc powyższe kroki nie są już potrzebne.

4. Utwórz obiekt LocalModel, określając ścieżkę do pliku manifestu modelu:

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

Skonfiguruj źródło modelu hostowanego w Firebase

Aby używać modelu hostowanego zdalnie, utwórz obiekt RemoteModel, określając nazwę, którą przypisano modelowi podczas jego publikowania:

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

Następnie rozpocznij zadanie pobierania modelu, określając warunki, w jakich chcesz zezwolić na pobieranie. Jeśli model nie znajduje się na urządzeniu lub jest dostępna jego nowsza wersja, zadanie asynchronicznie pobierze model z Firebase:

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

Wiele aplikacji rozpoczyna zadanie pobierania w kodzie inicjującym, ale możesz to zrobić w dowolnym momencie przed użyciem modelu.

Utwórz etykietę obrazu na podstawie modelu

Po skonfigurowaniu źródeł modelu utwórz obiekt ImageLabeler na podstawie jednego z nich.

Jeśli masz tylko model dołączony lokalnie, utwórz etykietę na podstawie obiektu AutoMLImageLabelerLocalModel i skonfiguruj próg wskaźnika ufności który chcesz wymagać (patrz Ocena modelu):

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)

Jeśli masz model hostowany zdalnie, przed jego uruchomieniem musisz sprawdzić, czy został pobrany. Stan zadania pobierania modelu możesz sprawdzić za pomocą metody isModelDownloaded() menedżera modeli.

Chociaż musisz to potwierdzić tylko przed uruchomieniem etykiety, jeśli masz zarówno model hostowany zdalnie, jak i model dołączony lokalnie, warto przeprowadzić to sprawdzenie podczas tworzenia instancji etykiety obrazu: utwórz etykietę na podstawie modelu zdalnego, jeśli został pobrany, a w przeciwnym razie na podstawie modelu lokalnego.

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

Jeśli masz tylko model hostowany zdalnie, wyłącz funkcje związane z modelem – na przykład wyszarz lub ukryj część interfejsu – dopóki nie potwierdzisz, że model został pobrany. Możesz to zrobić, dołączając detektor do metody download() menedżera modeli:

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. Przygotuj obraz wejściowy

Następnie dla każdego obrazu, który chcesz oznaczyć etykietą, utwórz InputImage obiekt na podstawie obrazu. Etykieta obrazu działa najszybciej, gdy używasz obiektu Bitmap lub, jeśli używasz interfejsu API Camera2, obiektu media.Image w formacie YUV_420_888. Zalecamy używanie tych formatów, gdy jest to możliwe.

Obiekt InputImage możesz utworzyć z różnych źródeł. Każde z nich opisujemy poniżej.

Używanie obiektu media.Image

Aby utworzyć obiekt InputImage na podstawie obiektu media.Image, np. gdy robisz zdjęcie aparatem urządzenia, przekaż obiekt media.Image i obrót obrazu do metody InputImage.fromMediaImage().

Jeśli używasz biblioteki CameraX, klasy OnImageCapturedListener i ImageAnalysis.Analyzer obliczają wartość obrotu.

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

Jeśli nie używasz biblioteki aparatu, która podaje stopień obrotu obrazu, możesz go obliczyć na podstawie stopnia obrotu urządzenia i orientacji czujnika aparatu w urządzeniu:

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

Następnie przekaż obiekt media.Image i wartość stopnia obrotu do InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Używanie identyfikatora URI pliku

Aby utworzyć obiekt na podstawie identyfikatora URI pliku, przekaż kontekst aplikacji i identyfikator URI pliku do InputImage.fromFilePath().InputImage Jest to przydatne, gdy używasz intencji ACTION_GET_CONTENT, aby poprosić użytkownika o wybranie obrazu z aplikacji galerii.

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

Używanie obiektu ByteBuffer lub ByteArray

Aby utworzyć obiekt InputImage na podstawie obiektu ByteBuffer lub ByteArray, najpierw oblicz stopień obrotu obrazu zgodnie z opisem w przypadku danych wejściowych media.Image. Następnie utwórz obiekt InputImage z buforem lub tablicą oraz wysokością, szerokością, formatem kodowania kolorów i stopniem obrotu obrazu:

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

Używanie obiektu Bitmap

Aby utworzyć obiekt InputImage na podstawie obiektu Bitmap, użyj tej deklaracji:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

Obraz jest reprezentowany przez obiekt Bitmap wraz ze stopniami obrotu.

3. Uruchom etykietę obrazu

Aby oznaczyć obiekty na obrazie etykietą, przekaż obiekt image do metody process() etykiety 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. Uzyskaj informacje o obiektach oznaczonych etykietą

Jeśli operacja oznaczania obrazu etykietą się powiedzie, do odbiornika sukcesu zostanie przekazana lista ImageLabel obiektów. Każdy obiekt ImageLabel reprezentuje coś, co zostało oznaczone etykietą na obrazie. Możesz uzyskać tekstowy opis każdej etykiety, wskaźnik ufności dopasowania i indeks dopasowania. Na przykład:

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

Wskazówki dotyczące zwiększania skuteczności w czasie rzeczywistym

Jeśli chcesz oznaczać obrazy etykietami w aplikacji działającej w czasie rzeczywistym, postępuj zgodnie z tymi wskazówkami, aby uzyskać najlepszą liczbę klatek na sekundę:

  • Jeśli używasz interfejsu Camera lub camera2 API, ograniczaj wywołania etykiety obrazu. Jeśli podczas działania etykiety obrazu pojawi się nowa klatka wideo, pomiń ją. Przykład znajdziesz w klasie VisionProcessorBase w przykładowej aplikacji z krótkiego przewodnika.
  • Jeśli używasz interfejsu CameraX API, upewnij się, że strategia backpressure jest ustawiona na wartość domyślną ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Gwarantuje to, że do analizy będzie dostarczany tylko 1 obraz naraz. Jeśli podczas pracy analizatora zostanie wygenerowanych więcej obrazów, zostaną one automatycznie pominięte i nie zostaną dodane do kolejki dostarczania. Gdy analizowany obraz zostanie zamknięty przez wywołanie ImageProxy.close(), zostanie dostarczony następny najnowszy obraz.
  • Jeśli używasz danych wyjściowych etykiety obrazu do nakładania grafiki na obraz wejściowy, najpierw pobierz wynik z ML Kit, a następnie w jednym kroku wyrenderuj obraz i nałóż na niego grafikę. Dzięki temu renderowanie na powierzchni wyświetlacza odbywa się tylko raz dla każdej klatki wejściowej. Przykład znajdziesz w klasach CameraSourcePreview i GraphicOverlay w przykładowej aplikacji z krótkiego przewodnika.
  • Jeśli używasz interfejsu Camera2 API, rób zdjęcia w ImageFormat.YUV_420_888 formacie. Jeśli używasz starszego interfejsu Camera API, rób zdjęcia w ImageFormat.NV21 formacie.