Skanuj kody kreskowe za pomocą aplikacji ML Kit na Androida

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Możesz używać ML Kit do rozpoznawania i dekodowania kodów kreskowych.

FunkcjaNiegrupowaneŁączenie w pakiety
WdrażanieModel jest pobierany dynamicznie przez Usługi Google Play.Model jest statycznie połączony z aplikacją podczas kompilacji.
Rozmiar aplikacjiZwiększenie rozmiaru o około 200 KB.Zwiększenie rozmiaru o około 2,4 MB.
Czas inicjowaniaMoże minąć trochę czasu, zanim model zostanie pobrany.Model jest dostępny od razu.

Wypróbuj

Zanim zaczniesz

  1. W pliku build.gradle na poziomie projektu umieść repozytorium Google Maven w sekcjach buildscript i allprojects.

  2. Dodaj zależności bibliotek ML Kit na Androida do pliku Gradle na poziomie aplikacji, który zwykle wynosi app/build.gradle. W zależności od potrzeb wybierz jedną z zależności:

    Aby połączyć model z aplikacją:

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:barcode-scanning:17.0.3'
    }
    

    Do używania 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.1.0'
    }
    
  3. Jeśli zdecydujesz się używać modelu w Usługach Google Play, możesz skonfigurować aplikację tak, aby automatycznie pobierała ją na urządzenie po zainstalowaniu jej ze Sklepu Play. Aby to zrobić, dodaj do pliku AndroidManifest.xml aplikacji tę deklarację:

    <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ż bezpośrednio sprawdzić dostępność modelu i poprosić o jego pobranie, korzystając z interfejsu ModuleInstallClient API w Usługach Google Play.

    Jeśli nie włączysz pobierania modelu w czasie instalacji lub nie poprosisz o jego pobieranie, zostanie on pobrany przy pierwszym uruchomieniu skanera. Wysyłanie żądań, które zakończyły się, zanim wyniki zostały ukończone, nie zwraca żadnych wyników.

Wskazówki dotyczące obrazu wejściowego

  • Aby ML Kit 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żą od typu kodu kreskowego i ilości danych zakodowanych w nim, ponieważ wiele takich kodów obsługuje ładunki o zmiennej wielkości. Zasadniczo najmniejsza istotna jednostka kodu kreskowego powinna mieć co najmniej 2 piksele szerokości, a w przypadku kodów 2-wymiarowych – 2 piksele wysokości.

    Na przykład kody kreskowe EAN-13 składają się z pasków i pokoi o szerokości 1, 2, 3 lub 4 jednostek, dzięki czemu obraz z kodem EAN-13 powinien mieć linie o długości co najmniej 2, 4, 6 i 8 pikseli. Kod kreskowy EAN-13 ma łącznie 95 jednostek, dlatego powinien mieć szerokość co najmniej 190 pikseli.

    Formaty dengi, takie jak PDF417, potrzebują większych wymiarów pikseli, aby ML Kit mógł je prawidłowo odczytywać. Na przykład kod PDF może zawierać do 34 jednostek o szerokości 17 jednostek w jednym wierszu. Najlepiej, aby szerokość wynosiła co najmniej 1156 pikseli.

  • Słaba ostrość obrazu może mieć wpływ na dokładność skanowania. Jeśli aplikacja nie przynosi oczekiwanych wyników, poproś użytkownika o ponowne przechwycenie obrazu.

  • W przypadku typowych aplikacji zalecamy korzystanie z obrazów w wyższej rozdzielczości, takich jak 1280 x 720 lub 1920 x 1080, które pozwalają na skanowanie kodów kreskowych z większej odległości od aparatu.

    Jednak w aplikacjach, w których opóźnienie jest kluczowe, możesz poprawić wydajność, przechwytując obrazy w niższej rozdzielczości, ale wymagając, aby kod kreskowy stanowił większość obrazu wejściowego. Zapoznaj się też ze wskazówkami, jak poprawić skuteczność w czasie rzeczywistym.

1. Skonfiguruj skaner kodów kreskowych

Jeśli wiesz, które formaty kodów kreskowych chcesz odczytać, możesz zwiększyć szybkość jego wykrywania, konfigurując je tylko pod kątem tych formatów.

Aby na przykład wykrywać tylko kody Aztec i kody QR, utwórz obiekt BarcodeScannerOptions w następujący sposób:

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)

2. Przygotuj obraz wejściowy

Aby rozpoznawać kody kreskowe na obrazie, utwórz obiekt InputImage z Bitmap, media.Image, ByteBuffer, tablicy bajtów lub pliku na urządzeniu. Następnie przekaż obiekt InputImage do metody process obiektu BarcodeScanner.

Obiekt InputImage możesz utworzyć z różnych źródeł. Poniżej objaśniamy poszczególne z nich.

Korzystanie z: media.Image

