Skip to content

Firebase ML Kit 3: Barcode Scanning

Barcode scanning has been around for a while, and for good reasons. it’s a great way to pass information from the real world to a digital device.

If this is you’re first time encountering Firebase ML Kit, feel free to check out my introduction on it.

With the new ML Kit, Firebase also offers that functionality. ML Kit can scan most linear and 2D barcode formats like Codabar, Data Matrix, and QR Code to name a few. On top of that, ML Kit can automatically identify which barcode format it’s scanning so you don’t have to set it manually. You can still restrict it to certain formats, and doing so will increase scanning speed.

Another useful quirk of ML Kit’s barcode scanning is that it automatically extracts and parses structured data such as URLs, Email Addresses, and WiFi Information.

 

Result
Corners (87,87) (612,87) (612,612) (87,612)
Raw value WIFI:S:SB1Guest;P:12345;T:WEP;;
WiFi information SSID                          SB1Guest

Password                12345

Type                          WEP

This is a table taken from the official docs showing what kind of data can be extract from the barcode scan.

Add the Dependencies and Metadata

implementation 'com.google.firebase:firebase-ml-vision:16.0.0'

As with any other Firebase Service, we’ll start by adding this dependency to your app-level build.gradle  which is the same one used for all the ML Kit features.

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

Although this is optional, it’s highly recommended to at this to your AndroidManifest.xml as well. Doing so will have the machine learning model downloaded along with your app in the Play Store. Otherwise, the model will be downloaded during the first ML request you make, at which point, you can’t get any results from ML operations before the model is downloaded.

Limiting Barcode Formats

Knowing which barcode formats you plan to read will boost the speed of the scan, so it would help to configure this setting, given the chance.

FirebaseVisionBarcodeDetectorOptions options =
        new FirebaseVisionBarcodeDetectorOptions.Builder()
        .setBarcodeFormats(
                FirebaseVisionBarcode.FORMAT_QR_CODE,
                FirebaseVisionBarcode.FORMAT_AZTEC)
        .build();

Taken from the official docs, here are the formats supported by ML Kit and their constants.

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

Getting the FirebaseVisionImage

The first step to most ML Kit operations is to get a FirebaseVisionImage which you can get from a bitmapmedia.ImageByteBufferbyte[], or a file on the device.

From Bitmap

FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

Your image must be upright for this to work. This would normally be the simplest way to get a FirebaseVisionImage.

From media.Image

Such as when taking a photo using your device’s camera. You’ll need to get the angle by which the image must be rotated to be turned upright, given the device’s orientation while taking a photo, and calculate that against the default camera orientation of the device (which is 90 on most devices, but can be different for other devices).

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);
    }
 
private int getRotationCompensation(String cameraId) throws CameraAccessException {
        
        int deviceRotation = getWindowManager().getDefaultDisplay().getRotation();
        int rotationCompensation = ORIENTATIONS.get(deviceRotation);
 
        CameraManager cameraManager = (CameraManager) 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(LOG_TAG, "Bad rotation value: " + rotationCompensation);
        }
        return result;
    }
 
private void someOtherMethod() {
    int rotation = getRotationCompensation(cameraId);
    FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
}

Long method do make all those calculations, but it’s pretty copy-pastable. Then you can pass in the mediaImage and the rotation to generate your FirebaseVisionImage.

From ByteBuffer

FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
        .setWidth(1280)
        .setHeight(720)
        .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
        .setRotation(rotation)
        .build();
 
FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);

You’ll need the above (from media.Image) method to get the rotation, then use this method to build the FirebaseVisionImage with the metadata of your image.

From File

FirebaseVisionImage image = FirebaseVisionImage.fromFilePath(context, uri);

Simple to present here in one line, but you’ll be wrapping this in a try-catch block.

Instantiate a FirebaseVisionBarcodeDetector

FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
        .getVisionBarcodeDetector();
// Or, to specify the formats to recognize:
// FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
//        .getVisionBarcodeDetector(options);

It’s a detector. It has a detectInImage method.

Call detectInImage

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

Use the detector, call detectInImage, add success and failure listeners, and in the success method you have access to a list of the barcodes scanned in the image. The code above says it all really.

Get Information from the Barcodes

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

In your success method, you want to loop through your list of barcodes. From there, you can extract all sorts of information including the corners of the barcode in the image, its raw value, and other information based on the barcodes actual data type.

The above code ungraciously ripped off of the official docs, however, today I’m taking a step further to list all the supported data types and the information you can extract from them. You’re welcome.

Extracting Info Table

 

All Data Types

getBoundingBox()

Gets the bounding rectangle of the detected barcode.

getRawValue()

Returns barcode value as it was encoded in the barcode as String

getDisplayValue()

Returns barcode value in a user-friendly format as String

getFormat()

Returns barcode format as int, for example FORMAT_EAN_13

getValueType()

Returns format type of the barcode value as int

getCornerPoints()

Returns four corner points in clockwise direction starting with top-left as Point[]
Calendar Event

getCalendarEvent()

Gets parsed calendar event details (set iff getValueType() is TYPE_CALENDAR_EVENT) as FirebaseVisionBarcode.CalendarEvent
Contact Info

getContactInfo()

Gets parsed contact details (set iff getValueType() is TYPE_CONTACT_INFO) as FirebaseVisionBarcode.ContactInfo
Driver’s License

getDriverLicense()

Gets parsed driver’s license details (set iff getValueType() is TYPE_DRIVER_LICENSE) as FirebaseVisionBarcode.DriverLicense
Email

getEmail()

Gets parsed email details (set iff getValueType() is TYPE_EMAIL) as FirebaseVisionBarcode.Email
Geo Coordinates

getGeoPoint()

Gets parsed geo coordinates (set iff getValueType() is TYPE_GEO) as FirebaseVisionBarcode.GeoPoint
Phone

getPhone()

Gets parsed phone details (set iff getValueType() is TYPE_PHONE) as FirebaseVisionBarcode.Phone
SMS

getSms()

Gets parsed SMS details (set iff getValueType() is TYPE_SMS) as FirebaseVisionBarcode.Sms
URL

getUrl()

Gets parsed URL bookmark details (set iff getValueType() is TYPE_URL) as FirebaeVisionBarcode.UrlBookmark
WiFi

getWifi()

Gets parsed WiFi AP details (set iff getValueType() is TYPE_WIFI) as FirebaseVisionBarcode.WiFi

Each FirebaseVisionBarcode.Whatever has its own methods to extract their respective data.

Conclusion

By no means am I an expert in the barcode scanning field, but Firebase ML Kit sure offers a ton on this and makes it really easy as well. ML Kit is definitely growing to be one of my favourites.

If you want to learn about other ML Kit features such as Text Recognition and Face Detection, check out the rest of my ML Kit series!