É possível usar o Kit de ML para reconhecer texto em imagens ou vídeos, como o texto de uma placa de rua. As principais características desse recurso são:
| Recurso | Sem pacote | Agrupadas |
|---|---|---|
| Nome da biblioteca | com.google.android.gms:play-services-mlkit-text-recognition
com.google.android.gms:play-services-mlkit-text-recognition-chinese com.google.android.gms:play-services-mlkit-text-recognition-devanagari com.google.android.gms:play-services-mlkit-text-recognition-japanese com.google.android.gms:play-services-mlkit-text-recognition-korean |
com.google.mlkit:text-recognition
com.google.mlkit:text-recognition-chinese com.google.mlkit:text-recognition-devanagari com.google.mlkit:text-recognition-japanese com.google.mlkit:text-recognition-korean |
| Implementação | O modelo é baixado dinamicamente pelo Google Play Services. | O modelo é vinculado estaticamente ao app no momento da criação. |
| Tamanho do app | Aumento de tamanho de cerca de 260 KB por arquitetura de script. | Aumento de tamanho de cerca de 4 MB por script por arquitetura. |
| Tempo de inicialização | Talvez seja necessário aguardar o download do modelo antes do primeiro uso. | O modelo fica disponível imediatamente. |
| Desempenho | Em tempo real na maioria dos dispositivos para a biblioteca de scripts latinos, mais lento para outros. | Em tempo real na maioria dos dispositivos para a biblioteca de scripts latinos, mais lento para outros. |
Faça um teste
- Teste o app de exemplo para ver um exemplo de uso dessa API.
- Teste o código com o codelab.
Antes de começar
- No arquivo
build.gradleno nível do projeto, inclua o repositório Maven do Google nas seçõesbuildscripteallprojects. Adicione as dependências das bibliotecas do Android do Kit de ML ao arquivo Gradle no nível do app do módulo, que geralmente é
app/build.gradle:Para agrupar o modelo e o app:
dependencies { // To recognize Latin script implementation 'com.google.mlkit:text-recognition:16.0.1' // To recognize Chinese script implementation 'com.google.mlkit:text-recognition-chinese:16.0.1' // To recognize Devanagari script implementation 'com.google.mlkit:text-recognition-devanagari:16.0.1' // To recognize Japanese script implementation 'com.google.mlkit:text-recognition-japanese:16.0.1' // To recognize Korean script implementation 'com.google.mlkit:text-recognition-korean:16.0.1' }Para usar o modelo no Google Play Services:
dependencies { // To recognize Latin script implementation 'com.google.android.gms:play-services-mlkit-text-recognition:19.0.1' // To recognize Chinese script implementation 'com.google.android.gms:play-services-mlkit-text-recognition-chinese:16.0.1' // To recognize Devanagari script implementation 'com.google.android.gms:play-services-mlkit-text-recognition-devanagari:16.0.1' // To recognize Japanese script implementation 'com.google.android.gms:play-services-mlkit-text-recognition-japanese:16.0.1' // To recognize Korean script implementation 'com.google.android.gms:play-services-mlkit-text-recognition-korean:16.0.1' }Se você optar por usar o modelo no Google Play Services, poderá configurar seu app para fazer o download automático do modelo no dispositivo após a instalação do app pela Play Store. Para fazer isso, adicione a seguinte declaração ao arquivo
AndroidManifest.xmldo app:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="ocr" > <!-- To use multiple models: android:value="ocr,ocr_chinese,ocr_devanagari,ocr_japanese,ocr_korean,..." --> </application>Também é possível verificar explicitamente a disponibilidade do modelo e solicitar o download pela API ModuleInstallClient dos Serviços do Google Play. Se você não ativar os downloads do modelo no momento da instalação ou solicitar o download explícito, o modelo será baixado na primeira vez em que você executar o scanner. As solicitações feitas antes da conclusão do download não produzem resultados.
1. Criar uma instância de TextRecognizer
Crie uma instância de TextRecognizer, transmitindo as opções
relacionadas à biblioteca em que você declarou uma dependência acima:
Kotlin
// When using Latin script library val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) // When using Chinese script library val recognizer = TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build()) // When using Devanagari script library val recognizer = TextRecognition.getClient(DevanagariTextRecognizerOptions.Builder().build()) // When using Japanese script library val recognizer = TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build()) // When using Korean script library val recognizer = TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())
Java
// When using Latin script library TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS); // When using Chinese script library TextRecognizer recognizer = TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build()); // When using Devanagari script library TextRecognizer recognizer = TextRecognition.getClient(new DevanagariTextRecognizerOptions.Builder().build()); // When using Japanese script library TextRecognizer recognizer = TextRecognition.getClient(new JapaneseTextRecognizerOptions.Builder().build()); // When using Korean script library TextRecognizer recognizer = TextRecognition.getClient(new KoreanTextRecognizerOptions.Builder().build());
2. Preparar a imagem de entrada
Para reconhecer texto em uma imagem, crie um objeto InputImage com base em
um Bitmap, media.Image, ByteBuffer, matriz de bytes ou um arquivo no
dispositivo. Em seguida, transmita o objeto InputImage para o método
processImage do TextRecognizer.
É possível criar um objeto InputImage
de diferentes fontes, cada uma explicada abaixo.
Como usar um media.Image
Para criar um objeto InputImage
usando um objeto media.Image, como ao capturar uma imagem da
câmera de um dispositivo, transmita o objeto media.Image e a
rotação da imagem para InputImage.fromMediaImage().
Se você usar a
biblioteca
CameraX, as classes OnImageCapturedListener e
ImageAnalysis.Analyzer vão calcular o valor de rotação
para você.
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 // ... } } }
Se você não usar uma biblioteca de câmera que ofereça o grau de rotação da imagem, será possível calcular usando o grau de rotação do dispositivo e a orientação do sensor da câmera:
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; }
Em seguida, transmita o objeto media.Image e o
valor do grau de rotação para InputImage.fromMediaImage():
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Como usar um URI de arquivo
Para criar um objeto InputImage
usando o URI de um arquivo, transmita o contexto do app e o URI do arquivo para
InputImage.fromFilePath(). Isso é útil ao usar
uma intent ACTION_GET_CONTENT para solicitar que o usuário selecione
uma imagem no app de galeria dele.
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(); }
Como usar ByteBuffer ou ByteArray
Para criar um objeto InputImage
usando um ByteBuffer ou um ByteArray, primeiro calcule o grau de rotação da imagem
conforme descrito anteriormente para a entrada de media.Image.
Em seguida, crie o objeto InputImage com o buffer ou a matriz, com a altura,
a largura, o formato de codificação de cores e o grau de rotação da imagem:
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 );
Como usar um Bitmap
Para criar um objeto InputImage
com base em um objeto Bitmap, faça a seguinte declaração:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
A imagem é representada por um objeto Bitmap com os graus de rotação.
3. Processar a imagem
Transmita a imagem para o 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. Extrair texto de blocos de texto reconhecido
Se a operação de reconhecimento de texto for bem-sucedida, um objeto Text será transmitido para o listener de êxito. Um objeto Text contém o texto completo reconhecido
na imagem e zero ou mais objetos TextBlock.
Cada TextBlock representa um bloco retangular de texto,
que contém zero ou mais objetos Line. Cada objeto
Line representa uma linha de texto, que contém zero
ou mais objetos Element. Cada objeto Element representa uma palavra ou uma entidade semelhante a uma palavra, que contém zero ou mais objetos Symbol. Cada objeto Symbol representa um caractere, um dígito ou uma entidade semelhante a uma palavra.
Para cada objeto TextBlock, Line, Element e Symbol, é possível receber o texto reconhecido na região, as coordenadas delimitadoras da região e muitos outros atributos, como informações de rotação, pontuação de confiança etc.
Exemplo:
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(); } } } }
Diretrizes de imagens de entrada
-
Para que o Kit de ML reconheça o texto com precisão, as imagens de entrada precisam conter texto representado por dados de pixel suficientes. O ideal é que cada caractere tenha pelo menos 16 x 16 pixels. Geralmente não há melhorias de precisão em usar caracteres maiores que 24 x 24 pixels.
Por exemplo, uma imagem de 640 x 480 pixels pode funcionar para digitalizar um cartão de visita que ocupe toda a largura da imagem. Para digitalizar um documento impresso em papel de tamanho carta, talvez seja necessária uma imagem de 720 x 1280 pixels.
-
O foco inadequado da imagem pode prejudicar a precisão do reconhecimento de texto. Se você não está conseguindo resultados aceitáveis, peça para o usuário recapturar a imagem.
-
Se você estiver fazendo reconhecimento de texto em um aplicativo em tempo real, considere as dimensões gerais das imagens de entrada. Imagens menores podem ser processadas mais rapidamente. Para reduzir a latência, faça o texto ocupar o máximo possível da imagem e capture imagens em resoluções menores (lembrando os requisitos de precisão mencionados acima). Para mais informações, consulte Dicas para melhorar o desempenho.
Dicas para melhorar a performance
- Se você usar a API
Cameraoucamera2, limite as chamadas ao detector. Se um novo frame de vídeo ficar disponível durante a execução do detector, descarte esse frame. Consulte a classeVisionProcessorBaseno app de amostra do guia de início rápido para conferir um exemplo. - Se você usar a API
CameraX, verifique se a estratégia de contrapressão está definida como o valor padrãoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Isso garante que apenas uma imagem seja entregue para análise por vez. Se mais imagens forem produzidas quando o analisador estiver ocupado, elas serão descartadas automaticamente e não serão enfileiradas para entrega. Quando a imagem analisada é fechada chamando ImageProxy.close(), a próxima imagem mais recente é entregue. - Se você usar a saída do detector para sobrepor elementos gráficos na
imagem de entrada, primeiro acesse o resultado do Kit de ML. Em seguida, renderize a imagem
e faça a sobreposição de uma só vez. Isso renderiza a superfície de exibição apenas uma vez para cada frame de entrada. Consulte as classes
CameraSourcePrevieweGraphicOverlayno app de amostra do guia de início rápido para conferir um exemplo. - Se você usar a API Camera2, capture imagens no
formato
ImageFormat.YUV_420_888. Se você usar a API Camera mais antiga, capture imagens no formatoImageFormat.NV21. - Capture imagens em uma resolução menor. No entanto, lembre-se também dos requisitos de dimensão de imagem da API.