Etiqueta imágenes con un modelo personalizado en Android

Puedes usar el Kit de AA para reconocer entidades en una imagen y etiquetarlas. Esta API admite una amplia variedad de modelos personalizados de clasificación de imágenes. Por favor, consulta Modelos personalizados con el Kit de AA para obtener orientación sobre requisitos de compatibilidad de modelos, dónde encontrar modelos previamente entrenados, y cómo entrenar tus propios modelos.

Hay dos formas de integrar el etiquetado de imágenes en los modelos personalizados: a través de la agrupación la canalización como parte de tu app o mediante una canalización sin agrupar que depende en los Servicios de Google Play. Si seleccionas la canalización sin empaquetar, se usará más pequeño. Consulta la siguiente tabla para obtener más detalles.

Red de Búsqueda y Red de DisplaySin agrupar
Nombre de la bibliotecacom.google.mlkit:image-labeling-customcom.google.android.gms:play-services-mlkit-image-labeling-custom
ImplementaciónLa canalización se vincula de forma estática a tu app en el tiempo de compilación.La canalización se descarga de forma dinámica a través de los Servicios de Google Play.
Tamaño de la appSe aumentó el tamaño de aproximadamente 3.8 MB.Aumento de tamaño aproximado de 200 KB.
Hora de inicializaciónLa canalización está disponible de inmediato.Es posible que debas esperar a que se descargue la canalización antes de usarla por primera vez.
Etapa del ciclo de vida de la APIDisponibilidad general (DG)Beta

Hay dos formas de integrar un modelo personalizado: empaquetar el modelo colocarla dentro de la carpeta de recursos de la app o descargarla de forma dinámica desde Firebase. En la siguiente tabla, se comparan estas dos opciones.

Modelo empaquetado Modelo alojado
El modelo es parte del APK de tu app, que aumenta su tamaño. El modelo no forma parte de tu APK. Se aloja subiendo a Aprendizaje automático de Firebase.
El modelo está disponible de inmediato, incluso cuando el dispositivo Android está sin conexión El modelo se descarga a pedido
No se necesita un proyecto de Firebase Requiere un proyecto de Firebase
Debes volver a publicar tu app para actualizar el modelo Envía actualizaciones del modelo sin volver a publicar tu app
Sin pruebas A/B integradas Pruebas A/B sencillas con Firebase Remote Config

Probar

Antes de comenzar

  1. En tu archivo build.gradle de nivel de proyecto, asegúrate de incluir lo siguiente: Puedes acceder al repositorio Maven de Google en tu buildscript y allprojects.

  2. Agrega las dependencias para las bibliotecas de Android del ML Kit al archivo archivo de Gradle a nivel de la app, que suele ser app/build.gradle. Elige uno de los las siguientes dependencias según tus necesidades:

    Para empaquetar la canalización con tu aplicación, sigue estos pasos:

    dependencies {
      // ...
      // Use this dependency to bundle the pipeline with your app
      implementation 'com.google.mlkit:image-labeling-custom:17.0.3'
    }
    

    Para usar la canalización en los Servicios de Google Play:

    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'
    }
    
  3. Si eliges usar la canalización en los Servicios de Google Play, puedes configura tu app para descargar automáticamente la canalización al dispositivo después tu app se instala desde Play Store. Para ello, agrega lo siguiente al archivo AndroidManifest.xml de tu app:

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

    También puedes verificar de forma explícita la disponibilidad de la canalización y solicitar su descarga a través de API de ModuleInstallClient de los Servicios de Google Play

    Si no habilitas las descargas de canalizaciones durante la instalación o solicitas una descarga explícita, la canalización se descarga la primera vez que ejecutas el etiquetador. Solicitudes que realizas antes de que se complete la descarga no producirá resultados.

  4. Agrega la dependencia linkFirebase si deseas descargar de forma dinámica un de Firebase:

    Para descargar un modelo de Firebase de forma dinámica, agrega linkFirebase. dependencia:

    dependencies {
      // ...
      // Image labeling feature with model downloaded from Firebase
      implementation 'com.google.mlkit:image-labeling-custom:17.0.3'
      // Or use the dynamically downloaded pipeline in Google Play Services
      // implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5'
      implementation 'com.google.mlkit:linkfirebase:17.0.0'
    }
    
  5. Si quieres descargar un modelo, asegúrate de agregar Firebase a tu proyecto de Android si aún no lo has hecho. Esto no es necesario cuando agrupas el modelo.

