1. Introducción
¿Qué es MediaPipe?
Las MediaPipe Solutions te permiten aplicar soluciones de aprendizaje automático (AA) en tus apps. Proporcionan un framework para que configures canalizaciones de procesamiento compiladas previamente que arrojan resultados inmediatos, pertinentes y útiles para los usuarios. Incluso puedes personalizar estas soluciones con MediaPipe Model Maker para actualizar los modelos predeterminados.
La clasificación de imágenes es una de las varias tareas de visión de AA que ofrece MediaPipe Solutions. MediaPipe Tasks está disponible para Android, iOS, Python (incluida la Raspberry Pi) y la Web.
En este codelab, comenzarás con una app para Android que te permitirá dibujar dígitos numéricos en la pantalla y, luego, agregarás una funcionalidad que clasifique esos dígitos dibujados como un solo valor del 0 al 9.
Qué aprenderás
- Cómo incorporar una tarea de clasificación de imágenes en una app para Android con MediaPipe Tasks.
Requisitos
- Una versión instalada de Android Studio (este codelab se escribió y probó con Android Studio Giraffe).
- Un dispositivo o emulador de Android para ejecutar la app
- Conocimientos básicos sobre el desarrollo de Android (no es "Hello World", pero no está muy lejos).
2. Agrega MediaPipe Tasks a la app para Android
Descarga la app de partida para Android
Este codelab comenzará con un ejemplo prediseñado que te permitirá dibujar en la pantalla. Puedes encontrar esa app de inicio en el repositorio oficial de Samples de MediaPipe aquí. Para clonar el repositorio o descargar el archivo ZIP, haz clic en Code > Download ZIP.
Importa la app a Android Studio
- Abre Android Studio.
- En la pantalla Welcome to Android Studio, selecciona Open en la esquina superior derecha.
- Navega a la ubicación donde clonaste o descargaste el repositorio y abre el directorio codelabs/digitclassifier/android/start.
- Para verificar que todo se haya abierto correctamente, haz clic en la flecha verde de ejecutar (
) en la parte superior derecha de Android Studio.
- Deberías ver que la app se abre con una pantalla negra en la que puedes dibujar, así como un botón Borrar para restablecerla. Si bien puedes dibujar en esa pantalla, no hace mucho más, así que comenzaremos a solucionarlo ahora.
Modelo
Cuando ejecutes la app por primera vez, es posible que notes que se descarga y almacena un archivo llamado mnist.tflite en el directorio de recursos de la app. Para simplificar, ya tomamos un modelo conocido, MNIST, que clasifica dígitos, y lo agregamos a la app con la secuencia de comandos download_models.gradle en el proyecto. Si decides entrenar tu propio modelo personalizado, como uno para letras escritas a mano, debes quitar el archivo download_models.gradle, borrar la referencia a él en el archivo build.gradle de nivel de la app y cambiar el nombre del modelo más adelante en el código (específicamente, en el archivo DigitClassifierHelper.kt).
Cómo actualizar build.gradle
Antes de comenzar a usar MediaPipe Tasks, debes importar la biblioteca.
- Abre el archivo build.gradle ubicado en el módulo app y desplázate hacia abajo hasta el bloque dependencies.
- Deberías ver un comentario en la parte inferior de ese bloque que dice // STEP 1 Dependency Import.
- Reemplaza esa línea por la siguiente implementación:
implementation("com.google.mediapipe:tasks-vision:latest.release")
- Haz clic en el botón Sync Now que aparece en el banner de la parte superior de Android Studio para descargar esta dependencia.
3. Crea un ayudante de clasificador de dígitos de MediaPipe Tasks
En el siguiente paso, completarás una clase que hará el trabajo pesado de tu clasificación de aprendizaje automático. Abre DigitClassifierHelper.kt y comencemos.
- Busca el comentario en la parte superior de la clase que dice // PASO 2 Crear objeto de escucha.
- Reemplaza esa línea por el siguiente código. Esto creará un objeto de escucha que se usará para pasar los resultados de la clase DigitClassifierHelper a cualquier lugar donde se escuchen esos resultados (en este caso, será tu clase DigitCanvasFragment, pero llegaremos allí pronto).
// STEP 2 Create listener
interface DigitClassifierListener {
fun onError(error: String)
fun onResults(
results: ImageClassifierResult,
inferenceTime: Long
)
}
- También deberás aceptar un DigitClassifierListener como parámetro opcional para la clase:
class DigitClassifierHelper(
val context: Context,
val digitClassifierListener: DigitClassifierListener?
) {
- Desplázate hasta la línea que dice // STEP 3 define classifier y agrega la siguiente línea para crear un marcador de posición para el ImageClassifier que se usará para esta app:
// PASO 3: Define el clasificador
private var digitClassifier: ImageClassifier? = null
- Agrega la siguiente función donde veas el comentario // STEP 4 set up classifier:
// STEP 4 set up classifier
private fun setupDigitClassifier() {
val baseOptionsBuilder = BaseOptions.builder()
.setModelAssetPath("mnist.tflite")
// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
.setRunningMode(RunningMode.IMAGE)
.setBaseOptions(baseOptionsBuilder.build())
try {
digitClassifier =
ImageClassifier.createFromOptions(
context,
optionsBuilder.build()
)
} catch (e: IllegalStateException) {
digitClassifierListener?.onError(
"Image classifier failed to initialize. See error logs for " +
"details"
)
Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}
}
En la sección anterior, ocurren varios procesos, así que analicemos partes más pequeñas para comprender realmente lo que sucede.
val baseOptionsBuilder = BaseOptions.builder()
.setModelAssetPath("mnist.tflite")
// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
.setRunningMode(RunningMode.IMAGE)
.setBaseOptions(baseOptionsBuilder.build())
Este bloque definirá los parámetros que usa ImageClassifier. Esto incluye el modelo almacenado en tu app (mnist.tflite) en BaseOptions y el RunningMode en ImageClassifierOptions, que en este caso es IMAGE, pero VIDEO y LIVE_STREAM son opciones disponibles adicionales. Otros parámetros disponibles son MaxResults, que limita el modelo para que muestre una cantidad máxima de resultados, y ScoreThreshold, que establece la confianza mínima que el modelo debe tener en un resultado antes de mostrarlo.
try {
digitClassifier =
ImageClassifier.createFromOptions(
context,
optionsBuilder.build()
)
} catch (e: IllegalStateException) {
digitClassifierListener?.onError(
"Image classifier failed to initialize. See error logs for " +
"details"
)
Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}
Después de crear tus opciones de configuración, puedes pasar un contexto y las opciones para crear tu nuevo ImageClassifier. Si algo sale mal con ese proceso de inicialización, se mostrará un error a través de tu DigitClassifierListener.
- Como queremos inicializar ImageClassifier antes de usarlo, puedes agregar un bloque de inicialización para llamar a setupDigitClassifier().
init {
setupDigitClassifier()
}
- Por último, desplázate hacia abajo hasta el comentario que dice // STEP 5 create classify function y agrega el siguiente código. Esta función aceptará un mapa de bits, que en este caso es el dígito dibujado, lo convertirá en un objeto de imagen de MediaPipe (MPImage) y, luego, clasificará esa imagen con ImageClassifier, además de registrar cuánto tiempo tarda la inferencia, antes de mostrar esos resultados a través de DigitClassifierListener.
// STEP 5 create classify function
fun classify(image: Bitmap) {
if (digitClassifier == null) {
setupDigitClassifier()
}
// Convert the input Bitmap object to an MPImage object to run inference.
// Rotating shouldn't be necessary because the text is being extracted from
// a view that should always be correctly positioned.
val mpImage = BitmapImageBuilder(image).build()
// Inference time is the difference between the system time at the start and finish of the
// process
val startTime = SystemClock.uptimeMillis()
// Run image classification using MediaPipe Image Classifier API
digitClassifier?.classify(mpImage)?.also { classificationResults ->
val inferenceTimeMs = SystemClock.uptimeMillis() - startTime
digitClassifierListener?.onResults(classificationResults, inferenceTimeMs)
}
}
Eso es todo para el archivo de ayuda. En la siguiente sección, completarás los pasos finales para comenzar a clasificar los números que dibujaste.
4. Ejecuta inferencias con MediaPipe Tasks
Para comenzar esta sección, abre la clase DigitCanvasFragment en Android Studio, que es donde se realizará todo el trabajo.
- En la parte inferior de este archivo, deberías ver un comentario que diga // PASO 6 Configura el objeto de escucha. Aquí agregarás las funciones onResults() y onError() asociadas con el objeto de escucha.
// STEP 6 Set up listener
override fun onError(error: String) {
activity?.runOnUiThread {
Toast.makeText(requireActivity(), error, Toast.LENGTH_SHORT).show()
fragmentDigitCanvasBinding.tvResults.text = ""
}
}
override fun onResults(
results: ImageClassifierResult,
inferenceTime: Long
) {
activity?.runOnUiThread {
fragmentDigitCanvasBinding.tvResults.text = results
.classificationResult()
.classifications().get(0)
.categories().get(0)
.categoryName()
fragmentDigitCanvasBinding.tvInferenceTime.text = requireActivity()
.getString(R.string.inference_time, inferenceTime.toString())
}
}
onResults() es particularmente importante, ya que mostrará los resultados recibidos de ImageClassifier. Como esta devolución de llamada se activa desde un subproceso en segundo plano, también deberás ejecutar las actualizaciones de la IU en el subproceso de IU de Android.
- A medida que agregues funciones nuevas desde una interfaz en el paso anterior, también deberás agregar la declaración de implementación en la parte superior de la clase.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
- Cerca de la parte superior de la clase, deberías ver un comentario que diga // STEP 7a Initialize classifier. Aquí es donde colocarás la declaración de DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
- Si avanzas al // PASO 7b Inicializa el clasificador, puedes inicializar digitClassifierHelper dentro de la función onViewCreated().
// STEP 7b Initialize classifier
// Initialize the digit classifier helper, which does all of the
// ML work. This uses the default values for the classifier.
digitClassifierHelper = DigitClassifierHelper(
context = requireContext(), digitClassifierListener = this
)
- Para los últimos pasos, busca el comentario // STEP 8a*: classify* y agrega el siguiente código para llamar a una función nueva que agregarás en un momento. Este bloque de código activará la clasificación cuando levantes el dedo del área de dibujo en la app.
// STEP 8a: classify
classifyDrawing()
- Por último, busca el comentario // STEP 8b classify para agregar la nueva función classifyDrawing(). Esto extraerá un mapa de bits del lienzo y, luego, lo pasará a DigitClassifierHelper para realizar la clasificación y recibir los resultados en la función de interfaz onResults().
// STEP 8b classify
private fun classifyDrawing() {
val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
digitClassifierHelper.classify(bitmap)
}
5. Implementa y prueba la app
Después de todo eso, deberías tener una app que funcione y que pueda clasificar los dígitos dibujados en la pantalla. Continúa y, luego, implementa la app en Android Emulator o en un dispositivo Android físico para probarla.
- Haz clic en Run (
) en la barra de herramientas de Android Studio para ejecutar la app.
- Dibuja cualquier dígito en el panel de dibujo y comprueba si la app puede reconocerlo. Debería mostrar el dígito que el modelo cree que se dibujó, así como el tiempo que tardó en predecir ese dígito.
6. ¡Felicitaciones!
¡Lo lograste! En este codelab, aprendiste a agregar la clasificación de imágenes a una app para Android y, específicamente, a clasificar dígitos dibujados a mano con el modelo MNIST.
Próximos pasos
- Ahora que puedes clasificar dígitos, te recomendamos que entrenes tu propio modelo para clasificar letras dibujadas, animales o una infinidad de otros elementos. Puedes encontrar la documentación para entrenar un nuevo modelo de clasificación de imágenes con MediaPipe Model Maker en la página developers.google.com/mediapipe.
- Obtén información sobre las otras MediaPipe Tasks que están disponibles para Android, como la detección de puntos de referencia de rostros, el reconocimiento de gestos y la clasificación de audio.
¡Esperamos ver todas las cosas geniales que crearás!