High-Speed Barcode and QR Code Detection on Android Using Camera2 API
Imagine you’re using barcode technology to scan parcels on a fast-moving logistics conveyor belt. A common challenge is recognizing barcodes from blurred images caused by motion. While advanced algorithms can mitigate this issue, enhancing image quality is a more effective first step. One straightforward solution is to adjust the camera’s shutter speed, also known as exposure time. By increasing the shutter speed, you can minimize motion blur. In this article, I’ll demonstrate how to use Android Camera2 APIs to control shutter speed and build a simple Android barcode reader that can effectively decode barcodes from fast-moving objects.
This article is Part 1 in a 7-Part Series.
- Part 1 - High-Speed Barcode and QR Code Detection on Android Using Camera2 API
- Part 2 - Optimizing Android Barcode Scanning with NDK and JNI C++
- Part 3 - Choosing the Best Tool for High-Density QR Code Scanning on Android: Google ML Kit vs. Dynamsoft Barcode SDK
- Part 4 - The Quickest Way to Create an Android QR Code Scanner
- Part 5 - Real-time QR Code Recognition on Android with YOLO and Dynamsoft Barcode Reader
- Part 6 - Accelerating Android QR Code Detection with TensorFlow Lite
- Part 7 - Effortlessly Recognize US Driver's Licenses in Android Apps
Prerequisites
Tuning Shutter Speed with Camera2 API for Barcode Detection
Building a basic Android Camera2 app doesn’t require starting from scratch. You can download or fork the android-Camera2Basic sample code provided by Google.
Intializing Dynamsoft Barcode Reader
In Camera2BasicFragment.java
, initialize the Dynamsoft Barcode Reader SDK in the onViewCreated
method:
try {
BarcodeReader.initLicense(
"LICENSE-KEY",
new DBRLicenseVerificationListener() {
@Override
public void DBRLicenseVerificationCallback(boolean isSuccessful, Exception e) {
}
});
mBarcodeReader = new BarcodeReader();
}
catch (Exception e) {
throw new RuntimeException(e);
}
Setting Up Shutter Speed Selection
To allow users to select the shutter speed, create an AlertDialog
with a list view:
Activity activity = getActivity();
if (null != activity) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Shutter Speed");
final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(activity, android.R.layout.select\_dialog\_singlechoice);
arrayAdapter.add("1/1000 s");
arrayAdapter.add("1/500 s");
arrayAdapter.add("1/250 s");
arrayAdapter.add("1/125 s");
arrayAdapter.add("1/100 s");
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.setAdapter(arrayAdapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mShutterSpeed = SHUTTER\_SPEED.get(which);
startPreview();
}
});
builder.show();
}
Configuring ImageReader
To capture images for barcode detection, get a list of compatible sizes for the YUV_420_888
image format and instantiate ImageReader
with the chosen size:
Size outputPreviewSize = new Size(MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT);
Size[] sizeList = map.getOutputSizes(ImageFormat.YUV_420_888);
for (Size size : sizeList) {
if(size.getWidth() * size.getHeight() > 1000000)
continue;
else{
outputPreviewSize = size;
break;
}
}
mImageReader = ImageReader.newInstance(outputPreviewSize.getWidth(), outputPreviewSize.getHeight(),
ImageFormat.YUV_420_888, 4);
Decoding Barcodes in ImageReader Callback
In the onImageAvailable()
callback, retrieve the image buffer and use the decodeBuffer()
function for barcode detection:
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
if (image == null) return;
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
int nRowStride = image.getPlanes()[0].getRowStride();
int nPixelStride = image.getPlanes()[0].getPixelStride();
image.close();
try {
TextResult[] results = mBarcodeReader.decodeBuffer(bytes, mImageReader.getWidth(), mImageReader.getHeight(), nRowStride * nPixelStride, EnumImagePixelFormat.IPF_NV21);
String output = "";
if (results != null && results.length > 0) {
for (TextResult result: results) {
String format = result.barcodeFormatString;
String text = result.barcodeText;
output += "Format: " + format + ", Text: " + text;
}
}
else {
output = "";
}
Message msg = new Message();
msg.what = MSG_UPDATE_TEXTVIEW;
msg.obj = output;
mMainHandler.sendMessage(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
Updating the UI on the Main Thread
Since the listener is triggered on a background thread, update the UI using the main thread handler:
mMainHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_TEXTVIEW:
String result = (String)msg.obj;
mTextView.setText((String)msg.obj);
if (!result.equals("")) {
mToneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP2);
}
}
}
};
Adding ImageReader Surface to CaptureRequest
Include the ImageReader surface in the CaptureRequest.Builder
:
mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
Controlling Shutter Speed and Starting Preview
When the camera capture session is ready, start the preview and control the shutter speed by disabling auto-exposure:
private void startPreview() {
try {
// Auto focus should be continuous for camera preview.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// Adjust the shutter speed
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
mPreviewRequestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, mShutterSpeed);
// Finally, we start displaying the camera preview.
mPreviewRequest = mPreviewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest,null,null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
Running the Barcode Reader
Now, build and run your simple Android barcode reader. If barcodes aren’t being detected from fast-moving objects, try adjusting the shutter speed:
Note: Due to hardware limitations, some budget phones may produce lower-quality preview images when the shutter speed is set to 1/500 seconds or faster.