Możesz używać ML Kit do rozpoznawania i dekodowania kodów kreskowych.
Zanim zaczniesz
- Jeśli jeszcze tego nie zrobiono, dodaj Firebase do projektu na Androida.
- Dodaj zależności do bibliotek ML Kit na Androida do pliku Gradle modułu (na poziomie aplikacji) (zwykle
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' }
Wskazówki dotyczące obrazów wejściowych
-
Aby ML Kit mógł prawidłowo odczytywać kody kreskowe, obrazy wejściowe muszą zawierać kody kreskowe reprezentowane przez wystarczającą ilość danych pikseli.
Wymagania dotyczące danych piksela zależą od typu kodu kreskowego i ilości zakodowanych w nim danych (ponieważ większość kodów kreskowych obsługuje ładunek o zmiennej długości). Zasadniczo najmniejsza znacząca jednostka kodu kreskowego powinna mieć co najmniej 2 piksele szerokości (a w przypadku kodów 2D – 2 piksele wysokości).
Na przykład kody kreskowe EAN-13 składają się z pasków i odstępów o szerokości 1, 2, 3 lub 4 jednostkach, dlatego obraz kodu kreskowego EAN-13 powinien mieć paski i odstępy o szerokości co najmniej 2, 4, 6 lub 8 pikseli. Kod EAN-13 ma łącznie 95 jednostek szerokości, dlatego kod powinien mieć co najmniej 190 pikseli szerokości.
Formaty gęstsze, takie jak PDF417, wymagają większych wymiarów pikseli, aby ML Kit mógł je niezawodnie odczytać. Na przykład kod PDF417 może zawierać maksymalnie 34 „słów” o szerokości 17 jednostek w pojedynczym wierszu, które w idealnym przypadku powinny mieć co najmniej 1156 pikseli szerokości.
-
Niewłaściwe wyostrzanie obrazu może obniżyć dokładność skanowania. Jeśli nie uzyskujesz zadowalających wyników, poproś użytkownika o ponowne zrobienie zdjęcia.
-
W przypadku typowych zastosowań zalecamy przesłanie obrazu o wyższej rozdzielczości (np. 1280 x 720 lub 1920 x 1080), co pozwoli wykrywać kody kreskowe z większej odległości od kamery.
Jednak w przypadku aplikacji, w których opóźnienie jest kluczowe, możesz zwiększyć wydajność, rejestrując obrazy w niższej rozdzielczości, ale wymagając, aby kod kreskowy stanowił większość obrazu wejściowego. Zobacz też wskazówki dotyczące zwiększania skuteczności w czasie rzeczywistym.
1. Konfigurowanie modułu wykrywania kodów kreskowych
Jeśli wiesz, jakie formaty kodów kreskowych chcesz odczytać, możesz zwiększyć szybkość działania wykrywacza kodów kreskowych, konfigurując go tak, aby wykrywał tylko te formaty.Aby na przykład wykrywać tylko kody Aztec i kody QR, utwórz obiekt FirebaseVisionBarcodeDetectorOptions
w taki sposób:
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()
Obsługiwane są te formaty:
- Kod 128 (
FORMAT_CODE_128
) - Kod 39 (
FORMAT_CODE_39
) - Kod 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
) - Kod QR (
FORMAT_QR_CODE
) - PDF417 (
FORMAT_PDF417
) - Aztec (
FORMAT_AZTEC
) - Data Matrix (
FORMAT_DATA_MATRIX
)
2. Uruchamianie wykrywacza kodów kreskowych
Aby rozpoznać kody kreskowe na obrazie, utwórz obiektFirebaseVisionImage
z użyciem Bitmap
, media.Image
, ByteBuffer
, tablicy bajtów lub pliku na urządzeniu. Następnie przekaż obiekt FirebaseVisionImage
metodzie detectInImage
obiektu FirebaseVisionBarcodeDetector
.
Utwórz obiekt
FirebaseVisionImage
na podstawie obrazu.-
Aby utworzyć obiekt
FirebaseVisionImage
na podstawie obiektumedia.Image
, na przykład podczas robienia zdjęcia aparatem urządzenia, przekaż obiektmedia.Image
i obrót obrazu do obiektuFirebaseVisionImage.fromMediaImage()
.Jeśli używasz biblioteki CameraX, klasy
OnImageCapturedListener
iImageAnalysis.Analyzer
obliczają wartość rotacji za Ciebie, więc przed wywołaniem funkcjiFirebaseVisionImage.fromMediaImage()
musisz tylko przekonwertować rotację na jedną z konstantROTATION_
w ML Kit: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 // ... } } }
Jeśli nie używasz biblioteki aparatu, która zapewnia obrócenie obrazu, możesz obliczyć je na podstawie obrotu urządzenia i orientacji czujnika aparatu na urządzeniu:
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 }
Następnie prześlij obiekt
media.Image
i wartość obrotu do funkcjiFirebaseVisionImage.fromMediaImage()
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
Kotlin
val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
- Aby utworzyć obiekt
FirebaseVisionImage
z identyfikatora URI pliku, prześlij kontekst aplikacji i identyfikator URI pliku do funkcjiFirebaseVisionImage.fromFilePath()
. Jest to przydatne, gdy używasz intencjiACTION_GET_CONTENT
, aby poprosić użytkownika o wybranie obrazu z aplikacji Galeria.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() }
- Aby utworzyć obiekt
FirebaseVisionImage
zByteBuffer
lub tablicy bajtów, najpierw oblicz obrót obrazu w sposób opisany powyżej w przypadku wejściamedia.Image
.Następnie utwórz obiekt
FirebaseVisionImageMetadata
, który zawiera wysokość, szerokość, format kodowania kolorów oraz obrót obrazu: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()
Użyj bufora lub tablicy oraz obiektu metadanych, aby utworzyć obiekt
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)
- Aby utworzyć obiekt
FirebaseVisionImage
z obiektuBitmap
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
musi być pionowy i nie wymagać dodatkowego obracania.
-
Pobieranie instancji
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)
Na koniec przekaż obraz do metody
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. Pobieranie informacji z kodów kreskowych
Jeśli operacja rozpoznawania kodu kreskowego zakończy się powodzeniem, do odbiorcy sukcesu zostanie przekazana lista obiektówFirebaseVisionBarcode
. Każdy obiekt FirebaseVisionBarcode
reprezentuje kod kreskowy wykryty na obrazie. W przypadku każdego kodu kreskowego możesz uzyskać współrzędne ograniczające w obrazie wejściowym, a także dane nieprzetworzone zakodowane w kodzie. Jeśli detekcja kodu kreskowego była w stanie określić typ danych zakodowanych w kodzie, możesz otrzymać obiekt zawierający przeanalizowane dane.
Przykład:
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 } } }
Wskazówki dotyczące zwiększania skuteczności w czasie rzeczywistym
Jeśli chcesz skanować kody kreskowe w czasie rzeczywistym, postępuj zgodnie z tymi wskazówkami, aby uzyskać najlepszą liczbę klatek na sekundę:
-
Nie rejestruj danych wejściowych w domyślnej rozdzielczości kamery. Na niektórych urządzeniach rejestrowanie danych wejściowych w rodzimej rozdzielczości powoduje, że obrazy są bardzo duże (ponad 10 megapikseli), co powoduje bardzo długi czas oczekiwania bez żadnej korzyści dla dokładności. Zamiast tego poproś o rozmiar wymagany do wykrywania kodów kreskowych: zazwyczaj nie więcej niż 2 megapiksele.
Jeśli szybkość skanowania jest ważna, możesz jeszcze bardziej obniżyć rozdzielczość obrazu. Pamiętaj jednak o minimalnym rozmiarze kodu kreskowego, o którym mowa powyżej.
- ograniczać wywołania do tego detektora. Jeśli podczas działania detektora pojawi się nowa klatka wideo, odrzuć ją.
- Jeśli używasz danych wyjściowych z detektora do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik z ML Kit, a potem wyrenderuj obraz i nałóż go w jednym kroku. W ten sposób renderujesz na powierzchni wyświetlacza tylko raz w przypadku każdej ramki wejściowej.
-
Jeśli używasz interfejsu Camera2 API, rób zdjęcia w formacie
ImageFormat.YUV_420_888
.Jeśli używasz starszej wersji interfejsu Camera API, rób zdjęcia w formacie
ImageFormat.NV21
.