Reconhecer texto em imagens com o Kit de ML no Android

Use 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 Desagrupado 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 download do modelo é feito dinamicamente pelo Google Play Services. O modelo é vinculado de forma estática ao app no momento da criação.
Tamanho do app Aumento de cerca de 260 KB por arquitetura de script. Aumento de cerca de 4 MB por script e arquitetura.
Tempo de inicialização Talvez seja necessário esperar o download do modelo antes do primeiro uso. O modelo está disponível imediatamente.
Desempenho Tempo real na maioria dos dispositivos com bibliotecas de script latino, mas mais lento para outros. Tempo real na maioria dos dispositivos com bibliotecas de script latino, mas mais lento para outros.

Faça um teste

Antes de começar

  1. No arquivo build.gradle no nível do projeto, inclua o repositório Maven do Google nas seções buildscript e allprojects.
  2. Adicione as dependências das bibliotecas do Android do Kit de ML ao arquivo Gradle do módulo no nível do app, que geralmente é app/build.gradle:

    Para agrupar o modelo com 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'
    }
    
  3. Se você optar por usar o modelo no Google Play Services, será possível configurar o app para fazer o download automático do modelo no dispositivo depois da instalação do app na Play Store. Para fazer isso, adicione a seguinte declaração ao arquivo AndroidManifest.xml do 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 usando a API ModuleInstallClient do Google Play Services. Se você não ativar os downloads do modelo no momento da instalação ou solicitar um download explícito, o modelo será transferido na primeira vez que você executar o scanner. As solicitações feitas antes da conclusão do download concluída 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 usando um Bitmap, media.Image, ByteBuffer, uma matriz de bytes ou um arquivo na dispositivo. Em seguida, transmita o objeto InputImage para o método processImage do TextRecognizer.

Você pode criar um InputImage de diferentes origens, cada uma explicada abaixo.

Como usar um media.Image

Para criar um objeto InputImage usando um objeto media.Image, como quando você captura 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 automaticamente.

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

Usar um URI de arquivo

Para criar um objeto InputImage com base no 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 InputImage objeto de uma ByteBuffer ou ByteArray, primeiro calcule a imagem grau de rotação conforme descrito anteriormente para a entrada 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 usando 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 sucesso. 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 O 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 que é representado por dados de pixel suficientes. O ideal é que cada caractere tenha uma resolução de pelo menos 16 x 16 pixels. Geralmente, não há benefício de precisão para 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, uma imagem de 720 x 1280 pixels pode ser necessária.

  • Uma imagem com foco inadequado pode afetar a precisão do reconhecimento de texto. Se você não conseguir resultados aceitáveis, peça para o usuário recapturar a imagem.

  • Se você estiver reconhecendo 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 mais baixas (lembrando os requisitos de precisão mencionados acima). Para mais informações, consulte Dicas para melhorar o desempenho.

Dicas para melhorar o desempenho

  • Se você usar o método Camera ou API camera2, limitar as chamadas ao detector. Se um novo vídeo fica disponível enquanto o detector está em execução, descarte esse frame. Consulte a classe VisionProcessorBase no app de amostra do guia de início rápido para conferir um exemplo.
  • Se você usa a API CameraX, verificar se a estratégia de pressão de retorno está definida para o valor padrão ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Isso garante que apenas uma imagem será enviada 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 colocadas na fila para envio. 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 é renderizado na superfície da tela. apenas uma vez para cada frame de entrada. Consulte a CameraSourcePreview e GraphicOverlay no 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 ImageFormat.NV21.
  • Capture imagens em uma resolução mais baixa. No entanto, também tenha em mente os requisitos de dimensão de imagem dessa API.