Do rozpoznawania i dekodowania kodów kreskowych możesz używać pakietu ML Kit.
Zanim zaczniesz
- Jeśli jeszcze nie masz tego za sobą, dodaj Firebase do swojego projektu na Androida.
- Dodaj do modułu zależności między bibliotekami ML Kit na Androida
Plik Gradle (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ć również 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 obsługują ładunek o zmiennej długości). Ogólnie rzecz biorąc, najmniej istotne musi mieć co najmniej 2 piksele szerokości (oraz dwuwymiarowych kodów o wysokości 2 pikseli).
Na przykład kody kreskowe EAN-13 składają się z kresek i spacji oznaczonych znakiem 1, 2, 3 lub 4 jednostki szerokości, więc na obrazie z kodem kreskowym EAN-13 powinny być słupki i o szerokości co najmniej 2, 4, 6 lub 8 pikseli. Ponieważ kod EAN-13 Kod kreskowy ma łącznie 95 jednostek szerokości, a kod kreskowy powinien mieć co najmniej 190 jednostek. pikseli szerokości ekranu.
Formaty o większej gęstości, np. PDF417, wymagają większych wymiarów w pikselach ML Kit. Na przykład kod PDF417 może mieć maksymalnie 34 „słowa” o szerokości 17 jednostek w jednym wierszu, czyli przynajmniej 1156 pikseli szerokości.
-
Słaba ostrość obrazu może obniżyć dokładność skanowania. Jeśli nie otrzymujesz akceptowalne wyniki, spróbuj poprosić użytkownika o ponowne przechwycenie obrazu.
-
W typowych zastosowaniach zaleca się podanie wyższego obrazu o rozdzielczości (np. 1280 x 720 lub 1920 x 1080), który tworzy kody kreskowe wykrywalny z większej odległości od kamery.
Jednak w aplikacjach, w których opóźnienia są kluczowe, można poprawić dzięki możliwości robienia zdjęć w niższej rozdzielczości, ale wymagając kod kreskowy stanowi większość zdjęcia wejściowego. Zobacz też Wskazówki, jak zwiększyć skuteczność w czasie rzeczywistym.
1. Skonfiguruj wykrywacz kodów kreskowych
Wiedząc, jakie formaty kodu kreskowego spodziewasz się odczytać, możesz zwiększyć szybkość detektora kodów kreskowych, konfigurując go tak, aby wykrywał tylko te formaty.Na przykład, aby wykrywać tylko kody Aztec i kody QR, utwórz
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
z obiektu Bitmap
, media.Image
, ByteBuffer
, tablicy bajtów lub pliku w
urządzenia. Następnie przekaż obiekt FirebaseVisionImage
do funkcji
Metoda detectInImage
użytkownika FirebaseVisionBarcodeDetector
.
Utwórz obiekt
FirebaseVisionImage
na podstawie swojego obrazu.-
Aby utworzyć obiekt
FirebaseVisionImage
na podstawiemedia.Image
, np. podczas przechwytywania obrazu z z aparatu urządzenia, przekazać obiektmedia.Image
oraz w kierunkuFirebaseVisionImage.fromMediaImage()
.Jeśli używasz tagu CameraX,
OnImageCapturedListener
orazImageAnalysis.Analyzer
klasy obliczają wartość rotacji więc wystarczy zmienić rotację na jeden z zestawów ML Kit StałyROTATION_
przed nawiązaniem połączeniaFirebaseVisionImage.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 zapewniającej obrót obrazu, może go obliczyć na podstawie obrotu urządzenia i orientacji aparatu czujnik 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
oraz 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 plikuFirebaseVisionImage.fromFilePath()
Jest to przydatne, gdy użyj intencjiACTION_GET_CONTENT
, aby zachęcić użytkownika do wyboru obraz z aplikacji Galeria.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 wartość obrazu w sposób opisany powyżej dla danych wejściowychmedia.Image
.Następnie utwórz obiekt
FirebaseVisionImageMetadata
określającą wysokość, szerokość i format kodowania kolorów obrazu i rotacja: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()
Za pomocą bufora lub tablicy oraz obiektu metadanych utwórz
FirebaseVisionImage
obiekt: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
na podstawie ObiektBitmap
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin+KTX
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
musi być pionowo bez konieczności dodatkowego obracania.
-
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, pojawi się lista ObiektyFirebaseVisionBarcode
zostaną przekazane detektorowi sukcesu. Każdy
FirebaseVisionBarcode
obiekt reprezentuje kod kreskowy wykryty w
. Dla każdego kodu kreskowego można uzyskać współrzędne ograniczające dla każdego kodu kreskowego
jak i nieprzetworzone dane zakodowane przez kod kreskowy. Jeśli kod kreskowy
udało się określić typ danych zakodowanych przez kod kreskowy, można
pobierz 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 za pomocą aplikacji działającej w czasie rzeczywistym, postępuj zgodnie z tymi wytycznych dotyczących uzyskiwania najlepszej liczby 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 zapewnia bardzo duże w megapikselach), co skutkuje bardzo małym opóźnieniem bez dokładności. Zamiast tego żądaj od aparatu tylko wymaganego rozmiaru dla wykrywania kodu kreskowego: zwykle nie więcej niż 2 megapiksele.
Jeśli szybkość skanowania jest ważna, możesz zmniejszyć zakres skanowania i ich rozwiązania. Należy jednak pamiętać o minimalnym rozmiarze kodu kreskowego. opisane powyżej.
- Ogranicz wywołania do detektora. Jeśli nowa klatka wideo dostępnych, gdy detektor jest uruchomiony, upuść ramkę.
- Jeśli używasz danych wyjściowych detektora do nakładania grafiki na obrazu wejściowego, najpierw pobierz wynik z ML Kit, a następnie wyrenderuj obraz i nakładanie nakładek w jednym kroku. W ten sposób renderowanie na powierzchni tylko raz na każdą ramkę wejściową.
-
Jeśli korzystasz z interfejsu API Camera2, rób zdjęcia w Format:
ImageFormat.YUV_420_888
.Jeśli używasz starszej wersji interfejsu Camera API, rób zdjęcia w Format:
ImageFormat.NV21
.