Sie können ML Kit verwenden, um Barcodes zu erkennen und zu dekodieren.
Bevor Sie beginnen
- Fügen Sie Ihrem Android-Projekt Firebase hinzu, falls Sie dies noch nicht getan haben .
- Fügen Sie die Abhängigkeiten für die ML Kit-Android-Bibliotheken zu Ihrer Modul-Gradle-Datei (auf App-Ebene) hinzu (normalerweise
app/build.gradle
):apply plugin: 'com.android.application' apply plugin: 'com.google.gms.google-services' dependencies { // ... implementation 'com.google.firebase:firebase-ml-vision:24.0.3' implementation 'com.google.firebase:firebase-ml-vision-barcode-model:16.0.1' }
Bildrichtlinien eingeben
Damit ML Kit Strichcodes genau lesen kann, müssen Eingabebilder Strichcodes enthalten, die durch ausreichend Pixeldaten dargestellt werden.
Die spezifischen Pixeldatenanforderungen hängen sowohl vom Strichcodetyp als auch von der darin codierten Datenmenge ab (da die meisten Strichcodes eine Nutzlast mit variabler Länge unterstützen). Im Allgemeinen sollte die kleinste sinnvolle Einheit des Barcodes mindestens 2 Pixel breit sein (und bei 2-dimensionalen Codes 2 Pixel hoch).
Beispielsweise bestehen EAN-13-Barcodes aus Strichen und Zwischenräumen mit einer Breite von 1, 2, 3 oder 4 Einheiten, sodass ein EAN-13-Barcodebild idealerweise Striche und Zwischenräume mit einer Breite von mindestens 2, 4, 6 und hat 8 Pixel breit. Da ein EAN-13-Barcode insgesamt 95 Einheiten breit ist, sollte der Barcode mindestens 190 Pixel breit sein.
Dichtere Formate wie PDF417 benötigen größere Pixelabmessungen, damit ML Kit sie zuverlässig lesen kann. Beispielsweise kann ein PDF417-Code bis zu 34 17-Einheiten breite "Wörter" in einer einzelnen Zeile haben, die idealerweise mindestens 1156 Pixel breit wäre.
Ein schlechter Bildfokus kann die Scangenauigkeit beeinträchtigen. Wenn Sie keine akzeptablen Ergebnisse erzielen, bitten Sie den Benutzer, das Bild erneut aufzunehmen.
Für typische Anwendungen wird empfohlen, ein Bild mit höherer Auflösung (z. B. 1280 x 720 oder 1920 x 1080) bereitzustellen, wodurch Barcodes aus größerer Entfernung von der Kamera erkennbar sind.
Bei Anwendungen, bei denen die Latenzzeit kritisch ist, können Sie die Leistung jedoch verbessern, indem Sie Bilder mit einer niedrigeren Auflösung erfassen, aber erfordern, dass der Barcode den Großteil des Eingabebildes ausmacht. Siehe auch Tipps zur Verbesserung der Echtzeitleistung .
1. Konfigurieren Sie den Barcode-Detektor
Wenn Sie wissen, welche Barcode-Formate Sie lesen möchten, können Sie die Geschwindigkeit des Barcode-Detektors verbessern, indem Sie ihn so konfigurieren, dass er nur diese Formate erkennt. Um beispielsweise nur Aztec-Code und QR-Codes zu erkennen, erstellen Sie ein FirebaseVisionBarcodeDetectorOptions
-Objekt wie im folgenden Beispiel:
Java
FirebaseVisionBarcodeDetectorOptions options = new FirebaseVisionBarcodeDetectorOptions.Builder() .setBarcodeFormats( FirebaseVisionBarcode.FORMAT_QR_CODE, FirebaseVisionBarcode.FORMAT_AZTEC) .build();
Kotlin+KTX
val options = FirebaseVisionBarcodeDetectorOptions.Builder() .setBarcodeFormats( FirebaseVisionBarcode.FORMAT_QR_CODE, FirebaseVisionBarcode.FORMAT_AZTEC) .build()
Folgende 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
) - Aztekisch (
FORMAT_AZTEC
) - Datenmatrix (
FORMAT_DATA_MATRIX
)
2. Führen Sie den Barcode-Detektor aus
Um Barcodes in einem Bild zu erkennen, erstellen Sie einFirebaseVisionImage
-Objekt entweder aus einem Bitmap
, media.Image
, ByteBuffer
, einem Byte-Array oder einer Datei auf dem Gerät. Übergeben Sie dann das FirebaseVisionImage
-Objekt an die Methode detectInImage
von FirebaseVisionBarcodeDetector
.Erstellen Sie ein
FirebaseVisionImage
-Objekt aus Ihrem Bild.Um ein
FirebaseVisionImage
-Objekt aus einemmedia.Image
Objekt zu erstellen, beispielsweise beim Erfassen eines Bildes von der Kamera eines Geräts, übergeben Sie dasmedia.Image
Objekt und die Drehung des Bilds anFirebaseVisionImage.fromMediaImage()
.Wenn Sie die CameraX- Bibliothek verwenden, berechnen die Klassen
OnImageCapturedListener
undImageAnalysis.Analyzer
den Rotationswert für Sie, sodass Sie die Rotation nur in eine derROTATION_
-Konstanten von ML Kit konvertieren müssen, bevor SieFirebaseVisionImage.fromMediaImage()
aufrufen:Java
private class YourAnalyzer implements ImageAnalysis.Analyzer { private int degreesToFirebaseRotation(int degrees) { switch (degrees) { case 0: return FirebaseVisionImageMetadata.ROTATION_0; case 90: return FirebaseVisionImageMetadata.ROTATION_90; case 180: return FirebaseVisionImageMetadata.ROTATION_180; case 270: return FirebaseVisionImageMetadata.ROTATION_270; default: throw new IllegalArgumentException( "Rotation must be 0, 90, 180, or 270."); } } @Override public void analyze(ImageProxy imageProxy, int degrees) { if (imageProxy == null || imageProxy.getImage() == null) { return; } Image mediaImage = imageProxy.getImage(); int rotation = degreesToFirebaseRotation(degrees); FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation); // Pass image to an ML Kit Vision API // ... } }
Kotlin+KTX
private class YourImageAnalyzer : ImageAnalysis.Analyzer { private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) { 0 -> FirebaseVisionImageMetadata.ROTATION_0 90 -> FirebaseVisionImageMetadata.ROTATION_90 180 -> FirebaseVisionImageMetadata.ROTATION_180 270 -> FirebaseVisionImageMetadata.ROTATION_270 else -> throw Exception("Rotation must be 0, 90, 180, or 270.") } override fun analyze(imageProxy: ImageProxy?, degrees: Int) { val mediaImage = imageProxy?.image val imageRotation = degreesToFirebaseRotation(degrees) if (mediaImage != null) { val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation) // Pass image to an ML Kit Vision API // ... } } }
Wenn Sie keine Kamerabibliothek verwenden, die Ihnen die Drehung des Bildes liefert, können Sie sie aus der Drehung des Geräts und der Ausrichtung des Kamerasensors im Gerät berechnen:
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } /** * 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, Context context) 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); // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360; // Return the corresponding FirebaseVisionImageMetadata rotation value. int result; switch (rotationCompensation) { case 0: result = FirebaseVisionImageMetadata.ROTATION_0; break; case 90: result = FirebaseVisionImageMetadata.ROTATION_90; break; case 180: result = FirebaseVisionImageMetadata.ROTATION_180; break; case 270: result = FirebaseVisionImageMetadata.ROTATION_270; break; default: result = FirebaseVisionImageMetadata.ROTATION_0; Log.e(TAG, "Bad rotation value: " + rotationCompensation); } return result; }
Kotlin+KTX
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 90) ORIENTATIONS.append(Surface.ROTATION_90, 0) ORIENTATIONS.append(Surface.ROTATION_180, 270) ORIENTATIONS.append(Surface.ROTATION_270, 180) } /** * 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, context: Context): 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) // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360 // Return the corresponding FirebaseVisionImageMetadata rotation value. val result: Int when (rotationCompensation) { 0 -> result = FirebaseVisionImageMetadata.ROTATION_0 90 -> result = FirebaseVisionImageMetadata.ROTATION_90 180 -> result = FirebaseVisionImageMetadata.ROTATION_180 270 -> result = FirebaseVisionImageMetadata.ROTATION_270 else -> { result = FirebaseVisionImageMetadata.ROTATION_0 Log.e(TAG, "Bad rotation value: $rotationCompensation") } } return result }
Übergeben Sie dann das
media.Image
Objekt und den Rotationswert anFirebaseVisionImage.fromMediaImage()
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
Kotlin+KTX
val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
- Um ein
FirebaseVisionImage
-Objekt aus einem Datei-URI zu erstellen, übergeben Sie den App-Kontext und den Datei-URI anFirebaseVisionImage.fromFilePath()
. Dies ist nützlich, wenn Sie einenACTION_GET_CONTENT
-Intent verwenden, um den Benutzer aufzufordern, ein Bild aus seiner Galerie-App auszuwählen.Java
FirebaseVisionImage image; try { image = FirebaseVisionImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
Kotlin+KTX
val image: FirebaseVisionImage try { image = FirebaseVisionImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
- Um ein
FirebaseVisionImage
-Objekt aus einemByteBuffer
oder einem Byte-Array zu erstellen, berechnen Sie zunächst die Bilddrehung wie oben für diemedia.Image
Eingabe beschrieben.Erstellen Sie dann ein
FirebaseVisionImageMetadata
-Objekt, das die Höhe, Breite, das Farbcodierungsformat und die Drehung des Bilds enthält:Java
FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder() .setWidth(480) // 480x360 is typically sufficient for .setHeight(360) // image recognition .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21) .setRotation(rotation) .build();
Kotlin+KTX
val metadata = FirebaseVisionImageMetadata.Builder() .setWidth(480) // 480x360 is typically sufficient for .setHeight(360) // image recognition .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21) .setRotation(rotation) .build()
Verwenden Sie den Puffer oder das Array und das Metadatenobjekt, um ein
FirebaseVisionImage
-Objekt zu erstellen:Java
FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata); // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);
Kotlin+KTX
val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata) // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
- So erstellen Sie ein
FirebaseVisionImage
Objekt aus einemBitmap
Objekt:Das vomJava
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin+KTX
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
-Objekt dargestellte Bild muss aufrecht stehen, ohne dass eine zusätzliche Drehung erforderlich ist.
Rufen Sie eine Instanz von
FirebaseVisionBarcodeDetector
:Java
FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance() .getVisionBarcodeDetector(); // Or, to specify the formats to recognize: // FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance() // .getVisionBarcodeDetector(options);
Kotlin+KTX
val detector = FirebaseVision.getInstance() .visionBarcodeDetector // Or, to specify the formats to recognize: // val detector = FirebaseVision.getInstance() // .getVisionBarcodeDetector(options)
Übergeben Sie das Bild schließlich an die Methode
detectInImage
:Java
Task<List<FirebaseVisionBarcode>> result = detector.detectInImage(image) .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionBarcode>>() { @Override public void onSuccess(List<FirebaseVisionBarcode> barcodes) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
Kotlin+KTX
val result = detector.detectInImage(image) .addOnSuccessListener { barcodes -> // Task completed successfully // ... } .addOnFailureListener { // Task failed with an exception // ... }
3. Erhalten Sie Informationen von Barcodes
Wenn der Barcode-Erkennungsvorgang erfolgreich ist, wird eine Liste vonFirebaseVisionBarcode
Objekten an den Erfolgs-Listener übergeben. Jedes FirebaseVisionBarcode
-Objekt stellt einen Barcode dar, 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 Strichcode-Detektor den Typ der durch den Strichcode codierten Daten bestimmen konnte, können Sie auch ein Objekt erhalten, das geparste Daten enthält.Zum Beispiel:
Java
for (FirebaseVisionBarcode 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 FirebaseVisionBarcode.TYPE_WIFI: String ssid = barcode.getWifi().getSsid(); String password = barcode.getWifi().getPassword(); int type = barcode.getWifi().getEncryptionType(); break; case FirebaseVisionBarcode.TYPE_URL: String title = barcode.getUrl().getTitle(); String url = barcode.getUrl().getUrl(); break; } }
Kotlin+KTX
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) { FirebaseVisionBarcode.TYPE_WIFI -> { val ssid = barcode.wifi!!.ssid val password = barcode.wifi!!.password val type = barcode.wifi!!.encryptionType } FirebaseVisionBarcode.TYPE_URL -> { val title = barcode.url!!.title val url = barcode.url!!.url } } }
Tipps zur Verbesserung der Echtzeitleistung
Wenn Sie Barcodes in einer Echtzeitanwendung scannen möchten, befolgen Sie diese Richtlinien, um die besten Frameraten zu erzielen:
Nehmen Sie Eingaben nicht mit der nativen Auflösung der Kamera auf. Bei einigen Geräten erzeugt die Erfassung von Eingaben mit der nativen Auflösung extrem große Bilder (10+ Megapixel), was zu einer sehr geringen Latenz führt, ohne dass die Genauigkeit verbessert wird. Fordern Sie stattdessen nur die Größe von der Kamera an, die für die Barcode-Erkennung benötigt wird: in der Regel nicht mehr als 2 Megapixel.
Wenn die Scangeschwindigkeit wichtig ist, können Sie die Bilderfassungsauflösung weiter verringern. Beachten Sie jedoch die oben beschriebenen Anforderungen an die Mindestgröße von Barcodes.
- Drosselrufe an den Detektor. Wenn ein neuer Videoframe verfügbar wird, während der Detektor läuft, löschen Sie den Frame.
- Wenn Sie die Ausgabe des Detektors verwenden, um Grafiken auf das Eingabebild zu legen, rufen Sie zuerst das Ergebnis von ML Kit ab, rendern Sie dann das Bild und überlagern Sie es in einem einzigen Schritt. Dadurch rendern Sie für jeden Eingabeframe nur einmal auf der Anzeigeoberfläche.
Wenn Sie die Camera2-API verwenden, erfassen Sie Bilder im Format
ImageFormat.YUV_420_888
.Wenn Sie die ältere Kamera-API verwenden, erfassen Sie Bilder im Format
ImageFormat.NV21
.