Ir para o console

Ler códigos de barras com o Kit de ML no Android

É possível usar o Kit de ML para reconhecer e decodificar códigos de barras.

Consulte o Material Design do aplicativo de demonstração do Kit de ML e a amostra do guia de início rápido do Kit de ML (ambos em inglês) no GitHub para ver exemplos desta API em uso.

Antes de começar

  1. Adicione o Firebase ao seu projeto para Android, caso ainda não tenha feito isso.
  2. No arquivo build.gradle no nível do projeto, inclua o repositório Maven do Google nas seções buildscript e allprojects.
  3. Adicione as dependências das bibliotecas Android do Kit de ML ao arquivo Gradle do módulo (nível do aplicativo) que geralmente é app/build.gradle:
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:23.0.0'
    }
    apply plugin: 'com.google.gms.google-services'
    
  4. Opcional, mas recomendado: configure seu aplicativo para fazer o download automático do modelo de ML no dispositivo após a instalação do aplicativo a partir da Play Store.

    Para fazer isso, adicione a seguinte declaração ao arquivo AndroidManifest.xml do seu aplicativo:

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="barcode" />
      <!-- To use multiple models: android:value="barcode,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.

Diretrizes de imagem de entrada

  • Para que o Kit de ML leia códigos de barras com precisão, as imagens de entrada devem conter códigos de barras representados por dados de pixel suficientes. Em geral, a menor unidade significativa do código de barras deve ter pelo menos 2 pixels de largura (e para códigos bidimensionais, 2 pixels de altura).

    Por exemplo, os códigos de barras EAN-13 são compostos de barras e espaços de 1, 2, 3 ou 4 unidades de largura, portanto, uma imagem de código de barras EAN-13 possui barras e espaços de, no mínimo, 2, 4, 6 e 8 pixels de largura. Como um código de barras EAN-13 tem 95 unidades no total, o código de barras deve ter pelo menos 190 pixels de largura.

    Formatos mais densos, como o PDF417, precisam de dimensões em pixels maiores para que o Kit de ML possa ler de forma confiável. Por exemplo, um código PDF417 pode ter até 34 "palavras" de 17 unidades em uma única linha, o que, idealmente, teria pelo menos 1.156 pixels de largura.

  • Uma imagem com foco inadequado pode prejudicar a precisão. Se você não estiver conseguindo resultados aceitáveis, peça para o usuário recapturar a imagem.

  • Se você estiver lendo códigos de barra em um aplicativo em tempo real, considere as dimensões gerais das imagens de entrada. Já que imagens menores podem ser processadas mais rapidamente, para reduzir a latência, capture imagens em resoluções mais baixas (tendo em mente os requisitos de precisão acima) e certifique-se de que o rosto da pessoa ocupe o máximo possível da imagem. Consulte também Dicas para melhorar o desempenho em tempo real.

1. Configurar o detector de código de barras

Se souber quais formatos de código de barras espera ler, você poderá aumentar a velocidade do detector de código de barras configurando-o para detectar apenas esses formatos.

Por exemplo, para detectar apenas os códigos Aztec e QR, crie um objeto FirebaseVisionBarcodeDetectorOptions como no seguinte exemplo:

Java

FirebaseVisionBarcodeDetectorOptions options =
        new FirebaseVisionBarcodeDetectorOptions.Builder()
        .setBarcodeFormats(
                FirebaseVisionBarcode.FORMAT_QR_CODE,
                FirebaseVisionBarcode.FORMAT_AZTEC)
        .build();

Kotlin

val options = FirebaseVisionBarcodeDetectorOptions.Builder()
        .setBarcodeFormats(
                FirebaseVisionBarcode.FORMAT_QR_CODE,
                FirebaseVisionBarcode.FORMAT_AZTEC)
        .build()

Os seguintes formatos são compatíveis:

  • Code 128 (FORMAT_CODE_128)
  • Code 39 (FORMAT_CODE_39)
  • Code 93 (FORMAT_CODE_93)
  • Codabar (FORMAT_CODABAR)
  • EAN-13 (FORMAT_EAN_13)
  • EAN-8 (FORMAT_EAN_8)
  • ITF (FORMAT_ITF)
  • UPC-A (FORMAT_UPC_A)
  • UPC-E (FORMAT_UPC_E)
  • QR Code (FORMAT_QR_CODE)
  • PDF417 (FORMAT_PDF417)
  • Aztec (FORMAT_AZTEC)
  • Data Matrix (FORMAT_DATA_MATRIX)

2. Executar o detector de código de barras

