Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Etykietuj obrazy za pomocą modelu przeszkolonego przez AutoML w systemie Android

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Po wytrenowaniu własnego modelu przy użyciu AutoML Vision Edge możesz użyć go w swojej aplikacji do oznaczania obrazów.

Istnieją dwa sposoby integracji modeli wytrenowanych z AutoML Vision Edge: Możesz połączyć model, umieszczając go w folderze zasobów aplikacji, lub możesz go dynamicznie pobrać z Firebase.

Opcje łączenia modeli
Dołączone do Twojej aplikacji
  • Model jest częścią pakietu APK Twojej aplikacji
  • Model jest dostępny od zaraz, nawet gdy urządzenie z Androidem jest offline
  • Nie potrzebujesz projektu Firebase
Hostowane w Firebase
  • Hostuj model, przesyłając go do systemów uczących się Firebase
  • Zmniejsza rozmiar APK
  • Model pobierany na żądanie
  • Wysyłaj aktualizacje modeli bez ponownego publikowania aplikacji
  • Łatwe testowanie A/B dzięki zdalnej konfiguracji Firebase
  • Wymaga projektu Firebase

Zanim zaczniesz

  1. Dodaj zależności dla bibliotek ML Kit dla systemu Android do pliku Gradle na poziomie aplikacji modułu, którym zwykle jest app/build.gradle :

    Aby połączyć model z aplikacją:

    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-custom:16.3.1'
    }
    

    Aby dynamicznie pobierać model z Firebase, dodaj zależność linkFirebase :

    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'
    }
    
  2. Jeśli chcesz pobrać model , upewnij się, że dodałeś Firebase do swojego projektu na Androida , jeśli jeszcze tego nie zrobiłeś. Nie jest to wymagane w przypadku łączenia modelu w pakiet.

1. Załaduj model

Skonfiguruj lokalne źródło modelu

Aby połączyć model z aplikacją:

  1. Wyodrębnij model i jego metadane z archiwum zip pobranego z konsoli Firebase. Zalecamy używanie pobranych plików bez modyfikacji (łącznie z nazwami plików).

  2. Dołącz swój model i jego pliki metadanych do pakietu aplikacji:

    1. Jeśli nie masz folderu zasobów w swoim projekcie, utwórz go, klikając prawym przyciskiem myszy app/ folder, a następnie klikając opcję Nowy > Folder > Folder zasobów .
    2. Utwórz podfolder w folderze zasobów, aby zawierać pliki modeli.
    3. Skopiuj pliki model.tflite , dict.txt i manifest.json do podfolderu (wszystkie trzy pliki muszą znajdować się w tym samym folderze).
  3. Dodaj następujące elementy do pliku build.gradle swojej aplikacji, aby upewnić się, że Gradle nie kompresuje pliku modelu podczas kompilowania aplikacji:

    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    

    Plik modelu zostanie dołączony do pakietu aplikacji i będzie dostępny dla ML Kit jako surowy zasób.

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

    Jawa

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

Skonfiguruj źródło modelu hostowane w Firebase

Aby użyć modelu hostowanego zdalnie, utwórz obiekt CustomRemoteModel , określając nazwę, którą przypisałeś modelowi podczas jego publikowania:

Jawa

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

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

Jawa

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

Wiele aplikacji rozpoczyna zadanie pobierania w swoim kodzie inicjującym, ale możesz to zrobić w dowolnym momencie, zanim będziesz musiał użyć modelu.

Stwórz etykietę obrazu ze swojego modelu

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

Jeśli masz tylko model wiązany lokalnie, po prostu utwórz etykietę z obiektu CustomImageLabelerOptions i skonfiguruj wymagany próg oceny ufności (zobacz Oceń model ):

Jawa

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)

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

Chociaż musisz to tylko potwierdzić przed uruchomieniem urządzenia etykietującego, jeśli masz zarówno model hostowany zdalnie, jak i model połączony lokalnie, może być sensowne wykonanie tego sprawdzenia podczas tworzenia wystąpienia urządzenia do etykietowania obrazu: utwórz etykietę z modelu zdalnego, jeśli został pobrany, az modelu lokalnego w inny sposób.

