Barcodes mit ML Kit unter Android scannen

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

FunktionEntbündeltGebündelt
ImplementierungDas Modell wird über die Google Play-Dienste dynamisch heruntergeladen.Das Modell ist zum Zeitpunkt der Build-Erstellung statisch mit Ihrer App verknüpft.
App-GrößeGröße um ca. 200 KB erhöht.Die Größe erhöht sich um ca.2,4 MB.
InitialisierungszeitMöglicherweise müssen Sie vor der ersten Verwendung warten, bis das Modell heruntergeladen ist.Das Modell ist sofort verfügbar.

Ausprobieren

Hinweis

  1. Fügen Sie in der Datei build.gradle auf Projektebene das Maven-Repository von Google in den Abschnitten buildscript und allprojects ein.

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

    So bündeln Sie das Modell mit Ihrer App:

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

    Zur 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.0'
    }
    
  3. Wenn Sie das Modell in den Google Play-Diensten verwenden, können Sie Ihre App so konfigurieren, dass das Modell nach der Installation aus dem Play Store automatisch auf das Gerät heruntergeladen wird. 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 über die ModuleInstallClient API der Google Play-Dienste prüfen und den Download anfordern.

    Wenn Sie das Herunterladen von Modellen zum Zeitpunkt der Installation nicht aktivieren oder einen expliziten Download anfordern, wird das Modell beim ersten Ausführen des Scanners heruntergeladen. Anfragen, die Sie vor dem Abschluss des Downloads stellen, generieren keine Ergebnisse.

Richtlinien für Eingabebilder

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

    Die spezifischen Pixeldatenanforderungen hängen sowohl vom Typ des Barcodes als auch von der darin codierten Datenmenge ab, da viele Barcodes Nutzlasten mit variabler Größe unterstützen. Im Allgemeinen sollte die kleinste sinnvolle Einheit des Barcodes mindestens 2 Pixel breit und bei zweidimensionalen Codes 2 Pixel hoch sein.

    EAN-13-Barcodes bestehen beispielsweise aus Balken und Leerzeichen, die 1, 2, 3 oder 4 Einheiten breit sind, sodass ein EAN-13-Barcodebild idealerweise Balken und Leerzeichen enthält, die mindestens 2, 4, 6 und 8 Pixel breit sind. Da ein EAN-13-Barcode insgesamt 95 Einheiten breit ist, sollte der Barcode mindestens 190 Pixel breit sein.

    Dichte Formate wie PDF417 benötigen größere Pixelabmessungen, damit ML Kit sie zuverlässig lesen kann. Ein PDF417-Code kann beispielsweise bis zu 34 „Wörter“ mit jeweils 17 Einheiten in einer einzelnen Zeile enthalten, idealerweise also mindestens 1.156 Pixel breit.

  • Ein schlechter Bildfokus kann die Scangenauigkeit beeinträchtigen. Wenn Ihre Anwendung keine akzeptablen Ergebnisse erzielt, bitten Sie den Nutzer, das Bild noch einmal aufzunehmen.

  • Für typische Anwendungen wird empfohlen, Bilder mit höherer Auflösung wie 1.280 × 720 oder 1.920 × 1.080 zu verwenden, damit Barcodes auch aus größerer Entfernung von der Kamera gescannt werden können.

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

1. Barcode-Scanner konfigurieren

Wenn Sie wissen, welche Barcodeformate Sie lesen möchten, können Sie die Geschwindigkeit des Barcodedetektors verbessern, indem Sie ihn so konfigurieren, dass nur diese Formate erkannt werden.

Wenn Sie beispielsweise nur Aztec-Code 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)
  • Azteken (FORMAT_AZTEC)
  • Datenmatrix (FORMAT_DATA_MATRIX)

Ab dem gebündelten Modell 17.1.0 und dem entbündelten Modell 18.2.0 können Sie auch enableAllPotentialBarcodes() aufrufen, um alle potenziellen Barcodes zurückzugeben, auch wenn diese nicht decodiert werden können. Dies kann zur weiteren Erkennung verwendet werden, z. B. durch Heranzoomen der Kamera, um ein klareres Bild jedes 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 Drehgrad des Bildes angibt, können Sie ihn anhand des Gerätedrehungsgrads 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 Rotationsgrad an InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Datei-URI verwenden

