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.
|
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 bitmap, media.Image, ByteBuffer, byte[], 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 |
Gets the bounding rectangle of the detected barcode.
|
Returns barcode value as it was encoded in the barcode as String
|
|
Returns barcode value in a user-friendly format as String
|
|
Returns barcode format as int, for example FORMAT_EAN_13
|
|
Returns format type of the barcode value as int
|
|
Returns four corner points in clockwise direction starting with top-left as Point[]
|
|
Calendar Event |
Gets parsed calendar event details (set iff getValueType() is TYPE_CALENDAR_EVENT) as FirebaseVisionBarcode.CalendarEvent
|
Contact Info |
Gets parsed contact details (set iff getValueType() is TYPE_CONTACT_INFO) as FirebaseVisionBarcode.ContactInfo
|
Driver’s License |
Gets parsed driver’s license details (set iff getValueType() is TYPE_DRIVER_LICENSE) as FirebaseVisionBarcode.DriverLicense
|
getEmail() Gets parsed email details (set iff getValueType() is TYPE_EMAIL) as FirebaseVisionBarcode.Email
|
|
Geo Coordinates |
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!