Jawa

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

Jeśli masz tylko model hostowany zdalnie, wyłącz funkcje związane z modelem — na przykład wyszarzanie lub ukrycie części interfejsu użytkownika — do momentu potwierdzenia, że ​​model został pobrany. Możesz to zrobić, dołączając detektor do metody download() menedżera modeli:

Jawa

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

Następnie dla każdego obrazu, który chcesz oznaczyć etykietą, utwórz z obrazu obiekt InputImage . Program do etykietowania obrazów działa najszybciej w przypadku korzystania z mapy Bitmap lub, w przypadku korzystania z interfejsu API camera2, w przypadku korzystania z nośnika YUV_420_888 media.Image , które są zalecane, jeśli jest to możliwe.

Możesz utworzyć InputImage z różnych źródeł, każde z nich zostało wyjaśnione poniżej.

Korzystanie z media.Image

Aby utworzyć obiekt InputImage z obiektu media.Image , na przykład podczas przechwytywania obrazu z aparatu urządzenia, przekaż obiekt media.Image i obrót obrazu do InputImage.fromMediaImage() .

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

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

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

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

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

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
}

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

Java

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

Kotlin+KTX

val image = InputImage.fromMediaImage(mediaImage, rotation)

Korzystanie z identyfikatora URI pliku

Aby utworzyć obiekt InputImage z identyfikatora URI pliku, przekaż kontekst aplikacji i identyfikator URI pliku do InputImage.fromFilePath() . Jest to przydatne, gdy używasz intencji ACTION_GET_CONTENT , aby skłonić użytkownika do wybrania obrazu z jego aplikacji galerii.

Java

InputImage image;
try {
    image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}

Kotlin+KTX

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Korzystanie z ByteBuffer lub ByteArray

Aby utworzyć obiekt InputImage z ByteBuffer lub ByteArray , najpierw oblicz stopień obrotu obrazu, jak opisano wcześniej dla danych wejściowych media.Image . Następnie utwórz obiekt InputImage z buforem lub tablicą wraz z wysokością, szerokością, formatem kodowania kolorów i stopniem obrotu obrazu:

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

Kotlin+KTX

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Korzystanie z Bitmap

Aby utworzyć obiekt InputImage z obiektu Bitmap , wykonaj następującą deklarację:

Java

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

Kotlin+KTX

val image = InputImage.fromBitmap(bitmap, 0)

Obraz jest reprezentowany przez obiekt Bitmap wraz ze stopniami obrotu.

3. Uruchom etykietę obrazu

Aby oznaczyć obiekty na obrazie, przekaż obiekt image do metody process() składnika ImageLabeler .

Jawa

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. Uzyskaj informacje o oznaczonych obiektach

Jeśli operacja oznaczania obrazu zakończy się pomyślnie, lista obiektów ImageLabel jest przekazywana do odbiornika sukcesu. Każdy obiekt ImageLabel reprezentuje coś, co zostało oznaczone na obrazie. Możesz uzyskać opis tekstowy każdej etykiety, stopień pewności dopasowania i indeks dopasowania. Na przykład:

Jawa

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
}

Wskazówki dotyczące poprawy wydajności w czasie rzeczywistym

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

  • Przepustnica wywołania drukarki etykiet obrazu. Jeśli nowa ramka wideo stanie się dostępna, gdy drukarka etykiet obrazu jest uruchomiona, upuść ramkę. Zobacz na przykład klasę VisionProcessorBase w przykładowej aplikacji szybkiego startu.
  • Jeśli używasz danych wyjściowych drukarki etykiet do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik, a następnie wyrenderuj obraz i nakładkę w jednym kroku. W ten sposób renderujesz na powierzchnię wyświetlania tylko raz dla każdej klatki wejściowej. Zobacz na przykład klasy CameraSourcePreview i GraphicOverlay w przykładowej aplikacji szybkiego startu.
  • W przypadku korzystania z interfejsu API Camera2 przechwytywanie obrazów w formacie ImageFormat.YUV_420_888 .

    Jeśli używasz starszego interfejsu Camera API, przechwytuj obrazy w formacie ImageFormat.NV21 .