1. Carga el modelo

Configura una fuente de modelo local

Para empaquetar el modelo con tu app, haz lo siguiente:

  1. Copia el archivo de modelo (que generalmente termina en .tflite o .lite) en el archivo assets/ carpeta (Es posible que primero debas crear la carpeta haz clic con el botón derecho en la carpeta app/ y, luego, en Nuevo > Carpeta > Recursos).

  2. Luego, agrega lo siguiente al archivo build.gradle de tu app para asegurarte de que Gradle no comprime el archivo del modelo cuando se compila la app:

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

    El archivo del modelo se incluirá en el paquete de la app y estará disponible para el Kit de AA como activo sin procesar.

  3. Crea un objeto LocalModel y especifica la ruta de acceso al archivo de modelo:

    Kotlin

    val localModel = LocalModel.Builder()
            .setAssetFilePath("model.tflite")
            // or .setAbsoluteFilePath(absolute file path to model file)
            // or .setUri(URI to model file)
            .build()

    Java

    LocalModel localModel =
        new LocalModel.Builder()
            .setAssetFilePath("model.tflite")
            // or .setAbsoluteFilePath(absolute file path to model file)
            // or .setUri(URI to model file)
            .build();

Configura una fuente de modelo alojada en Firebase

Para usar el modelo alojado de forma remota, crea un objeto RemoteModel. Para ello, haz lo siguiente: FirebaseModelSource, que especifica el nombre que le asignaste al modelo cuando lo publicó:

Kotlin

// Specify the name you assigned in the Firebase console.
val remoteModel =
    CustomRemoteModel
        .Builder(FirebaseModelSource.Builder("your_model_name").build())
        .build()

Java

// Specify the name you assigned in the Firebase console.
CustomRemoteModel remoteModel =
    new CustomRemoteModel
        .Builder(new FirebaseModelSource.Builder("your_model_name").build())
        .build();

Luego, inicia la tarea de descarga del modelo y especifica las condiciones que deseas permitir la descarga. Si el modelo no está en el dispositivo o si es del modelo está disponible, la tarea descargará de forma asíncrona la de 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.
            }
        });

Muchas apps comienzan la tarea de descarga en su código de inicialización, pero tú puedes hacerlo en cualquier momento antes de usar el modelo.

Configura el etiquetador de imágenes

Después de configurar las fuentes de tu modelo, crea un objeto ImageLabeler a partir del una de ellas.

Están disponibles las siguientes opciones:

Opciones
confidenceThreshold

Puntuación de confianza mínima de las etiquetas detectadas. Si no se establece, de clasificación especificado por los metadatos del modelo. Si el modelo no contiene metadatos o si estos no especificas un umbral del clasificador, se establecerá un umbral predeterminado de 0.0 que se usan.

maxResultCount

Cantidad máxima de etiquetas que se mostrarán. Si no está establecido, el valor predeterminado de Se usará 10.

Si solo tienes un modelo empaquetado a nivel local, crea un etiquetador desde tu Objeto LocalModel:

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

Si tienes un modelo alojado de forma remota, deberás verificar que se haya descargado antes de ejecutarlo. Puedes verificar el estado de la descarga del modelo tarea con el método isModelDownloaded() del administrador de modelos.

Aunque solo tienes que confirmarlo antes de ejecutar el etiquetador, si tener un modelo alojado de forma remota y uno empaquetado localmente, podría hacer sentido realizar esta verificación cuando se crea una instancia del etiquetador de imágenes: crea un del etiquetador del modelo remoto, si se descargó, y del sistema de lo contrario.

Kotlin

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded ->
    val optionsBuilder =
        if (isDownloaded) {
            CustomImageLabelerOptions.Builder(remoteModel)
        } else {
            CustomImageLabelerOptions.Builder(localModel)
        }
    val options = optionsBuilder
                  .setConfidenceThreshold(0.5f)
                  .setMaxResultCount(5)
                  .build()
    val labeler = ImageLabeling.getClient(options)
}

Java

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener() {
            @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.5f)
                    .setMaxResultCount(5)
                    .build();
                ImageLabeler labeler = ImageLabeling.getClient(options);
            }
        });