Aby utworzyć obiekt InputImage z obiektu media.Image, na przykład podczas robienia zdjęcia aparatem urządzenia, przekaż obiekt media.Image i obrót obrazu do InputImage.fromMediaImage().

Jeśli używasz biblioteki Aparat X, klasy OnImageCapturedListener i ImageAnalysis.Analyzer obliczają wartość rotacji.

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 używasz biblioteki zdjęć, która zapewnia kąt obrotu zdjęcia, możesz obliczyć ją na podstawie stopnia obrotu urządzenia i orientacji czujnika aparatu w urządzeniu.

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ść stopni obrotu do InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

Używanie identyfikatora URI pliku

Aby utworzyć obiekt InputImage z identyfikatora URI pliku, przekaż kontekst aplikacji i identyfikator URI pliku do InputImage.fromFilePath(). Jest to przydatne, gdy użyjesz intencji ACTION_GET_CONTENT, aby poprosić użytkownika o wybranie obrazu z 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();
}

Korzystanie z ByteBuffer lub ByteArray

Aby utworzyć obiekt InputImage z ByteBuffer lub ByteArray, najpierw oblicz stopień obrotu obrazu, jak opisano wcześniej w przypadku danych wejściowych media.Image. Następnie utwórz obiekt InputImage z buforem lub tablicą wraz z wysokością, szerokością, formatem kolorów i stopniem rotacji:

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
);

Korzystanie z: 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 wraz ze stopniami obrotu.

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. Przetwórz obraz

Przekaż obraz za pomocą metody process:

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. Uzyskiwanie informacji z kodów kreskowych

Po rozpoznaniu kodu kreskowego lista obiektów Barcode zostanie przekazana do odbiornika. Każdy obiekt Barcode reprezentuje kod kreskowy, który został wykryty na obrazie. W przypadku każdego kodu kreskowego możesz uzyskać jego granice ograniczenia na obrazie wejściowym oraz nieprzetworzone dane zakodowane w kodzie kreskowym. Jeśli skaner kodów kreskowych potrafi określić typ danych zakodowanych przez kod paskowy, 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 zwiększania skuteczności w czasie rzeczywistym

Jeśli chcesz skanować kody kreskowe w aplikacji w czasie rzeczywistym, postępuj zgodnie z tymi wskazówkami, by uzyskać optymalną liczbę klatek na sekundę:

  • Nie przechwytuj danych wejściowych w natywnej rozdzielczości kamery. Na niektórych urządzeniach rejestrowanie danych w rozdzielczości natywnej generuje bardzo duże (ponad 10 megapikseli) zdjęcia, co przekłada się na bardzo małe opóźnienie i niekorzystny wpływ na dokładność. Wystarczy, że poprosisz o rozmiar z kamery, który jest wymagany 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. Pamiętaj jednak o minimalnych wymaganiach dotyczących kodu kreskowego opisanych powyżej.

    Jeżeli próbujesz rozpoznać kody kreskowe z sekwencji strumieniowej transmisji wideo, moduł rozpoznawania może zwracać różne wyniki z każdej klatki. Zaczekaj, aż otrzymasz kolejną serię o tej samej wartości, by mieć pewność, że zwracasz dobry wynik.

    Cyfra sumy kontrolnej nie jest obsługiwana w przypadku ITF i CODE-39.

  • Jeśli używasz interfejsu API Camera lub camera2, ograniczaj wywołania do wzorca do wykrywania treści. Jeśli po uruchomieniu wzorca do wykrywania filmów dostępna będzie nowa ramka, puść ją. Przykład znajdziesz w klasie VisionProcessorBase w przykładowej aplikacji krótkiego wprowadzenia.
  • Jeśli korzystasz z interfejsu API CameraX, sprawdź, czy strategia pushpress ma ustawioną wartość domyślną ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Gwarantuje to, że w danym momencie do analizy zostanie wysłany tylko 1 obraz. Jeśli analizator jest zajęty, więcej obrazów jest usuwanych, a następnie usuwanych automatycznie i nie znajdujących się w kolejce w celu dostarczenia. Gdy analizowany obraz zostanie zamknięty, wywołany jest obiekt ImageProxy.close(). Pojawi się następny obraz.
  • Jeśli używasz danych wyjściowych wzorca do nakładania grafiki na obrazie wejściowym, najpierw pobierz wynik z ML Kit, a następnie wyrenderuj obraz i nakładkę w jednym kroku. Renderuje się na wyświetlaczu tylko raz dla każdej klatki wejściowej. Przykład znajdziesz w klasach CameraSourcePreview i GraphicOverlay w przykładowej aplikacji krótkiego wprowadzenia.
  • Jeśli używasz interfejsu Camera2 API, zrób zdjęcia w formacie ImageFormat.YUV_420_888. Jeśli używasz starszego interfejsu API aparatu, zrób zdjęcia w formacie ImageFormat.NV21.