Para reconhecer códigos de barras em uma imagem, crie um objeto FirebaseVisionImage usando um Bitmap, um media.Image, um ByteBuffer, uma matriz de bytes ou um arquivo no dispositivo. Em seguida, passe o objeto FirebaseVisionImage para o método FirebaseVisionBarcodeDetector do detectInImage.

  1. Crie um objeto FirebaseVisionImage usando sua imagem.

    • Para criar um objeto FirebaseVisionImage usando um media.Image, como na captura de uma imagem da câmera de um dispositivo, transmita o objeto media.Image e a rotação da imagem para FirebaseVisionImage.fromMediaImage().

      Se usar a biblioteca CameraX, as classes OnImageCapturedListener e ImageAnalysis.Analyzer calcularão o valor de rotação e você precisará apenas converter a rotação para uma das constantes ROTATION_ do Kit de ML antes de chamar FirebaseVisionImage.fromMediaImage().

      Java

      private class YourAnalyzer implements ImageAnalysis.Analyzer {
      
          private int degreesToFirebaseRotation(int degrees) {
              switch (degrees) {
                  case 0:
                      return FirebaseVisionImageMetadata.ROTATION_0;
                  case 90:
                      return FirebaseVisionImageMetadata.ROTATION_90;
                  case 180:
                      return FirebaseVisionImageMetadata.ROTATION_180;
                  case 270:
                      return FirebaseVisionImageMetadata.ROTATION_270;
                  default:
                      throw new IllegalArgumentException(
                              "Rotation must be 0, 90, 180, or 270.");
              }
          }
      
          @Override
          public void analyze(ImageProxy imageProxy, int degrees) {
              if (imageProxy == null || imageProxy.getImage() == null) {
                  return;
              }
              Image mediaImage = imageProxy.getImage();
              int rotation = degreesToFirebaseRotation(degrees);
              FirebaseVisionImage image =
                      FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
              // Pass image to an ML Kit Vision API
              // ...
          }
      }
      

      Kotlin

      private class YourImageAnalyzer : ImageAnalysis.Analyzer {
          private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
              0 -> FirebaseVisionImageMetadata.ROTATION_0
              90 -> FirebaseVisionImageMetadata.ROTATION_90
              180 -> FirebaseVisionImageMetadata.ROTATION_180
              270 -> FirebaseVisionImageMetadata.ROTATION_270
              else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
          }
      
          override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
              val mediaImage = imageProxy?.image
              val imageRotation = degreesToFirebaseRotation(degrees)
              if (mediaImage != null) {
                  val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                  // Pass image to an ML Kit Vision API
                  // ...
              }
          }
      }
      

      Se você não usar uma biblioteca de câmera que ofereça a rotação da imagem, será possível calculá-la a partir da rotação do dispositivo e da orientação do sensor da câmera:

      Java

      private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
      static {
          ORIENTATIONS.append(Surface.ROTATION_0, 90);
          ORIENTATIONS.append(Surface.ROTATION_90, 0);
          ORIENTATIONS.append(Surface.ROTATION_180, 270);
          ORIENTATIONS.append(Surface.ROTATION_270, 180);
      }
      
      /**
       * 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, Context context)
              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);
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
          int sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION);
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          int result;
          switch (rotationCompensation) {
              case 0:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  break;
              case 90:
                  result = FirebaseVisionImageMetadata.ROTATION_90;
                  break;
              case 180:
                  result = FirebaseVisionImageMetadata.ROTATION_180;
                  break;
              case 270:
                  result = FirebaseVisionImageMetadata.ROTATION_270;
                  break;
              default:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  Log.e(TAG, "Bad rotation value: " + rotationCompensation);
          }
          return result;
      }

      Kotlin

      private val ORIENTATIONS = SparseIntArray()
      
      init {
          ORIENTATIONS.append(Surface.ROTATION_0, 90)
          ORIENTATIONS.append(Surface.ROTATION_90, 0)
          ORIENTATIONS.append(Surface.ROTATION_180, 270)
          ORIENTATIONS.append(Surface.ROTATION_270, 180)
      }/**
       * 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, context: Context): 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)
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
          val sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          val result: Int
          when (rotationCompensation) {
              0 -> result = FirebaseVisionImageMetadata.ROTATION_0
              90 -> result = FirebaseVisionImageMetadata.ROTATION_90
              180 -> result = FirebaseVisionImageMetadata.ROTATION_180
              270 -> result = FirebaseVisionImageMetadata.ROTATION_270
              else -> {
                  result = FirebaseVisionImageMetadata.ROTATION_0
                  Log.e(TAG, "Bad rotation value: $rotationCompensation")
              }
          }
          return result
      }

      Depois, transmita o objeto media.Image e o valor de rotação para FirebaseVisionImage.fromMediaImage():

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);

      Kotlin

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • Para criar um objeto FirebaseVisionImage a partir do URI de um arquivo, transmita o contexto do aplicativo e o URI do arquivo para FirebaseVisionImage.fromFilePath(). Isso é útil ao usar um intent ACTION_GET_CONTENT para solicitar que o usuário selecione uma imagem do aplicativo de galeria dele.

      Java

      FirebaseVisionImage image;
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri);
      } catch (IOException e) {
          e.printStackTrace();
      }

      Kotlin

      val image: FirebaseVisionImage
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri)
      } catch (e: IOException) {
          e.printStackTrace()
      }
    • Para criar um objeto FirebaseVisionImage usando um ByteBuffer ou uma matriz de bytes, primeiro, calcule a rotação da imagem conforme na entrada media.Image.

      Em seguida, crie um objeto FirebaseVisionImageMetadata que contenha a altura, a largura, o formato de codificação de cor e a rotação da imagem:

      Java

      FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
              .setWidth(480)   // 480x360 is typically sufficient for
              .setHeight(360)  // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build();

      Kotlin

      val metadata = FirebaseVisionImageMetadata.Builder()
              .setWidth(480) // 480x360 is typically sufficient for
              .setHeight(360) // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build()

      Use o buffer ou a matriz junto do objeto de metadados para criar um objeto FirebaseVisionImage:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);// Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

      Kotlin

      val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)// Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
    • Para criar um objeto FirebaseVisionImage a partir de um objeto Bitmap, use os snippets a seguir:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      A imagem representada pelo objeto Bitmap precisa estar na posição vertical, sem necessidade de girá-la novamente.

  2. Consiga uma instância de FirebaseVisionBarcodeDetector:

    Java

    FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
            .getVisionBarcodeDetector();
    // Or, to specify the formats to recognize:
    // FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
    //        .getVisionBarcodeDetector(options);

    Kotlin

    val detector = FirebaseVision.getInstance()
            .visionBarcodeDetector
    // Or, to specify the formats to recognize:
    // val detector = FirebaseVision.getInstance()
    //        .getVisionBarcodeDetector(options)
  3. Finalmente, passe a imagem para o método detectInImage:

    Java

    Task<List<FirebaseVisionBarcode>> result = detector.detectInImage(image)
            .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionBarcode>>() {
                @Override
                public void onSuccess(List<FirebaseVisionBarcode> barcodes) {
                    // Task completed successfully
                    // ...
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // Task failed with an exception
                    // ...
                }
                    });

    Kotlin

    val result = detector.detectInImage(image)
            .addOnSuccessListener { barcodes ->
                // Task completed successfully
                // ...
            }
            .addOnFailureListener {
                // Task failed with an exception
                // ...
            }

3. Receber informações de códigos de barras

Se a operação de reconhecimento de código de barras for bem-sucedida, uma matriz de objetos FirebaseVisionBarcode será transmitida ao listener de êxito. Cada objeto FirebaseVisionBarcode representa um código de barras detectado na imagem. Para cada código de barras, é possível receber as coordenadas delimitadoras na imagem de entrada, bem como os dados brutos codificados no código de barras. Além disso, se o detector de código de barras tiver sido capaz de determinar o tipo de dados codificados pelo código de barras, será possível receber um objeto contendo dados analisados.

Exemplo:

Java

for (FirebaseVisionBarcode barcode: barcodes) {
    Rect bounds = barcode.getBoundingBox();
    Point[] corners = barcode.getCornerPoints();

    String rawValue = barcode.getRawValue();

    int valueType = barcode.getValueType();
    // See API reference for complete list of supported types
    switch (valueType) {
        case FirebaseVisionBarcode.TYPE_WIFI:
            String ssid = barcode.getWifi().getSsid();
            String password = barcode.getWifi().getPassword();
            int type = barcode.getWifi().getEncryptionType();
            break;
        case FirebaseVisionBarcode.TYPE_URL:
            String title = barcode.getUrl().getTitle();
            String url = barcode.getUrl().getUrl();
            break;
    }
}

Kotlin

for (barcode in barcodes) {
    val bounds = barcode.boundingBox
    val corners = barcode.cornerPoints

    val rawValue = barcode.rawValue

    val valueType = barcode.valueType
    // See API reference for complete list of supported types
    when (valueType) {
        FirebaseVisionBarcode.TYPE_WIFI -> {
            val ssid = barcode.wifi!!.ssid
            val password = barcode.wifi!!.password
            val type = barcode.wifi!!.encryptionType
        }
        FirebaseVisionBarcode.TYPE_URL -> {
            val title = barcode.url!!.title
            val url = barcode.url!!.url
        }
    }
}

Dicas para melhorar o desempenho em tempo real

Caso você queira ler códigos de barra em um aplicativo em tempo real, siga estas diretrizes para obter as melhores taxas de quadros:

  • Limite as chamadas ao detector. Se um novo quadro de vídeo for disponibilizado enquanto o detector estiver em execução, descarte esse quadro. Para ver um exemplo, consulte a classe VisionProcessorBase (em inglês) no aplicativo de amostra do guia de início rápido.
  • Se você estiver usando 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. Ao fazer isso, você renderiza a superfície de exibição apenas uma vez para cada quadro de entrada. Para ver exemplos, consulte as classes CameraSourcePreview e GraphicOverlay (ambos em inglês) no aplicativo de amostra do guia de início rápido.
  • 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 da API.

Próximas etapas

Consulte o Material Design do aplicativo de demonstração do Kit de ML e a amostra do guia de início rápido do Kit de ML (ambos em inglês) no GitHub para ver exemplos desta API em uso.