Za pomocą ML Kit możesz rozpoznawać i dekodować kody kreskowe.
Zanim zaczniesz
- Jeśli jeszcze tego nie zrobiłeś, dodaj Firebase do swojego projektu na Androida .
- Dodaj zależności dla bibliotek ML Kit Android 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' }
Wytyczne dotyczące obrazu wejściowego
Aby zestaw ML Kit mógł dokładnie odczytywać kody kreskowe, obrazy wejściowe muszą zawierać kody kreskowe reprezentowane przez wystarczającą ilość danych w pikselach.
Konkretne wymagania dotyczące danych pikselowych zależą zarówno od typu kodu kreskowego, jak i ilości danych w nim zakodowanych (ponieważ większość kodów kreskowych obsługuje ładunek o zmiennej długości). Ogólnie rzecz biorąc, najmniejsza znacząca jednostka kodu kreskowego powinna mieć co najmniej 2 piksele szerokości (a w przypadku kodów dwuwymiarowych – 2 piksele wysokości).
Na przykład kody kreskowe EAN-13 składają się z kresek i spacji o szerokości 1, 2, 3 lub 4 jednostek, więc obraz kodu kreskowego EAN-13 w idealnym przypadku zawiera kreski i spacje o długości co najmniej 2, 4, 6 i Szerokość 8 pikseli. Ponieważ kod kreskowy EAN-13 ma łącznie 95 jednostek szerokości, kod kreskowy powinien mieć szerokość co najmniej 190 pikseli.
Gęstsze formaty, takie jak PDF417, wymagają większych wymiarów w pikselach, aby ML Kit mógł je niezawodnie odczytać. Na przykład kod PDF417 może zawierać do 34 „słów” o szerokości 17 jednostek w jednym wierszu, co w idealnym przypadku powinno mieć co najmniej 1156 pikseli szerokości.
Słaba ostrość obrazu może obniżyć dokładność skanowania. Jeśli wyniki nie są akceptowalne, spróbuj poprosić użytkownika o ponowne wykonanie zdjęcia.
Do typowych zastosowań zaleca się podanie obrazu w wyższej rozdzielczości (np. 1280x720 lub 1920x1080), dzięki której kody kreskowe będą wykrywalne z większej odległości od kamery.
Jednakże w zastosowaniach, w których opóźnienie jest krytyczne, można poprawić wydajność, przechwytując obrazy w niższej rozdzielczości, ale wymagając, aby kod kreskowy stanowił większość obrazu wejściowego. Zobacz także Wskazówki dotyczące poprawy wydajności w czasie rzeczywistym .
1. Skonfiguruj detektor kodów kreskowych
Jeśli wiesz, jakich formatów kodów kreskowych chcesz czytać, możesz zwiększyć szybkość detektora kodów kreskowych, konfigurując go tak, aby wykrywał tylko te formaty. Na przykład, aby wykryć tylko kod Azteków i kody QR, zbuduj obiekt FirebaseVisionBarcodeDetectorOptions
jak w poniższym przykładzie:
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()
Obsługiwane są następujące formaty:
- Kod 128 (
FORMAT_CODE_128
) - Kod 39 (
FORMAT_CODE_39
) - Kod 93 (
FORMAT_CODE_93
) - Kodabar (
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
) - Aztek (
FORMAT_AZTEC
) - Macierz danych (
FORMAT_DATA_MATRIX
)
2. Uruchom detektor kodów kreskowych
Aby rozpoznać kody kreskowe na obrazie, utwórz obiektFirebaseVisionImage
z Bitmap
, media.Image
, ByteBuffer
, tablicy bajtów lub pliku na urządzeniu. Następnie przekaż obiekt FirebaseVisionImage
do metody detectInImage
FirebaseVisionBarcodeDetector
.Utwórz obiekt
FirebaseVisionImage
ze swojego obrazu.Aby utworzyć obiekt
FirebaseVisionImage
z obiektumedia.Image
, na przykład podczas przechwytywania obrazu z kamery urządzenia, przekaż obiektmedia.Image
i obrót obrazu doFirebaseVisionImage.fromMediaImage()
.Jeśli korzystasz z biblioteki CameraX , klasy
OnImageCapturedListener
iImageAnalysis.Analyzer
obliczają wartość rotacji za Ciebie, więc wystarczy przekonwertować rotację na jedną ze stałychROTATION_
ML Kit przed wywołaniemFirebaseVisionImage.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 // ... } } }
Jeśli nie korzystasz z biblioteki kamer, która umożliwia obrót obrazu, możesz go obliczyć na podstawie obrotu urządzenia i orientacji czujnika kamery w 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+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 }
Następnie przekaż obiekt
media.Image
i wartość rotacji doFirebaseVisionImage.fromMediaImage()
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
Kotlin+KTX
val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
- Aby utworzyć obiekt
FirebaseVisionImage
na podstawie identyfikatora URI pliku, przekaż kontekst aplikacji i identyfikator URI pliku doFirebaseVisionImage.fromFilePath()
. Jest to przydatne, gdy używasz intencjiACTION_GET_CONTENT
do monitowania użytkownika o wybranie obrazu z aplikacji galerii.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() }
- Aby utworzyć obiekt
FirebaseVisionImage
zByteBuffer
lub tablicy bajtów, najpierw oblicz obrót obrazu w sposób opisany powyżej dla wejściamedia.Image
.Następnie utwórz obiekt
FirebaseVisionImageMetadata
zawierający wysokość, szerokość, format kodowania kolorów i 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+KTX
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+KTX
val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata) // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
- Aby utworzyć obiekt
FirebaseVisionImage
z obiektuBitmap
:Obraz reprezentowany przez obiektJava
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin+KTX
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
musi być ustawiony pionowo i nie jest wymagany żaden dodatkowy obrót.
Uzyskaj instancję
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)
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+KTX
val result = detector.detectInImage(image) .addOnSuccessListener { barcodes -> // Task completed successfully // ... } .addOnFailureListener { // Task failed with an exception // ... }
3. Uzyskaj informacje z kodów kreskowych
Jeśli operacja rozpoznawania kodu kreskowego zakończy się pomyślnie, lista obiektówFirebaseVisionBarcode
zostanie przekazana do odbiornika powodzenia. Każdy obiekt FirebaseVisionBarcode
reprezentuje kod kreskowy wykryty na obrazie. Dla każdego kodu kreskowego można uzyskać jego współrzędne graniczne na obrazie wejściowym, a także surowe dane zakodowane w kodzie kreskowym. Ponadto, jeśli detektor kodów kreskowych był w stanie określić typ danych zakodowanych w kodzie kreskowym, można uzyskać obiekt zawierający przeanalizowane dane.Na 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+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 } } }
Wskazówki, jak poprawić wydajność w czasie rzeczywistym
Jeśli chcesz skanować kody kreskowe w aplikacji działającej w czasie rzeczywistym, postępuj zgodnie z poniższymi wskazówkami, aby uzyskać najlepszą liczbę klatek na sekundę:
Nie przechwytuj sygnału wejściowego w natywnej rozdzielczości kamery. Na niektórych urządzeniach przechwytywanie danych wejściowych w natywnej rozdzielczości powoduje powstanie niezwykle dużych obrazów (ponad 10 megapikseli), co skutkuje bardzo niskimi opóźnieniami bez korzyści w zakresie dokładności. Zamiast tego żądaj od aparatu tylko rozmiaru wymaganego do wykrywania kodów kreskowych: zwykle nie więcej niż 2 megapiksele.
Jeśli szybkość skanowania jest ważna, możesz jeszcze bardziej obniżyć rozdzielczość przechwytywania obrazu. Należy jednak pamiętać o wymaganiach dotyczących minimalnego rozmiaru kodu kreskowego opisanych powyżej.
- Przepustnica wzywa do detektora. Jeżeli w trakcie działania detektora pojawi się nowa klatka wideo, usuń ją.
- Jeśli używasz wyjścia detektora do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik z ML Kit, a następnie wyrenderuj obraz i nakładkę w jednym kroku. W ten sposób renderujesz na powierzchnię wyświetlacza tylko raz dla każdej klatki wejściowej.
Jeśli korzystasz z interfejsu API Camera2, przechwytuj obrazy w formacie
ImageFormat.YUV_420_888
.Jeśli używasz starszego interfejsu Camera API, przechwytuj obrazy w formacie
ImageFormat.NV21
.