É possível usar o kit de aprendizado de máquina para reconhecer e decodificar códigos de barras.
Recurso | Desagrupado | Agrupadas |
---|---|---|
Implementação | O download do modelo é feito dinamicamente pelo Google Play Services. | O modelo é estaticamente vinculado ao app no momento da criação. |
Tamanho do app | Aumento de cerca de 200 KB. | Aumento de cerca de 2,4 MB. |
Tempo de inicialização | Talvez seja necessário aguardar o download do modelo para usá-lo pela primeira vez. | O modelo estará disponível imediatamente. |
Testar
- Explore o app de amostra para ver um exemplo de uso dessa API.
- Consulte o app de demonstração do Material Design (em inglês) para ver uma implementação completa dessa API.
Antes de começar
No arquivo
build.gradle
no nível do projeto, inclua o repositório Maven do Google nas seçõesbuildscript
eallprojects
.Adicione as dependências das bibliotecas Android do Kit de ML ao arquivo Gradle do módulo no nível do app, que geralmente é
app/build.gradle
. Escolha uma das seguintes dependências com base nas suas necessidades:Para agrupar o modelo e o app:
dependencies { // ... // Use this dependency to bundle the model with your app implementation 'com.google.mlkit:barcode-scanning:17.1.0' }
Para usar o modelo no Google Play Services:
dependencies { // ... // Use this dependency to use the dynamically downloaded model in Google Play Services implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.2.0' }
Se você optar por usar o modelo no Google Play Services, poderá configurar o app para fazer o download automaticamente no dispositivo depois que ele for instalado na 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="barcode" > <!-- To use multiple models: android:value="barcode,model2,model3" --> </application>
Também é possível verificar explicitamente a disponibilidade do modelo e solicitar o download por meio da API ModuleInstallClient do Google Play Services.
Se você não ativar os downloads do modelo de tempo de instalação ou solicitar o download explícito, o modelo será transferido na primeira vez que você executar o verificador. As solicitações feitas antes da conclusão do download não produzem resultados.
Diretrizes para imagens de entrada
-
Para que o Kit de ML leia códigos de barras com precisão, as imagens de entrada precisam 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 muitos códigos de barras são compatíveis com um payload de tamanho 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 por barras e espaços de 1, 2, 3 ou 4 unidades de largura. Portanto, uma imagem de código de barras EAN-13 tem barras e espaços de, no mínimo, 2, 4, 6 e 8 pixels. 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 PDF417, precisam de dimensões em pixels maiores para que o kit de ML os leia de maneira confiável. Por exemplo, um código PDF417 pode ter até 34 "palavras" de 17 unidades de largura em uma única linha, o ideal seria pelo menos 1.156 pixels de largura.
-
O foco insatisfatório da imagem pode afetar a precisão da verificação. Se o app não estiver recebendo resultados aceitáveis, peça ao usuário para recapturar a imagem.
-
Para aplicativos típicos, recomenda-se fornecer uma imagem de resolução mais alta, como 1280 x 720 ou 1920 x 1080, o que torna os códigos de barras digitalizáveis a uma distância maior da câmera.
No entanto, em aplicativos em que a latência é crítica, é possível melhorar o desempenho capturando imagens em uma resolução mais baixa, mas exigindo que o código de barras componha a maior parte da imagem de entrada. Consulte também Dicas para melhorar o desempenho em tempo real.
1. Configurar o leitor de código de barras
Se você sabe quais formatos de código de barras espera ler, pode melhorar a velocidade do detector de código de barras configurando-o para detectar apenas esses formatos.Por exemplo, para detectar apenas códigos Aztec e QR, crie um
objeto BarcodeScannerOptions
como no exemplo a seguir:
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats( Barcode.FORMAT_QR_CODE, Barcode.FORMAT_AZTEC) .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats( Barcode.FORMAT_QR_CODE, Barcode.FORMAT_AZTEC) .build();
Os seguintes formatos são compatíveis:
- 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
)
A partir do modelo 17.1.0 empacotado e do modelo 18.2.0 desagrupado, também é possível chamar enableAllPotentialBarcodes()
para retornar todos os possíveis códigos de barras, mesmo que eles
não possam ser decodificados. Isso pode ser usado para facilitar a detecção, por exemplo, ao aumentar o zoom da câmera para ter uma imagem mais clara de qualquer código de barras na caixa delimitadora retornada.
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .enableAllPotentialBarcodes() // Optional .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .enableAllPotentialBarcodes() // Optional .build();
2. Prepare the input image
To recognize barcodes in an image, create anInputImage
object
from either a Bitmap
, media.Image
, ByteBuffer
, byte array, or a file on
the device. Then, pass the InputImage
object to the
BarcodeScanner
's process
method.
You can create an InputImage
object from different sources, each is explained below.
Using a media.Image
To create an InputImage
object from a media.Image
object, such as when you capture an image from a
device's camera, pass the media.Image
object and the image's
rotation to InputImage.fromMediaImage()
.
If you use the
CameraX library, the OnImageCapturedListener
and
ImageAnalysis.Analyzer
classes calculate the rotation value
for you.
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, você 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 um intent ACTION_GET_CONTENT
para solicitar que o usuário selecione uma imagem do 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 um ByteBuffer
ou ByteArray
Para criar um objeto InputImage
a partir de ByteBuffer
ou ByteArray
, primeiro calcule o grau de rotação de imagem conforme descrito anteriormente para a entrada media.Image
.
Em seguida, crie o objeto InputImage
com o buffer ou a matriz, junto 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
a partir de 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. Receber uma instância do BarcodeScanner
Kotlin
val scanner = BarcodeScanning.getClient() // Or, to specify the formats to recognize: // val scanner = BarcodeScanning.getClient(options)
Java
BarcodeScanner scanner = BarcodeScanning.getClient(); // Or, to specify the formats to recognize: // BarcodeScanner scanner = BarcodeScanning.getClient(options);
4. Processar a imagem
Transmita a imagem para o métodoprocess
:
Kotlin
val result = scanner.process(image) .addOnSuccessListener { barcodes -> // Task completed successfully // ... } .addOnFailureListener { // Task failed with an exception // ... }
Java
Task<List<Barcode>> result = scanner.process(image) .addOnSuccessListener(new OnSuccessListener<List<Barcode>>() { @Override public void onSuccess(List<Barcode> barcodes) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
5. Receber informações de códigos de barras
Se a operação de reconhecimento de código de barras for bem-sucedida, uma lista de objetosBarcode
será transmitida para o listener de êxito. Cada objeto Barcode
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 leitor de código de barras
puder determinar o tipo de dados codificados pelo código de barras, você poderá
receber um objeto com dados analisados.
Por exemplo:
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) { Barcode.TYPE_WIFI -> { val ssid = barcode.wifi!!.ssid val password = barcode.wifi!!.password val type = barcode.wifi!!.encryptionType } Barcode.TYPE_URL -> { val title = barcode.url!!.title val url = barcode.url!!.url } } }
Java
for (Barcode 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 Barcode.TYPE_WIFI: String ssid = barcode.getWifi().getSsid(); String password = barcode.getWifi().getPassword(); int type = barcode.getWifi().getEncryptionType(); break; case Barcode.TYPE_URL: String title = barcode.getUrl().getTitle(); String url = barcode.getUrl().getUrl(); break; } }
Dicas para melhorar o desempenho em tempo real
Se você quiser ler códigos de barras em um aplicativo em tempo real, siga estas diretrizes para conseguir as melhores taxas de frames:
-
Não capture entrada na resolução nativa da câmera. Em alguns dispositivos, a captura da entrada na resolução nativa produz imagens extremamente grandes (mais de 10 megapixels), o que resulta em latência muito ruim, sem benefícios para a precisão. Em vez disso, solicite apenas o tamanho da câmera necessário para a detecção do código de barras, que normalmente não é superior a 2 megapixels.
Se a velocidade de leitura for importante, você poderá diminuir ainda mais a resolução da captura da imagem. No entanto, tenha em mente os requisitos mínimos de tamanho de código de barras descritos acima.
Se você estiver tentando reconhecer códigos de barras de uma sequência de frames de vídeo de streaming, o reconhecedor poderá produzir resultados diferentes frame a frame. Aguarde até receber uma série consecutiva com o mesmo valor para ter certeza de que está retornando um bom resultado.
O dígito de soma de verificação não é compatível com ITF e CODE-39.
- Se você usar a API
Camera
oucamera2
, as chamadas para o detector serão limitadas. Se um novo frame de vídeo for disponibilizado enquanto o detector estiver em execução, descarte esse frame. Consulte a classeVisionProcessorBase
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 pressão de retorno está definida como o valor padrãoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Isso garante que apenas uma imagem será exibida por vez para análise. Se mais imagens forem produzidas quando o analisador estiver ocupado, elas serão descartadas automaticamente e não ficarão na fila para entrega. Quando a imagem que está sendo 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 e, em seguida, renderize a imagem e a sobreposição em uma única etapa. Isso renderiza a superfície de exibição apenas uma vez para cada frame de entrada. Consulte as classes
CameraSourcePreview
eGraphicOverlay
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 formatoImageFormat.NV21
.