Puedes usar el Kit de AA para reconocer texto en imágenes o videos, como el de una señal de tránsito. Las características principales de esta función son las siguientes:
API de reconocimiento de texto | |
---|---|
Descripción | Reconoce texto con alfabeto latino en imágenes o videos. |
Nombre de la biblioteca | com.google.android.gms:play-services-mlkit-text-recognition |
Implementación | La biblioteca se descarga de manera dinámica a través de los servicios de Google Play. |
Impacto en el tamaño de la app | 260KB |
Hora de inicialización | Es posible que debas esperar a que se descargue la biblioteca para usarla por primera vez. |
Rendimiento | En tiempo real en la mayoría de los dispositivos. |
Probarlo
- Juega con la app de muestra para ver un ejemplo de uso de esta API.
- Prueba tú mismo el código con el codelab.
Antes de comenzar
- En el archivo
build.gradle
de nivel de proyecto, asegúrate de incluir el repositorio Maven de Google en las seccionesbuildscript
yallprojects
. - Agrega las dependencias para las bibliotecas de Android del Kit de AA al archivo Gradle a nivel de la app del módulo, que suele ser
app/build.gradle
:dependencies { // ... implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2' }
-
Opcional, pero recomendado: Puedes configurar tu app para que descargue automáticamente el modelo de AA en el dispositivo después de instalar la app desde Play Store. Para ello, agrega la siguiente declaración en el archivo
AndroidManifest.xml
de tu app:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="ocr" /> <!-- To use multiple models: android:value="ocr,model2,model3" --> </application>
También puedes verificar de manera explícita la disponibilidad del modelo y solicitar su descarga a través de la API de ModuleInstallClient de los Servicios de Google Play.
Si no habilitas las descargas de modelos en el momento de la instalación, el modelo se descargará la primera vez que ejecutes el detector en el dispositivo. Las solicitudes que realices antes de que se complete la descarga no generarán resultados.
1. Crea una instancia de TextRecognizer
.
Crea una instancia de TextRecognizer
:
Kotlin
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
Java
TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);
2. Prepara la imagen de entrada
Para reconocer texto en una imagen, crea un objeto InputImage
desde Bitmap
, media.Image
, ByteBuffer
, un arreglo de bytes o un archivo ubicado en el dispositivo. Luego, pasa el objeto InputImage
al método processImage
de TextRecognizer
.
Puedes crear un objeto InputImage
a partir de diferentes fuentes, que se explican a continuación.
Usa un media.Image
Para crear un objeto InputImage
a partir de un objeto media.Image
, como cuando capturas una imagen desde la cámara de un dispositivo, pasa el objeto media.Image
y la rotación de la imagen a InputImage.fromMediaImage()
.
Si usas la biblioteca
CameraX, las clases OnImageCapturedListener
y 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 te proporcione el grado de rotación de la imagen, puedes calcularla a partir del grado de rotación del dispositivo y la orientación del sensor de la cámara 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 objeto InputImage
a partir de un URI de archivo, pasa el contexto de la app y el URI de archivo a InputImage.fromFilePath()
. Esto es útil cuando usas 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 ByteBuffer
o ByteArray
Para crear un objeto InputImage
a partir de ByteBuffer
o ByteArray
, primero calcula el grado de rotación de la imagen como se describió anteriormente para la entrada media.Image
.
Luego, crea el objeto InputImage
con el búfer o el array, junto con la altura, el ancho, el formato de codificación de color y el grado de rotación de la imagen:
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 objeto InputImage
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. Procesa la imagen
Pasa la imagen al método process
:
Kotlin
val result = recognizer.process(image) .addOnSuccessListener { visionText -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Java
Task<Text> result = recognizer.process(image) .addOnSuccessListener(new OnSuccessListener<Text>() { @Override public void onSuccess(Text visionText) { // Task completed successfully // ... } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. Extrae texto de bloques de texto reconocido
Si la operación de reconocimiento de texto se ejecuta correctamente, se pasará un objeto Text
al objeto de escucha que detecta el resultado correcto. Un objeto Text
contiene el texto completo reconocido en la imagen, y cero o más objetos TextBlock
.
Cada TextBlock
representa un bloque rectangular de texto que contiene cero o más objetos Line
. Cada objeto Line
representa una línea de texto que contiene cero o más objetos Element
. Cada objeto Element
representa una palabra o una entidad similar a una palabra, que contiene cero o más objetos Symbol
. Cada objeto Symbol
representa un carácter, un dígito o una entidad similar a una palabra.
Para cada objeto TextBlock
, Line
, Element
y Symbol
, puedes obtener el texto reconocido en la región, las coordenadas que limitan la región y muchos otros atributos, como la información de rotación, la puntuación de confianza, etcétera.
Por ejemplo:
Kotlin
val resultText = result.text for (block in result.textBlocks) { val blockText = block.text val blockCornerPoints = block.cornerPoints val blockFrame = block.boundingBox for (line in block.lines) { val lineText = line.text val lineCornerPoints = line.cornerPoints val lineFrame = line.boundingBox for (element in line.elements) { val elementText = element.text val elementCornerPoints = element.cornerPoints val elementFrame = element.boundingBox } } }
Java
String resultText = result.getText(); for (Text.TextBlock block : result.getTextBlocks()) { String blockText = block.getText(); Point[] blockCornerPoints = block.getCornerPoints(); Rect blockFrame = block.getBoundingBox(); for (Text.Line line : block.getLines()) { String lineText = line.getText(); Point[] lineCornerPoints = line.getCornerPoints(); Rect lineFrame = line.getBoundingBox(); for (Text.Element element : line.getElements()) { String elementText = element.getText(); Point[] elementCornerPoints = element.getCornerPoints(); Rect elementFrame = element.getBoundingBox(); for (Text.Symbol symbol : element.getSymbols()) { String symbolText = symbol.getText(); Point[] symbolCornerPoints = symbol.getCornerPoints(); Rect symbolFrame = symbol.getBoundingBox(); } } } }
Lineamientos para ingresar imágenes
-
Para que el Kit de AA reconozca con precisión el texto, las imágenes de entrada deben contener texto que esté representado por suficientes datos de píxeles. Lo ideal es que cada carácter sea de al menos 16x16 píxeles. Por lo general, no existe un beneficio de precisión para los caracteres que superen los 24 × 24 píxeles.
Por ejemplo, una imagen de 640 × 480 podría funcionar bien para escanear una tarjeta de presentación que ocupa todo el ancho de la imagen. Para escanear un documento impreso en papel de tamaño carta, es posible que se requiera una imagen de 720 × 1280 píxeles.
-
Un enfoque de imagen deficiente puede afectar la exactitud del reconocimiento de texto. Si no obtienes resultados aceptables, intenta pedirle al usuario que vuelva a capturar la imagen.
-
Si reconoces texto en una aplicación en tiempo real, debes considerar las dimensiones generales de las imágenes de entrada. Las imágenes más pequeñas se pueden procesar más rápido. Para reducir la latencia, asegúrate de que el texto ocupe la mayor parte de la imagen y captura las imágenes en resoluciones más bajas (ten en cuenta los requisitos de exactitud mencionados anteriormente). Si quieres obtener más información, consulta Sugerencias para mejorar el rendimiento.
Sugerencias para mejorar el rendimiento
- Si usas las API de
Camera
ocamera2
, limita las llamadas al detector. Si hay un fotograma de video nuevo disponible mientras se ejecuta el detector, descarta el fotograma. Consulta la claseVisionProcessorBase
de 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 tenga el valor predeterminadoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Esto garantiza que solo se envíe una imagen a la vez para su análisis. Si se producen más imágenes cuando el analizador está ocupado, se descartarán automáticamente y no se agregarán a la cola para entregarlas. Una vez que la imagen que se analiza se cierra mediante una llamada a ImageProxy.close(), se entregará la siguiente imagen más reciente. - Si usas la salida del detector para superponer gráficos en la imagen de entrada, primero obtén el resultado del Kit de AA y, luego, procesa la imagen y la superposición en un solo paso. Esto se renderiza en la superficie de visualización solo una vez por cada marco de entrada. Consulta las clases
CameraSourcePreview
yGraphicOverlay
en la app de muestra de inicio rápido para ver un ejemplo. - Si usas la API de Camera2, captura imágenes en formato
ImageFormat.YUV_420_888
. Si usas la API de Camera más antigua, captura imágenes en formatoImageFormat.NV21
. - Considere la opción de capturar imágenes con una resolución más baja. Sin embargo, también ten en cuenta los requisitos de dimensiones de imágenes de esta API.