Digitalize códigos de barras com kit de ML no Android

Você pode usar o ML Kit para reconhecer e decodificar códigos de barras.

Antes de você começar

  1. Adicione o Firebase ao seu projeto Android , caso ainda não o tenha feito.
  2. Adicione as dependências das bibliotecas Android do ML Kit ao arquivo Gradle do módulo (nível do aplicativo) (geralmente app/build.gradle ):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
      implementation 'com.google.firebase:firebase-ml-vision-barcode-model:16.0.1'
    }
    

Diretrizes de imagem de entrada

  • Para que o ML Kit leia códigos de barras com precisão, as imagens de entrada devem conter códigos de barras representados por dados de pixel suficientes.

    Os requisitos específicos de dados de pixel dependem do tipo de código de barras e da quantidade de dados codificados nele (já que a maioria dos códigos de barras suporta uma carga útil de comprimento variável). 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 com 1, 2, 3 ou 4 unidades de largura, portanto, uma imagem de código de barras EAN-13 idealmente possui barras e espaços com pelo menos 2, 4, 6 e 8 pixels de largura. Como um código de barras EAN-13 tem 95 unidades de largura no total, o código de barras deve ter pelo menos 190 pixels de largura.

    Formatos mais densos, como PDF417, precisam de dimensões de pixels maiores para que o ML Kit possa lê-los com segurança. Por exemplo, um código PDF417 pode ter até 34 "palavras" de 17 unidades de largura em uma única linha, o que idealmente teria pelo menos 1156 pixels de largura.

  • O foco inadequado da imagem pode prejudicar a precisão da digitalização. Se você não estiver obtendo resultados aceitáveis, tente pedir ao usuário para recapturar a imagem.

  • Para aplicações típicas, recomenda-se fornecer uma imagem de resolução mais alta (como 1280x720 ou 1920x1080), o que torna os códigos de barras detectáveis ​​a uma distância maior da câmera.

    No entanto, em aplicações onde a latência é crítica, você pode melhorar o desempenho capturando imagens em uma resolução mais baixa, mas exigindo que o código de barras represente a maior parte da imagem de entrada. Consulte também Dicas para melhorar o desempenho em tempo real .

1. Configure o detector de código de barras

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

Por exemplo, para detectar apenas códigos astecas e códigos QR, crie um objeto FirebaseVisionBarcodeDetectorOptions como no exemplo a seguir:

Java

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

Kotlin+KTX

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

Os seguintes formatos são suportados:

  • Código 128 ( FORMAT_CODE_128 )
  • Código 39 ( FORMAT_CODE_39 )
  • Código 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 )
  • Código QR ( FORMAT_QR_CODE )
  • PDF417 ( FORMAT_PDF417 )
  • Asteca ( FORMAT_AZTEC )
  • Matriz de dados ( FORMAT_DATA_MATRIX )

2. Execute o detector de código de barras

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

  1. Crie um objeto FirebaseVisionImage a partir da sua imagem.

    • Para criar um objeto FirebaseVisionImage a partir de um objeto media.Image , como ao capturar uma imagem da câmera de um dispositivo, passe o objeto media.Image e a rotação da imagem para FirebaseVisionImage.fromMediaImage() .

      Se você usar a biblioteca CameraX , as classes OnImageCapturedListener e ImageAnalysis.Analyzer calcularão o valor de rotação para você, então você só precisa converter a rotação em 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+KTX

      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 usa uma biblioteca de câmeras que fornece a rotação da imagem, você pode calculá-la a partir da rotação do dispositivo e da orientação do sensor da câmera no dispositivo:

      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+KTX

      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
      }

      Em seguida, passe o objeto media.Image e o valor de rotação para FirebaseVisionImage.fromMediaImage() :

      Java

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

      Kotlin+KTX

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

      Java

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

      Kotlin+KTX

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

      Em seguida, crie um objeto FirebaseVisionImageMetadata que contenha a altura, a largura, o formato de codificação de cores 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+KTX

      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 array e o objeto de metadados para criar um objeto FirebaseVisionImage :

      Java

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

      Kotlin+KTX

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

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      A imagem representada pelo objeto Bitmap deve estar na vertical, sem necessidade de rotação adicional.

  2. Obtenha uma instância do FirebaseVisionBarcodeDetector :

    Java

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

    Kotlin+KTX

    val detector = FirebaseVision.getInstance()
            .visionBarcodeDetector
    // Or, to specify the formats to recognize:
    // val detector = FirebaseVision.getInstance()
    //        .getVisionBarcodeDetector(options)
  3. Por fim, 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+KTX

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

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

Se a operação de reconhecimento de código de barras for bem-sucedida, uma lista de objetos FirebaseVisionBarcode será transmitida ao listener de sucesso. Cada objeto FirebaseVisionBarcode representa um código de barras que foi detectado na imagem. Para cada código de barras, você pode obter suas coordenadas delimitadoras na imagem de entrada, bem como os dados brutos codificados pelo código de barras. Além disso, se o detector de código de barras foi capaz de determinar o tipo de dados codificados pelo código de barras, você poderá obter um objeto contendo dados analisados.

Por 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+KTX

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

Se você deseja ler códigos de barras em um aplicativo em tempo real, siga estas diretrizes para obter as melhores taxas de quadros:

  • Não capture a entrada na resolução nativa da câmera. Em alguns dispositivos, a captura de entrada na resolução nativa produz imagens extremamente grandes (mais de 10 megapixels), o que resulta em latência muito baixa, sem nenhum benefício para a precisão. Em vez disso, solicite da câmera apenas o tamanho necessário para a detecção de código de barras: geralmente não mais que 2 megapixels.

    Se a velocidade de digitalização for importante, você poderá diminuir ainda mais a resolução de captura da imagem. No entanto, tenha em mente os requisitos mínimos de tamanho do código de barras descritos acima.

  • Limite as chamadas para o detector. Se um novo quadro de vídeo ficar disponível enquanto o detector estiver em execução, elimine o quadro.
  • Se você estiver usando a saída do detector para sobrepor gráficos na imagem de entrada, primeiro obtenha 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 na superfície de exibição apenas uma vez para cada quadro de entrada.
  • 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 .