É possível usar o Kit de ML para reconhecer e decodificar códigos de barras.
Seleção de | Desagrupado | Agrupadas |
---|---|---|
Implementação | O download do modelo é feito dinamicamente pelo Google Play Services. | O modelo é vinculado estaticamente ao app no tempo de compilação. |
Tamanho do app | Aumento de aproximadamente 200 KB. | Aumento de aproximadamente 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. |
Faça um teste
- Teste o app de exemplo 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 do Android do Kit de ML ao arquivo Gradle do módulo no nível do app, que geralmente é
app/build.gradle
. Escolha uma das dependências abaixo com base nas suas necessidades:Para empacotar o modelo e o app:
dependencies { // ... // Use this dependency to bundle the model with your app implementation 'com.google.mlkit:barcode-scanning:17.0.3' }
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.1.0' }
Se você escolher usar o modelo no Google Play Services, poderá configurar o app para fazer o download dele automaticamente para o dispositivo depois que ele for instalado 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="barcode" > <!-- To use multiple models: android:value="barcode,model2,model3" --> </application>
Também é possível conferir explicitamente a disponibilidade do modelo e solicitar o download pela API ModuleInstallClient do Google Play.
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 geram 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.
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 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 os leia de maneira confiável. Por exemplo, um código PDF417 pode ter até 34 "palavras" de 17 unidades em uma única linha, o que, em uma situação ideal, seria de pelo menos 1.156 pixels de largura.
-
O foco inadequado da imagem pode afetar a precisão da leitura. Se o app não estiver conseguindo resultados aceitáveis, peça para o usuário 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 detectá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 com resolução mais baixa, mas exigindo que o código de barras compacte 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ê 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 o código Aztec e códigos 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
) - PDF 417 (
FORMAT_PDF417
) - Asteca (
FORMAT_AZTEC
) - Matriz de dados (
FORMAT_DATA_MATRIX
)
2. Preparar a imagem de entrada
Para reconhecer códigos de barras em uma imagem, crie um objetoInputImage
com base em um Bitmap
, media.Image
, ByteBuffer
, matriz de bytes ou um arquivo
no dispositivo. Em seguida, transmita o objeto InputImage
para o
método process
do BarcodeScanner
.
É possível criar um objeto InputImage
usando diferentes origens, cada uma explicada abaixo.
Como usar um media.Image
Para criar um objeto InputImage
com base em 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 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
com base em 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 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 objeto InputImage
usando um ByteBuffer
ou um ByteArray
, primeiro calcule o grau de rotação da imagem, conforme descrito anteriormente para a entrada de 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
com base em 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. Gerar 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 do 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 por ele, será possível
gerar um objeto contendo dados analisados.
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
Caso você queira ler códigos de barras em um aplicativo em tempo real, siga estas diretrizes para obter as melhores taxas de frames:
-
Não capture a 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 baixa sem nenhum benefício da precisão. Em vez disso, solicite apenas o tamanho da câmera necessário para a detecção de código de barras, que normalmente não é maior que 2 megapixels.
Se a velocidade de leitura for importante, você pode diminuir ainda mais a resolução da captura de imagem. No entanto, considere 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 streaming de vídeo, o reconhecedor poderá produzir diferentes resultados de frame a frame. Aguarde até receber uma série consecutiva do mesmo valor para ter certeza de que você 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
, o Android limita as chamadas ao detector. Se um novo frame de vídeo for disponibilizado enquanto o detector estiver em execução, elimine o 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 está definida para o valor padrãoImageAnalysis.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 ficarão 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 para 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
.