Barcodes mit ML Kit unter Android scannen

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

FunktionNicht in PaketenGebündelt
ImplementierungDas Modell wird dynamisch über die Google Play-Dienste heruntergeladen.Das Modell wird während der Buildzeit statisch mit Ihrer App verknüpft.
App-GrößeDie Größe erhöht sich um etwa 200 KB.Die Größe erhöht sich um etwa 2,4 MB.
InitialisierungszeitMöglicherweise müssen Sie warten, bis das Modell heruntergeladen wurde, bevor Sie es 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 Ihres Moduls auf App-Ebene hinzu, 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.3.0'
    }
    

    Verwendung des Modells in 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 Verfügbarkeit des Modells auch explizit prüfen und den Download über die ModuleInstallClient API von Google Play-Diensten anfordern.

    Wenn Sie keine Modelldownloads bei der Installation aktivieren oder einen expliziten Download anfordern, wird das Modell beim ersten Ausführen des Scanners heruntergeladen. Anfragen, die Sie vor Abschluss des Downloads stellen, führen zu keinen Ergebnissen.

Richtlinien für Eingabebilder

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

    Die spezifischen Anforderungen an die Pixeldaten hängen sowohl vom Barcodetyp als auch von der Menge der darin codierten Daten ab, da viele Barcodes eine variable Nutzlast unterstützen. Im Allgemeinen sollte die kleinste aussagekräftige Einheit des Barcodes mindestens 2 Pixel breit und bei zweidimensionalen Codes 2 Pixel hoch sein.

    EAN-13-Barcodes bestehen beispielsweise aus Balken und Leerräumen, die 1, 2, 3 oder 4 Einheiten breit sind. Ein EAN-13-Barcodebild sollte daher idealerweise Balken und Leerräume mit einer Breite von mindestens 2, 4, 6 und 8 Pixeln haben. Da ein EAN-13-Barcode insgesamt 95 Einheiten breit ist, sollte er 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 17 Einheiten in einer einzigen Zeile enthalten, die idealerweise mindestens 1.156 Pixel breit sein sollte.

  • Ein unscharfer Bildfokus kann sich auf die Scangenauigkeit auswirken. Wenn Ihre App keine zufriedenstellenden 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. So können Barcodes aus größerer Entfernung von der Kamera gescannt werden.

    Bei Anwendungen, bei denen die Latenz entscheidend ist, können Sie die Leistung jedoch verbessern, indem Sie Bilder mit niedrigerer Auflösung aufnehmen, wobei der Barcode den Großteil des Eingabebilds ausmachen muss. Weitere Informationen finden Sie unter Tipps zur Verbesserung der Echtzeitleistung.

1. Barcodescanner konfigurieren

Wenn Sie wissen, welche Barcodeformate gelesen werden sollen, können Sie die Geschwindigkeit des Barcode-Detektors 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)
  • 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. So lässt sich die weitere Erkennung erleichtern, z. B. durch Heranzoomen der Kamera, um ein klareres Bild von 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 anhand des Drehwinkels 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 Drehungsgrad an InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Datei-URI verwenden

Wenn du ein InputImage-Objekt aus einem Datei-URI erstellen möchtest, übergebe den App-Kontext und den Datei-URI an InputImage.fromFilePath(). Das ist nützlich, wenn Sie mit einer 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();
}

Mit einem ByteBuffer oder ByteArray

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

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

Wenn Sie ein InputImage-Objekt aus einem Bitmap-Objekt erstellen möchten, verwenden Sie die folgende Deklaration:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

Das Bild wird durch ein Bitmap-Objekt zusammen mit den Drehgraden 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 process-Methode:

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 aus Barcodes abrufen

Wenn die Barcodeerkennung erfolgreich war, wird dem Erfolgsempfänger eine Liste von Barcode-Objekten übergeben. Jedes Barcode-Objekt steht für einen Barcode, der im Bild erkannt wurde. Für jeden Barcode können Sie die Begrenzungskoordinaten im Eingabebild sowie die vom Barcode codierten Rohdaten abrufen. Wenn der Barcodescanner den Typ der vom Barcode codierten Daten ermitteln konnte, können Sie auch 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, beachten Sie die folgenden Richtlinien, um die beste Framerate zu erzielen:

  • Erfassen Sie die Eingabe nicht mit der nativen Auflösung der Kamera. Auf einigen Geräten führt die Erfassung der Eingabe in der nativen Auflösung zu extrem großen Bildern (mehr als 10 Megapixel), 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. Diese beträgt in der Regel nicht mehr als 2 Megapixel.

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

    Wenn Sie versuchen, Barcodes aus einer Sequenz von gestreamten Videoframes zu erkennen, kann der Recognizer von Frame zu Frame unterschiedliche Ergebnisse liefern. Sie sollten warten, bis Sie eine fortlaufende Reihe desselben Werts erhalten, um sicherzugehen, dass Sie ein gutes Ergebnis erhalten.

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

  • Wenn Sie die Camera- oder camera2-API verwenden, begrenzen Sie die Aufrufe an den Detektor. Wenn während der Laufzeit des Detektors ein neuer Videoframe verfügbar wird, legen Sie ihn ab. Ein Beispiel finden Sie in der Klasse VisionProcessorBase in der Beispiel-App für die Schnellstartanleitung.
  • Wenn Sie die CameraX API verwenden, muss die Backpressure-Strategie auf den Standardwert ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST festgelegt sein. So wird sichergestellt, dass immer nur ein Bild zur Analyse gesendet wird. Wenn mehr Bilder erstellt werden, während der Analyser beschäftigt ist, werden sie automatisch gelöscht und nicht für die Übermittlung in die Warteschlange gestellt. Sobald das analysierte Bild durch Aufrufen von ImageProxy.close() geschlossen wurde, wird das nächste aktuelle Bild gesendet.
  • Wenn Sie die Ausgabe des Detektors verwenden, um Grafiken auf das Eingabebild zu legen, rufen Sie zuerst das Ergebnis aus ML Kit ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. Dieser wird nur einmal pro Eingabeframe auf der Anzeigeoberfläche gerendert. Eines dieser Beispiele finden Sie in der Beispiel-App für den Schnellstart in den Klassen CameraSourcePreview und GraphicOverlay.
  • Wenn Sie die Camera2 API verwenden, sollten Sie Bilder im ImageFormat.YUV_420_888-Format aufnehmen. Wenn Sie die ältere Camera API verwenden, nehmen Sie Bilder im ImageFormat.NV21-Format auf.