Możesz użyć ML Kit do rozpoznawania i dekodowania kodów kreskowych.
Funkcja | Niegrupowane | Łączenie w pakiety |
---|---|---|
Implementacja | Model jest pobierany dynamicznie przez Usługi Google Play. | Model jest statycznie połączony z aplikacją w czasie kompilacji. |
Rozmiar aplikacji | Zwiększenie rozmiaru o około 200 KB. | Zwiększenie rozmiaru o około 2,4 MB. |
Czas inicjowania | Możliwe, że przed pierwszym użyciem trzeba będzie poczekać na pobranie modelu. | Model jest dostępny od razu. |
Wypróbuj
- Przetestuj przykładową aplikację, aby zobaczyć przykład użycia tego interfejsu API.
- Pełną implementację tego interfejsu API znajdziesz w aplikacji Material Design Show.
Zanim zaczniesz
Upewnij się, że w sekcji
buildscript
iallprojects
w plikubuild.gradle
Google na poziomie projektu znajduje się repozytorium Maven.Dodaj zależności dla bibliotek ML Kit na Androida do pliku Gradle na poziomie modułu. Zwykle ma on postać
app/build.gradle
. Wybierz jedną z tych zależności w zależności od potrzeb:Aby połączyć model w pakiet z aplikacją:
dependencies { // ... // Use this dependency to bundle the model with your app implementation 'com.google.mlkit:barcode-scanning:17.2.0' }
Aby używać modelu w Usługach Google Play:
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.3.0' }
Jeśli zdecydujesz się używać modelu w Usługach Google Play, możesz skonfigurować automatyczne pobieranie modelu przez aplikację po jej zainstalowaniu ze Sklepu Play. Aby to zrobić, dodaj tę deklarację do pliku
AndroidManifest.xml
aplikacji:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="barcode" > <!-- To use multiple models: android:value="barcode,model2,model3" --> </application>
Możesz też wyraźnie sprawdzić dostępność modelu i poprosić o pobranie za pomocą interfejsu ModuleInstallClient API z Usług Google Play.
Jeśli nie włączysz pobierania modelu podczas instalacji ani nie zażądasz bezpośredniego pobierania, model zostanie pobrany przy pierwszym uruchomieniu skanera. Żądania wysłane przed zakończeniem pobierania nie dają żadnych wyników.
Wskazówki dotyczące obrazu wejściowego
-
Aby narzędzie ML Kit mogło dokładnie odczytywać kody kreskowe, obrazy wejściowe muszą zawierać kody kreskowe reprezentowane przez wystarczającą ilość danych pikseli.
Konkretne wymagania dotyczące danych w pikselach zależą od typu kodu kreskowego i ilości zakodowanych w nim danych, ponieważ wiele takich kodów obsługuje ładunek o zmiennej wielkości. Ogólnie najmniejsza istotna jednostka kodu kreskowego powinna mieć co najmniej 2 piksele szerokości, a kodów dwuwymiarowych – 2 piksele wysokości.
Na przykład kody kreskowe EAN-13 składają się ze słupków o szerokości 1, 2, 3 lub 4 jednostek szerokości, dlatego obraz kodu kreskowego EAN-13 powinien mieć szerokość przynajmniej 2, 4, 6 i 8 pikseli. Ponieważ kod kreskowy EAN-13 ma łącznie 95 jednostek szerokości, powinien mieć co najmniej 190 pikseli szerokości.
Gęstsze formaty, takie jak PDF417, wymagają większych pikseli, aby pakiet ML Kit mógł je prawidłowo odczytać. Na przykład kod PDF417 może zawierać w jednym wierszu do 34 „słów” o szerokości 17 jednostek, co najlepiej wynosi co najmniej 1156 pikseli.
-
Słabe ostrość obrazu może zmniejszyć dokładność skanowania. Jeśli aplikacja nie uzyskuje zadowalających wyników, poproś użytkownika o ponowne zdjęcie obrazu.
-
W typowych zastosowaniach zalecane jest obrazy o wyższej rozdzielczości, na przykład 1280 x 720 lub 1920 x 1080, dzięki czemu kody kreskowe można skanować z większej odległości od aparatu.
Jednak w aplikacjach, w których opóźnienia mają kluczowe znaczenie, możesz poprawić wydajność, robiąc obrazy o 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. Skonfiguruj skaner kodów kreskowych
Jeśli wiesz, które formaty kodów kreskowych zamierzasz odczytywać, możesz zwiększyć szybkość wykrywacza 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 BarcodeScannerOptions
jak w tym przykładzie:
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();
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
) - aztecki (
FORMAT_AZTEC
) - Macierz danych (
FORMAT_DATA_MATRIX
)
Począwszy od modelu pakietu 17.1.0 w pakiecie oraz modelu 18.2.0 bez pakietu możesz też wywoływać metodę enableAllPotentialBarcodes()
, aby zwracać wszystkie potencjalne kody kreskowe nawet wtedy, gdy nie można ich odkodować. Może to ułatwić dalsze wykrywanie, np. przez powiększenie kamery w celu uzyskania wyraźniejszego obrazu kodu kreskowego w zwróconej ramce ograniczającej.
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .enableAllPotentialBarcodes() // Optional .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .enableAllPotentialBarcodes() // Optional .build();
Further on, starting from bundled library 17.2.0 and unbundled library 18.3.0, a new feature called auto-zoom has been introduced to further enhance the barcode scanning experience. With this feature enabled, the app is notified when all barcodes within the view are too distant for decoding. As a result, the app can effortlessly adjust the camera's zoom ratio to the recommended setting provided by the library, ensuring optimal focus and readability. This feature will significantly enhance the accuracy and success rate of barcode scanning, making it easier for apps to capture information precisely.
To enable auto-zooming and customize the experience, you can utilize the
setZoomSuggestionOptions()
method along with your
own ZoomCallback
handler and desired maximum zoom
ratio, as demonstrated in the code below.
Kotlin
val options = BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .setZoomSuggestionOptions( new ZoomSuggestionOptions.Builder(zoomCallback) .setMaxSupportedZoomRatio(maxSupportedZoomRatio) .build()) // Optional .build()
Java
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() .setBarcodeFormats(...) .setZoomSuggestionOptions( new ZoomSuggestionOptions.Builder(zoomCallback) .setMaxSupportedZoomRatio(maxSupportedZoomRatio) .build()) // Optional .build();
zoomCallback
is required to be provided to handle whenever the library
suggests a zoom should be performed and this callback will always be called on
the main thread.
The following code snippet shows an example of defining a simple callback.
Kotlin
fun setZoom(ZoomRatio: Float): Boolean { if (camera.isClosed()) return false camera.getCameraControl().setZoomRatio(zoomRatio) return true }
Java
boolean setZoom(float zoomRatio) { if (camera.isClosed()) { return false; } camera.getCameraControl().setZoomRatio(zoomRatio); return true; }
maxSupportedZoomRatio
is related to the camera hardware, and different camera
libraries have different ways to fetch it (see the javadoc of the setter
method). In case this is not provided, an
unbounded zoom ratio might be produced by the library which might not be
supported. Refer to the
setMaxSupportedZoomRatio()
method
introduction to see how to get the max supported zoom ratio with different
Camera libraries.
When auto-zooming is enabled and no barcodes are successfully decoded within
the view, BarcodeScanner
triggers your zoomCallback
with the requested
zoomRatio
. If the callback correctly adjusts the camera to this zoomRatio
,
it is highly probable that the most centered potential barcode will be decoded
and returned.
A barcode may remain undecodable even after a successful zoom-in. In such cases,
BarcodeScanner
may either invoke the callback for another round of zoom-in
until the maxSupportedZoomRatio
is reached, or provide an empty list (or a
list containing potential barcodes that were not decoded, if
enableAllPotentialBarcodes()
was called) to the OnSuccessListener
(which
will be defined in step 4. Process the image).
2. Prepare the input image
To recognize barcodes in an image, create anInputImage
object
from either a Bitmap
, media.Image
, ByteBuffer
, byte array, or a file on
the device. Then, pass the InputImage
object to the
BarcodeScanner
's process
method.
You can create an InputImage
object from different sources, each is explained below.
Using a media.Image
To create an InputImage
object from a media.Image
object, such as when you capture an image from a
device's camera, pass the media.Image
object and the image's
rotation to InputImage.fromMediaImage()
.
If you use the
CameraX library, the OnImageCapturedListener
and
ImageAnalysis.Analyzer
classes calculate the rotation value
for you.
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 // ... } } }
Jeśli nie korzystasz z biblioteki aparatu, która określa stopień obrotu obrazu, możesz ją obliczyć na podstawie stopnia obrotu urządzenia i orientacji czujnika aparatu:
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; }
Następnie przekaż obiekt media.Image
i wartość stopnia obrotu do InputImage.fromMediaImage()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Korzystanie z identyfikatora URI pliku
Aby utworzyć obiekt InputImage
na podstawie identyfikatora URI pliku, przekaż kontekst aplikacji i identyfikator URI pliku do InputImage.fromFilePath()
. Jest to przydatne, gdy używasz intencji ACTION_GET_CONTENT
, aby prosić użytkownika o wybranie obrazu z aplikacji galerii.
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(); }
Za pomocą: ByteBuffer
lub ByteArray
Aby utworzyć obiekt InputImage
na podstawie ByteBuffer
lub ByteArray
, najpierw oblicz stopień obrotu obrazu zgodnie z opisem powyżej dla danych wejściowych media.Image
.
Następnie utwórz obiekt InputImage
z buforem lub tablicą oraz podaj wysokość, szerokość, format kodowania kolorów i stopień obrotu obrazu:
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 );
Przy użyciu: Bitmap
Aby utworzyć obiekt InputImage
z obiektu Bitmap
, złóż tę deklarację:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
Obraz jest reprezentowany przez obiekt Bitmap
razem z obróconymi stopniami.
3. Pobieranie instancji aplikacji 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. Przetwarzanie obrazu
Przekaż obraz do metodyprocess
:
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. Uzyskaj informacje na podstawie kodów kreskowych
Jeśli operacja rozpoznawania kodu kreskowego się powiedzie, detektor sukcesu otrzyma listę obiektówBarcode
. Każdy obiekt Barcode
reprezentuje kod kreskowy wykryty na obrazie. W przypadku każdego kodu kreskowego możesz uzyskać współrzędne ograniczające zdjęcie, a także nieprzetworzone dane zakodowane za pomocą tego kodu. Jeśli skaner kodów kreskowych będzie w stanie określić typ danych kodowanych za pomocą tego kodu, możesz uzyskać obiekt zawierający przeanalizowane dane.
Na przykład:
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; } }
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 wskazówkami, aby uzyskać najlepszą liczbę klatek na sekundę:
-
Nie rejestruj danych wejściowych w natywnej rozdzielczości kamery. Na niektórych urządzeniach nagrywanie danych wejściowych w rozdzielczości natywnej tworzy wyjątkowo duże obrazy (ponad 10 megapikseli), co skutkuje bardzo małym opóźnieniem, a przy tym nie zwiększa dokładności. Zamiast tego żądaj z aparatu tylko rozmiaru wymaganego do wykrywania kodu kreskowego, który zwykle nie przekracza 2 megapikseli.
Jeśli zależy Ci na szybkości skanowania, możesz jeszcze bardziej obniżyć rozdzielczość przechwytywania obrazu. Pamiętaj jednak o opisanych powyżej minimalnych wymaganiach dotyczących rozmiaru kodu kreskowego.
Jeśli próbujesz rozpoznać kody kreskowe z sekwencji strumieniowanych klatek wideo, moduł rozpoznawania może pokazywać różne wyniki w zależności od klatki. Aby mieć pewność, że uzyskujesz dobry wynik, poczekaj, aż otrzymasz kolejną serię o tej samej wartości.
Cyfra sumy kontrolnej nie jest obsługiwana w przypadku ITF i CODE-39.
- Jeśli używasz interfejsu API
Camera
lubcamera2
, ogranicz wywołania wzorca do wykrywania treści. Jeśli podczas działania wzorca pojawi się nowa klatka wideo, upuść ją. Przykład znajdziesz w klasieVisionProcessorBase
w przykładowej aplikacji krótkiego wprowadzenia. - Jeśli korzystasz z interfejsu API
CameraX
, upewnij się, że strategia dotycząca ciśnienia wstecznego jest ustawiona na wartość domyślnąImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Gwarantuje to, że w danym momencie do analizy będzie dostarczany tylko 1 obraz. Jeśli w czasie, gdy analizator jest zajęty, zostanie utworzonych więcej obrazów, zostaną one automatycznie usunięte i nie zostaną umieszczone w kolejce do dostarczenia. Gdy analizowany obraz zostanie zamknięty przez wywołanie ImageProxy.close(), zostanie dostarczony następny najnowszy obraz. - Jeśli używasz danych wyjściowych detektora do nakładania grafiki na obraz wejściowy, najpierw pobierz wynik z ML Kit, a następnie wyrenderuj obraz i nakładkę w jednym kroku. Wyświetla się na powierzchni wyświetlacza tylko raz dla każdej klatki wejściowej. Przykład znajdziesz w klasach
CameraSourcePreview
iGraphicOverlay
w przykładowej aplikacji z krótkim wprowadzeniem. - Jeśli korzystasz z interfejsu Camera2 API, zrób zdjęcia w formacie
ImageFormat.YUV_420_888
. Jeśli używasz starszej wersji interfejsu Camera API, zrób zdjęcia w formacieImageFormat.NV21
.
O ile nie stwierdzono inaczej, treść tej strony jest objęta licencją Creative Commons – uznanie autorstwa 4.0, a fragmenty kodu są dostępne na licencji Apache 2.0. Szczegółowe informacje na ten temat zawierają zasady dotyczące witryny Google Developers. Java jest zastrzeżonym znakiem towarowym firmy Oracle i jej podmiotów stowarzyszonych.
Ostatnia aktualizacja: 2024-02-28 UTC.