Android Barcode Detection from Fast Moving Objects

Assume you apply barcode technology to the logistics conveyor belt for scanning parcels. A problem you may face is how to recognize barcodes from blurred images. Although we can use advanced algorithms to deal with this complicated case, we’d better improve the image quality as possible as we can. A simple way is to adjust the camera shutter speed which is also known as exposure time. Faster shutter speed can avoid motion blur. In this post, I will share how to invoke Android Camera2 APIs to change the shutter speed, as well as how to build a simple Android barcode reader to decode barcodes from fast-moving objects

Android Barcode Reader with Camera2 API

To build a basic Android Camera2 app, we don’t need to reinvent the wheel. Just download or fork the source code of android-Camera2Basic provided by Google.

Create an AlertDialog with a list view for selecting shutter speed:

Activity activity = getActivity();
if (null != activity) {
    // https://stackoverflow.com/questions/15762905/how-can-i-display-a-list-view-in-an-android-alert-dialog
    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();
}

Get a list of sizes compatible with the requested image format YUV_420_888 and instantiate ImageReader with the opted image 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);

In the ImageReader callback function onImageAvailable(), get the image buffer and call the decodeBuffer() function for barcode detection:

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

Because the listener is invoked on a thread handler, we can not update the UI directly.  We can send an update request via 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);
                }

        }
    }
};

Add the ImageReader surface to the CaptureRequest.Builder:

mPreviewRequestBuilder.addTarget(mImageReader.getSurface());

Start the preview when the camera capture session is ready. To control the shutter speed, turn off the auto-exposure mode:

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

Now build and run the simple Android barcode reader. If the barcodes cannot be read from the fast-moving objects, you just need to change the shutter speed:

Android barcode reader with Camera2 API

Note: due to the hardware specs, you may get low-quality preview images on some cheap phones when changing the shutter speed to 1/500 seconds or faster.

Source Code

https://github.com/yushulx/android-camera2-barcode