Reconhecer texto em imagens com o Kit de ML no Android

Você pode 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:

API Text Recognition
DescriçãoReconhecer texto em latino em imagens ou vídeos.
Nome da bibliotecacom.google.android.gms:play-services-mlkit-text-recognition
ImplementaçãoO download da biblioteca é feito dinamicamente pelo Google Play Services.
Impacto do tamanho do app260KB
Tempo de inicializaçãoPode ser necessário aguardar o download da biblioteca para usá-la.
PerformanceEm tempo real na maioria dos dispositivos.

A API de reconhecimento de texto usa uma biblioteca desagrupada que precisa ser transferida por download. Você tem a opção de fazer esse download quando o app for instalado, quando ele for iniciado pela primeira vez ou pela API ModuleInstallClient do Google Play Services. Em muitos casos, outros apps Android já podem ter realizado essa etapa. Nesse caso, a API está disponível imediatamente.

  • Teste o app de exemplo para ver um exemplo de uso dessa API.
  • Tente o código por conta própria com o codelab.

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 do Gradle no nível do app do seu módulo, que geralmente é app/build.gradle:
    dependencies {
      // ...
    
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
    }
    
  3. Opcional, mas recomendado: você pode configurar seu app para fazer o download automaticamente do modelo de ML para o dispositivo depois que seu app for instalado da Play Store. Para fazer isso, adicione a seguinte declaração ao arquivo AndroidManifest.xml do seu app:

    <application ...>
      ...
      <meta-data
          android:name="com.google.mlkit.vision.DEPENDENCIES"
          android:value="ocr" />
      <!-- To use multiple models: android:value="ocr,model2,model3" -->
    </application>
    
    Se você não ativar os downloads do modelo de tempo de instalação, ele será transferido na primeira vez em que você executar o detector. As solicitações feitas antes da conclusão do download não produzirão resultados.

1. Criar uma instância de TextRecognizer

Crie uma instância de TextRecognizer:

Kotlin

val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)

Java

TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);

2. Preparar a imagem de entrada

Para reconhecer texto em uma imagem, crie um objeto InputImage a partir de 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 usando 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 de uma 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, poderá calculá-lo 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 a partir de um URI de 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 do aplicativo 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 ByteArray, primeiro calcule o grau de rotação da imagem 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 textos 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 de texto retangular, 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, você pode 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 imagem de entrada

  • Para que o Kit de ML reconheça o texto com precisão, as imagens de entrada devem 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 afetar 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, garanta que o texto ocupe o máximo possível da imagem e capture imagens em resoluções mais baixas, tendo em mente os requisitos de precisão mencionados acima. Para mais informações, consulte Dicas para melhorar o desempenho.

Dicas para melhorar o desempenho

  • Se você usa a API Camera ou camera2, o limite de chamadas para o detector é limitado. Se um novo frame de vídeo for disponibilizado enquanto o detector estiver em execução, descarte o frame. Consulte a classe VisionProcessorBase no app de amostra do guia de início rápido para ver um exemplo.
  • Se você usar a API CameraX, verifique se a estratégia de contrapressão está definida com o valor padrão ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Isso garante que apenas uma imagem seja enviada por vez para análise. Se mais imagens forem produzidas quando o analisador estiver ocupado, elas serão descartadas automaticamente e não serão colocadas na fila para entrega. Quando a imagem que está sendo analisada é fechada, chamando ImageProxy.close(), a próxima imagem mais recente é enviada.
  • 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 e, em seguida, renderize a imagem e a sobreposição em uma única etapa. Ela será renderizada na superfície de exibição apenas uma vez para cada frame de entrada. Consulte as classes CameraSourcePreview e GraphicOverlay no app de amostra do guia de início rápido para ver 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 formato ImageFormat.NV21.
  • Capture imagens em uma resolução menor. No entanto, lembre-se também dos requisitos de dimensão de imagem desta API.