Barcodes mit ML Kit unter Android scannen

Mit ML Kit können Sie Barcodes erkennen und decodieren.

FunktionNicht gebündeltGebündelt
ImplementierungDas Modell wird dynamisch über die Google Play-Dienste heruntergeladen.Das Modell wird zur Build-Zeit statisch mit Ihrer App verknüpft.
App-GrößeDie Größe nimmt um etwa 200 KB zu.Die Größe hat sich um etwa 2,4 MB erhöht.
InitialisierungszeitMöglicherweise müssen Sie warten, bis das Modell heruntergeladen wurde, bevor Sie es zum ersten Mal verwenden können.Das Modell ist sofort verfügbar.

Jetzt ausprobieren

Hinweis

  1. In die Datei build.gradle auf Projektebene muss das Maven-Repository von Google in die Abschnitte buildscript und allprojects aufgenommen werden.

  2. Fügen Sie die Abhängigkeiten für die ML Kit Android-Bibliotheken der Gradle-Datei auf App-Ebene Ihres Moduls hinzu. Diese Datei ist in der Regel app/build.gradle. Wählen Sie je nach Bedarf eine der folgenden Abhängigkeiten aus:

    Für das Bündeln des Modells mit Ihrer App:

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

    Für die Verwendung des Modells in den Google Play-Diensten:

    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.1'
    }
    
  3. Wenn Sie das Modell in Google Play-Diensten verwenden, können Sie Ihre App so konfigurieren, dass das Modell automatisch auf das Gerät heruntergeladen wird, nachdem Ihre App aus dem Play Store installiert wurde. Fügen Sie dazu der Datei AndroidManifest.xml Ihrer App die folgende Deklaration hinzu:

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="barcode" >
          <!-- To use multiple models: android:value="barcode,model2,model3" -->
    </application>
    

    Sie können die Modellverfügbarkeit auch explizit prüfen und den Download über die ModuleInstallClient API der Google Play-Dienste anfordern.

    Wenn Sie keine Modell-Downloads zur Installationszeit aktivieren oder keinen expliziten Download anfordern, wird das Modell beim ersten Ausführen des Scanners heruntergeladen. Anfragen, die Sie vor Abschluss des Downloads stellen, liefern keine Ergebnisse.

Richtlinien für Eingabebilder

  • Damit ML Kit Barcodes genau lesen kann, müssen Eingabebilder Barcodes enthalten, die durch ausreichend Pixeldaten dargestellt werden.

    Die spezifischen Anforderungen an die Pixeldaten hängen sowohl vom Typ des Barcodes als auch von der Menge der darin codierten Daten ab, da viele Barcodes eine Nutzlast variabler Größe unterstützen. Im Allgemeinen sollte die kleinste sinnvolle Einheit des Barcodes mindestens 2 Pixel breit und bei 2‑dimensionalen Codes mindestens 2 Pixel hoch sein.

    EAN‑13-Barcodes bestehen beispielsweise aus Strichen und Zwischenräumen mit einer Breite von 1, 2, 3 oder 4 Einheiten. Ein EAN‑13-Barcodebild sollte daher Striche und Zwischenräume mit einer Breite von mindestens 2, 4, 6 und 8 Pixeln haben. Da ein EAN‑13-Barcode insgesamt 95 Einheiten breit ist, sollte der Barcode mindestens 190 Pixel breit sein.

    Für dichtere Formate wie PDF417 sind größere Pixelabmessungen erforderlich, damit ML Kit sie zuverlässig lesen kann. Ein PDF417-Code kann beispielsweise bis zu 34 „Wörter“ mit einer Breite von 17 Einheiten in einer einzelnen Zeile haben, die idealerweise mindestens 1.156 Pixel breit sein sollte.

  • Eine schlechte Bildschärfe kann sich auf die Genauigkeit des Scans auswirken. Wenn Ihre App keine akzeptablen Ergebnisse liefert, bitten Sie den Nutzer, das Bild noch einmal aufzunehmen.

  • Für typische Anwendungen wird empfohlen, ein Bild mit höherer Auflösung wie 1280 × 720 oder 1920 × 1080 bereitzustellen, damit Barcodes aus größerer Entfernung von der Kamera gescannt werden können.

    In Anwendungen, in denen die Latenz entscheidend ist, können Sie die Leistung jedoch verbessern, indem Sie Bilder mit einer niedrigeren Auflösung aufnehmen, aber darauf achten, dass der Barcode den Großteil des Eingabebilds ausmacht. Weitere Informationen finden Sie unter Tipps zur Verbesserung der Echtzeitleistung.

1. Barcodescanner konfigurieren

Wenn Sie wissen, welche Barcodeformate Sie lesen möchten, können Sie die Geschwindigkeit des Barcode-Scanners verbessern, indem Sie ihn so konfigurieren, dass er nur diese Formate erkennt.

Wenn Sie beispielsweise nur Aztec- und QR-Codes erkennen möchten, erstellen Sie ein BarcodeScannerOptions-Objekt wie im folgenden Beispiel:

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

Die folgenden Formate werden unterstützt:

  • Code 128 (FORMAT_CODE_128)
  • Code 39 (FORMAT_CODE_39)
  • Code 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)
  • QR‑Code (FORMAT_QR_CODE)
  • PDF417 (FORMAT_PDF417)
  • Aztec (FORMAT_AZTEC)
  • Data Matrix (FORMAT_DATA_MATRIX)