Si solo tienes un modelo alojado de forma remota, debes inhabilitar las funciones (por ejemplo, ocultar o inhabilitar parte de la IU) hasta que confirmas que el modelo se descargó. Puedes hacerlo adjuntando un objeto de escucha al método download() del administrador de modelos:

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. Prepara la imagen de entrada

Luego, para cada imagen que quieras etiquetar, crea un InputImage. objeto de tu imagen. El etiquetador de imágenes se ejecuta de forma más rápida cuando usas un Bitmap o, si usas la API de camera2, un media.Image de YUV_420_888, que son se recomienda siempre que sea posible.

Puedes crear un InputImage objeto de diferentes fuentes, cada uno se explica a continuación.

Usa un media.Image

Para crear un elemento InputImage, sigue estos pasos: objeto de un objeto media.Image, como cuando capturas una imagen de una la cámara del dispositivo, pasa el objeto media.Image y el rotación a InputImage.fromMediaImage().

Si usas biblioteca de CameraX, los elementos OnImageCapturedListener y Las clases ImageAnalysis.Analyzer calculan el valor de rotación por ti.

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

Si no usas una biblioteca de cámaras que indique el grado de rotación de la imagen, calcularlo a partir del grado de rotación del dispositivo y la orientación de la cámara sensor en el dispositivo:

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

Luego, pasa el objeto media.Image y el valor de grado de rotación a InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Usa un URI de archivo

Para crear un elemento InputImage, sigue estos pasos: objeto de un URI de archivo, pasa el contexto de la app y el URI del archivo a InputImage.fromFilePath() Esto es útil cuando usa un intent ACTION_GET_CONTENT para solicitarle al usuario que seleccione una imagen de su app de galería.

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

Usa un objeto ByteBuffer o ByteArray

Para crear un elemento InputImage, sigue estos pasos: objeto de una ByteBuffer o ByteArray, primero calcula la imagen grado de rotación como se describió anteriormente para la entrada media.Image. Luego, crea el objeto InputImage con el búfer o array, junto con los atributos El alto, el ancho, el formato de codificación de color y el grado de rotación:

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

Usa un Bitmap

Para crear un elemento InputImage, sigue estos pasos: objeto a partir de un objeto Bitmap, realiza la siguiente declaración:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

La imagen se representa con un objeto Bitmap junto con los grados de rotación.

3. Ejecuta el etiquetador de imágenes

Para etiquetar objetos de una imagen, pasa el objeto image al de ImageLabeler process().

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. Obtén información sobre las entidades etiquetadas

Si la operación de etiquetado de imágenes se ejecuta correctamente, se mostrará una lista de ImageLabel. se pasan al objeto de escucha que detecta el resultado correcto. Cada objeto ImageLabel representa un elemento etiquetado en la imagen. Puedes obtener el texto de cada etiqueta (si está disponible en los metadatos del archivo de modelo de TensorFlow Lite), la puntuación de confianza y el índice. Por ejemplo:

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

Sugerencias para mejorar el rendimiento en tiempo real

Si quieres etiquetar imágenes en una aplicación en tiempo real, sigue estos pasos: pautas para lograr la mejor velocidad de fotogramas:

  • Si usas Camera o API de camera2, limitar las llamadas al etiquetador de imágenes Si un video nuevo marco esté disponible mientras se ejecuta el etiquetador de imágenes, suéltalo. Consulta la VisionProcessorBase en la app de muestra de inicio rápido para ver un ejemplo.
  • Si usas la API de CameraX, asegúrate de que la estrategia de contrapresión se haya establecido en su valor predeterminado ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST De esta forma, se garantiza que solo se entregará una imagen a la vez para su análisis. Si hay más imágenes que se producen cuando el analizador está ocupado, se eliminarán automáticamente y no se agregarán a la cola y la entrega de modelos. Una vez que la imagen que se está analizando se cierra con una llamada a ImageProxy.close(), se publicará la siguiente imagen más reciente.
  • Si usas la salida del etiquetador de imágenes para superponer gráficos la imagen de entrada, primero obtén el resultado del ML Kit y, luego, renderiza la imagen y superponerla en un solo paso. Se renderiza en la superficie de visualización. solo una vez para cada fotograma de entrada. Consulta la CameraSourcePreview y GraphicOverlay en la app de muestra de inicio rápido para ver un ejemplo.
  • Si usas la API de Camera2, captura imágenes en ImageFormat.YUV_420_888. Si usas la API de Camera, captura imágenes en ImageFormat.NV21.