Übergeben Sie den App-Kontext und den Datei-URI an InputImage.fromFilePath(), um ein InputImage-Objekt aus einem Datei-URI zu erstellen. Das ist nützlich, wenn du mit einem ACTION_GET_CONTENT-Intent den Nutzer auffordern möchtest, 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();
}

Mithilfe von ByteBuffer oder ByteArray

Zum Erstellen eines InputImage-Objekts aus einem ByteBuffer- oder ByteArray-Objekt müssen Sie zuerst den Grad der Bilddrehung berechnen, wie zuvor für die media.Image-Eingabe beschrieben. Erstellen Sie dann das InputImage-Objekt mit dem Zwischenspeicher oder Array sowie der Höhe, Breite, Farbcodierungsformat und 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

Mit der folgenden Deklaration kannst du ein InputImage-Objekt aus einem Bitmap-Objekt erstellen:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

Das Bild wird durch ein Bitmap-Objekt zusammen mit Grad der Drehung dargestellt.

3. BarcodeScanner-Instanz 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
                // ...
            }
        });

5. Informationen von Barcodes abrufen

Wenn die Barcodeerkennung erfolgreich ist, wird eine Liste mit Barcode-Objekten an den Erfolgs-Listener übergeben. Jedes Barcode-Objekt steht für einen Barcode, der im Bild erkannt wurde. Für jeden Barcode können Sie seine Begrenzungskoordinaten im Eingabebild sowie die durch den Barcode codierten Rohdaten abrufen. Wenn der Barcode-Scanner den Typ der vom Barcode codierten Daten ermitteln konnte, können Sie außerdem ein Objekt abrufen, das geparste Daten enthält.

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, beachten Sie die folgenden Richtlinien, um die besten Framerates zu erzielen:

  • Erfassen Sie keine Eingaben mit der nativen Auflösung der Kamera. Auf einigen Geräten werden durch das Erfassen der Eingabe in der nativen Auflösung extrem große Bilder (mehr als 10 Megapixel) erzeugt, was zu einer sehr schlechten Latenz führt, ohne dass die Genauigkeit verbessert wird. Fordern Sie stattdessen nur die Größe von der Kamera an, die für die Barcodeerkennung erforderlich ist (in der Regel nicht mehr als 2 Megapixel).

    Wenn die Scangeschwindigkeit wichtig ist, können Sie die Auflösung der Bildaufnahme weiter verringern. Beachten Sie jedoch die oben beschriebenen Mindestanforderungen an die Barcodegröße.

    Wenn Sie versuchen, Barcodes aus einer Abfolge von gestreamten Videoframes zu erkennen, kann die Erkennung von Frame zu Frame unterschiedliche Ergebnisse liefern. Sie sollten warten, bis Sie eine aufeinanderfolgende Reihe mit demselben Wert erhalten, um sicher zu sein, dass Sie ein gutes Ergebnis liefern.

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

  • Wenn Sie die Camera oder camera2 API verwenden, drosseln Sie Aufrufe an den Detektor. Wenn ein neuer Videoframe verfügbar ist, während der Detektor ausgeführt wird, löschen Sie den Frame. Ein Beispiel hierfür finden Sie in der Beispiel-App der Kurzanleitung in der Klasse VisionProcessorBase.
  • Wenn Sie die CameraX API verwenden, muss die Rückdruckstrategie auf den Standardwert ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST festgelegt sein. Dadurch wird sichergestellt, dass jeweils nur ein Bild zur Analyse geliefert wird. Werden weitere Bilder erstellt, während das Analysetool ausgelastet ist, werden diese automatisch gelöscht und nicht in die Warteschlange gestellt. Nachdem das zu analysierende Bild durch Aufrufen von ImageProxy.close() geschlossen wurde, wird das jeweils neueste Bild bereitgestellt.
  • Wenn Sie die Ausgabe des Detektors verwenden, um Grafiken über das Eingabebild einzublenden, rufen Sie zuerst das Ergebnis aus dem ML Kit ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. Dies wird für jeden Eingabeframe nur einmal auf der Anzeigeoberfläche gerendert. Ein entsprechendes Beispiel finden Sie in der Beispiel-App der Kurzanleitung in den Klassen CameraSourcePreview und GraphicOverlay.
  • Wenn du die Camera2 API verwendest, nimm Bilder im ImageFormat.YUV_420_888-Format auf. Wenn du die ältere Camera API verwendest, nimm Bilder im ImageFormat.NV21-Format auf.