Do rozpoznawania i dekodowania kodów kreskowych możesz używać pakietu ML Kit.
Zanim zaczniesz
- Dodaj Firebase do swojego projektu Android, chyba że masz to już za sobą.
- Dodaj zależności 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' }
Wytyczne dotyczące obrazu wejściowego
-
Aby ML Kit mógł dokładnie odczytywać kody kreskowe, obrazy wejściowe muszą zawierać kody kreskowe, które są reprezentowane przez wystarczającą ilość danych pikseli.
Konkretne wymagania dotyczące danych pikseli zależą od typu kodu kreskowego oraz ilości zakodowanych w nim danych (ponieważ większość kodów kreskowych obsługuje ładunek o zmiennej długości). Ogólnie najmniejsza istotna 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 słupków i spacji o szerokości 1, 2, 3 lub 4 jednostek. Na przykład obraz kodu kreskowego EAN-13 powinien zawierać słupki i przestrzenie o szerokości co najmniej 2, 4, 6 i 8 pikseli. Ponieważ kod kreskowy EAN-13 ma łącznie 95 jednostek, powinien mieć co najmniej 190 pikseli szerokości.
Formaty o większej gęstości, takie jak PDF417, wymagają większych wymiarów w pikselach, aby narzędzie ML Kit mogło je prawidłowo odczytywać. Na przykład kod PDF417 może zawierać w 1 wierszu do 34 słów o szerokości 17 jednostek, czyli co najmniej 1156 pikseli.
-
Słaba ostrość obrazu może obniżyć dokładność skanowania. Jeśli nie uzyskujesz akceptowalnych wyników, poproś użytkownika o ponowne przechwycenie obrazu.
-
W typowych zastosowaniach zalecamy przesłanie obrazu o wyższej rozdzielczości (np. 1280 x 720 lub 1920 x 1080), dzięki czemu kody kreskowe są wykrywalne z większej odległości od aparatu.
Jednak w aplikacjach, w których opóźnienia mają kluczowe znaczenie, możesz zwiększyć wydajność przez robienie obrazów w niższej rozdzielczości, gdy kod kreskowy powinien stanowić większość obrazu wejściowego. Zobacz też wskazówki, jak zwiększyć wydajność w czasie rzeczywistym.
1. Skonfiguruj wykrywacz kodów kreskowych
Jeśli wiesz, jakie formaty kodów kreskowych chcesz odczytywać, możesz zwiększyć szybkość wykrywania kodów kreskowych, konfigurując go tak, aby wykrywał tylko te formaty.Aby na przykład wykryć tylko kod Aztec i kody QR, utwórz obiekt FirebaseVisionBarcodeDetectorOptions
jak w tym 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 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
) - aztecki (
FORMAT_AZTEC
) - Macierz danych (
FORMAT_DATA_MATRIX
)
2. Uruchom wykrywacz kodów kreskowych
Aby rozpoznawać kody kreskowe na obrazie, utwórz obiektFirebaseVisionImage
na podstawie Bitmap
, media.Image
, ByteBuffer
, tablicy bajtów lub pliku na urządzeniu. Następnie przekaż obiekt FirebaseVisionImage
do metody detectInImage
instancji FirebaseVisionBarcodeDetector
.
Utwórz obiekt
FirebaseVisionImage
na podstawie swojego obrazu.-
Aby utworzyć obiekt
FirebaseVisionImage
na podstawie obiektumedia.Image
, na przykład podczas rejestrowania obrazu aparatem urządzenia, przekaż obiektmedia.Image
i obrót obrazu doFirebaseVisionImage.fromMediaImage()
.Jeśli używasz biblioteki AparatuX, klasy
OnImageCapturedListener
iImageAnalysis.Analyzer
obliczają za Ciebie wartość rotacji. Dlatego musisz tylko przekonwertować rotację na jedną ze stałych wartościROTATION_
zestawu 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 aparatu, która określa obrót obrazu, możesz obliczyć wartość obrotu urządzenia oraz orientacji czujnika aparatu 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ż doFirebaseVisionImage.fromFilePath()
kontekst aplikacji i identyfikator URI pliku. Jest to przydatne, gdy używasz intencjiACTION_GET_CONTENT
, aby zachęcić użytkownika do wybrania 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
na podstawieByteBuffer
lub tablicy bajtów, najpierw oblicz obrót obrazu w sposób opisany powyżej dla danych wejściowychmedia.Image
.Następnie utwórz obiekt
FirebaseVisionImageMetadata
, który zawiera 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
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin+KTX
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
musi być ustawiony pionowo i nie jest wymagany dodatkowy obrót.
-
Pobierz 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. Uzyskiwanie informacji z kodów kreskowych
Jeśli operacja rozpoznawania kodu kreskowego się powiedzie, do detektora sukcesu zostanie przekazana lista obiektówFirebaseVisionBarcode
. Każdy obiekt FirebaseVisionBarcode
reprezentuje kod kreskowy wykrytego na obrazie. Dla każdego kodu kreskowego można poznać współrzędne ograniczające z obrazu wejściowego, a także nieprzetworzone dane. Poza tym, jeśli detektor kodu kreskowego był w stanie określić typ danych zakodowanych przez ten kod, możesz uzyskać 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+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 dotyczące poprawy skuteczności w czasie rzeczywistym
Jeśli chcesz skanować kody kreskowe w aplikacji w czasie rzeczywistym, postępuj zgodnie z tymi wytycznymi, aby uzyskać najlepszą liczbę klatek na sekundę:
-
Nie rejestruj danych wejściowych w rozdzielczości natywnej kamery. Na niektórych urządzeniach przechwytywanie danych wejściowych w rozdzielczości natywnej powoduje uzyskanie bardzo dużych obrazów (ponad 10 megapikseli), co skutkuje bardzo małym opóźnieniem bez zwiększania dokładności. Zamiast tego żądaj od aparatu tylko wymaganego rozmiaru, który jest wymagany do wykrywania kodu kreskowego – zwykle nie więcej niż 2 megapiksele.
Jeśli szybkość skanowania jest ważna, możesz jeszcze bardziej zmniejszyć rozdzielczość przechwytywania obrazu. Należy jednak pamiętać o minimalnych wymaganiach dotyczących rozmiaru kodu kreskowego opisane powyżej.
- Ogranicz wywołania do detektora. Jeśli podczas działania detektora dostępna będzie nowa klatka wideo, upuść ją.
- Jeśli używasz danych wyjściowych detektora do nakładania grafiki na obraz wejściowy, najpierw pobierz wynik z ML Kit, a potem wyrenderuj obraz i nakładkę w jednym kroku. Dzięki temu renderowanie na powierzchni wyświetlania będzie odbywać się tylko raz na każdą klatkę wejściową.
-
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
.