Ab dem gebündelten Modell 17.1.0 und dem nicht gebündelten Modell 18.2.0 können Sie auch enableAllPotentialBarcodes() aufrufen, um alle potenziellen Barcodes zurückzugeben, auch wenn sie nicht decodiert werden können. Dies kann verwendet werden, um die weitere Erkennung zu erleichtern, z. B. durch Zoomen der Kamera, um ein klareres Bild eines Barcodes im zurückgegebenen Begrenzungsrahmen zu erhalten.

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 an InputImage 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
          // ...
        }
    }
}

Wenn Sie keine Kamerabibliothek verwenden, die den Drehwinkel des Bildes angibt, können Sie ihn aus dem Drehwinkel des Geräts und der Ausrichtung des Kamerasensors im Gerät berechnen:

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

Übergeben Sie dann das media.Image-Objekt und den Wert für den Drehwinkel an InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Datei-URI verwenden

Wenn Sie ein InputImage-Objekt aus einem Datei-URI erstellen möchten, übergeben Sie den App-Kontext und den Datei-URI an InputImage.fromFilePath(). Das ist nützlich, wenn Sie mit einem ACTION_GET_CONTENT-Intent den Nutzer auffordern, ein Bild aus seiner Galerie-App auszuwählen.

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

ByteBuffer oder ByteArray verwenden

Wenn Sie ein InputImage-Objekt aus einem ByteBuffer oder einem ByteArray erstellen möchten, berechnen Sie zuerst den Bildrotationsgrad wie zuvor für die media.Image-Eingabe beschrieben. Erstellen Sie dann das InputImage-Objekt mit dem Puffer oder Array sowie der Höhe, Breite, dem Farbcodierungsformat und dem Rotationsgrad des Bildes:

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

Mit einem Bitmap

So erstellen Sie ein InputImage-Objekt aus einem Bitmap-Objekt:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

Das Bild wird durch ein Bitmap-Objekt zusammen mit den Rotationsgraden dargestellt.

3. Instanz von BarcodeScanner abrufen

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. Bild verarbeiten

Übergeben Sie das Bild an die Methode 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
                // ...
            }
        });
in der Beispiel-App für die Kurzanleitung.

5. Informationen aus Barcodes abrufen

Wenn die Barcodeerkennung erfolgreich ist, wird eine Liste von Barcode-Objekten an den Erfolgs-Listener übergeben. Jedes Barcode-Objekt stellt einen Barcode dar, der im Bild erkannt wurde. Für jeden Barcode können Sie die Begrenzungskoordinaten im Eingabebild sowie die Rohdaten abrufen, die vom Barcode codiert werden. Wenn der Barcodescanner den Typ der im Barcode codierten Daten ermitteln konnte, können Sie außerdem ein Objekt mit geparsten Daten abrufen.

Beispiel:

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

Tipps zur Verbesserung der Echtzeitleistung

Wenn Sie Barcodes in einer Echtzeitanwendung scannen möchten, sollten Sie die folgenden Richtlinien beachten, um die besten Framerates zu erzielen:

  • Nehmen Sie keine Eingaben in der nativen Auflösung der Kamera auf. Auf einigen Geräten führt die Erfassung von Eingaben in der nativen Auflösung zu extrem großen Bildern (über 10 Megapixel), was zu einer sehr schlechten Latenz ohne Vorteile für die Genauigkeit führt. Fordern Sie stattdessen nur die Größe von der Kamera an, die für die Barcode-Erkennung erforderlich ist. Das sind in der Regel nicht mehr als 2 Megapixel.

    Wenn die Scangeschwindigkeit wichtig ist, können Sie die Auflösung für die Aufnahme von Bildern weiter verringern. Beachten Sie jedoch die oben genannten Mindestanforderungen für die Barcodegröße.

    Wenn Sie versuchen, Barcodes aus einer Sequenz von Streaming-Videoframes zu erkennen, kann der Erkennungsdienst von Frame zu Frame unterschiedliche Ergebnisse liefern. Warten Sie, bis Sie eine fortlaufende Reihe desselben Werts erhalten, um sicher zu sein, dass Sie ein gutes Ergebnis zurückgeben.

    Die Prüfziffer wird für ITF und CODE-39 nicht unterstützt.

  • Wenn Sie die API Camera oder camera2 verwenden, drosseln Sie die Aufrufe des Detektors. Wenn ein neuer Videoframes verfügbar wird, während der Detektor ausgeführt wird, verwerfen Sie den Frame. Ein Beispiel finden Sie in der Klasse VisionProcessorBase in der Beispiel-App für die Kurzanleitung.
  • Wenn Sie die CameraX API verwenden, muss die Backpressure-Strategie auf den Standardwert ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST festgelegt sein. So wird sichergestellt, dass jeweils nur ein Bild zur Analyse bereitgestellt wird. Wenn mehr Bilder erstellt werden, während der Analyzer beschäftigt ist, werden sie automatisch verworfen und nicht für die Übermittlung in die Warteschlange gestellt. Sobald das analysierte Bild durch Aufrufen von ImageProxy.close() geschlossen wird, wird das nächste aktuelle Bild bereitgestellt.
  • Wenn Sie die Ausgabe des Detektors verwenden, um Grafiken auf dem Eingabebild zu überlagern, rufen Sie zuerst das Ergebnis von ML Kit ab und rendern Sie dann das Bild und die Überlagerung in einem einzigen Schritt. Das Bild wird für jeden Eingabe-Frame nur einmal auf der Displayoberfläche gerendert. Ein Beispiel finden Sie in der Beispiel-App für die Kurzanleitung in den Klassen CameraSourcePreview und GraphicOverlay.
  • Wenn Sie die Camera2 API verwenden, nehmen Sie Bilder im ImageFormat.YUV_420_888-Format auf. Wenn Sie die ältere Camera API verwenden, nehmen Sie Bilder im ImageFormat.NV21-Format auf.