Click here to Skip to main content
14,298,919 members

Real-time Barcode Scanning from Camera Stream

25 Jul 2019CPOL
In this post, we discuss how to leverage Dynamsoft Barcode Reader video decoding APIs to implement the barcode scanning functionality in camera preview scenario. Also, we demonstrate how to implement for desktop and mobile platforms respectively with the code snippet.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

Barcode detection and recognition technology, especially the application of real-time barcode scanning, is imperative to industries such as warehouse management and mobile payment. Dynamsoft Barcode Reader, as a cross-platform and cross-programing language SDK, is a lifesaver for developers who are dedicated to building enterprise-class barcode solutions in a short time.

In this post, we discuss how to leverage Dynamsoft Barcode Reader video decoding APIs to implement the barcode scanning functionality in camera preview scenario. Also, we demonstrate how to implement for desktop and mobile platforms respectively with the code snippet.

The Challenge of Camera Real-time Barcode Scanning

The process of reading barcodes consists of various computer vision algorithms, some of which are CPU-intensive. To make the camera preview run smoothly, you have to execute intensive computation asynchronously in a worker thread. A multithreading model is required for this case, in which developers have to spend a lot more time on thread scheduling, shared memory, status synchronization, and lock. Developers are prone to make mistakes in such a complicated scenario.

To eliminate the complexity of multi-threaded programming and improve developers’ efficiency, Dynamsoft Barcode Reader 7.0 brings a set of thread-based APIs for decoding continuous frames.

Dynamsoft Barcode Reader Overview

Dynamsoft's Barcode Reader SDK enables you to efficiently embed barcode reading functionality in your web, desktop or mobile application using just a few lines of code, which saves your development time and cost. With the SDK, you can create high-speed and reliable barcode scanner software to meet your business needs.

License

Get a FREE 30-day trial license.

Supported Barcode Symbologies

Linear Barcodes (1D): Code 39, Code 93, Code 128, Codabar, Interleaved 2 of 5, EAN-8, EAN-13, UPC-A, UPC-E, Industrial 2 of 5.

2D Barcodes: QR Code, DataMatrix, PDF417 and Aztec Code.

Developer’s Guide

https://www.dynamsoft.com/help/Barcode-Reader/devguide/index.html

API Documentation

https://www.dynamsoft.com/help/Barcode-Reader/index.html

Code Gallery

https://www.dynamsoft.com/Downloads/Dynamic-Barcode-Reader-Sample-Download.aspx

Online Demo

https://demo.dynamsoft.com/DBR/BarcodeReaderDemo.aspx

How Barcode Video Decoding APIs Work

The video decoding model contains a user main thread, a decoding thread, and a result consumer thread.

Image 1

User Main Thread

The user main thread is initiated when StartFrameDecoding() is being called. Invoke AppendFrame() to add frames to an auto-managed frame queue in a video frame callback function.

Decoding Thread

The decoding thread takes all CPU-intensive work, running barcode algorithms. It uses some strategies to select and discard frames in order to avoid lagging.

Result Consumer Thread

This thread manages registered callback functions for fetching results.

Desktop Barcode Scanning

To build a desktop barcode app quickly, we recommend using Python and OpenCV.

Breaking Through Python’s GIL

The performance of Python multithreading code, especially for computation-intensive tasks, is restricted due to Python’s GIL (Global Interpreter Lock). The new video decoding APIs of Dynamsoft Barcode Reader is based on native C/C++ thread, which will not be affected by Python’s virtual machine.

Let’s take a glimpse of the Python sample code:

import cv2
import dbr
import time
import os
 
# The callback function for receiving barcode results
def onBarcodeResult(format, text):
    print("Type: " + format)
    print("Value: " + text + "\n")
 
def read_barcode():
    video_width = 640
    video_height = 480
 
    vc = cv2.VideoCapture(0)
    vc.set(3, video_width) #set width
    vc.set(4, video_height) #set height
 
    if vc.isOpened():  
        dbr.initLicense('LICENSE-KEY')
        rval, frame = vc.read()
    else:
        return
 
    windowName = "Barcode Reader"
 
    max_buffer = 2
    max_results = 10
    barcodeTypes = 0x3FF | 0x2000000 | 0x4000000 | 0x8000000 | 0x10000000 # 1D, PDF417, QRCODE, DataMatrix, Aztec Code
    image_format = 1 # 0: gray; 1: rgb888
 
    dbr.startVideoMode(max_buffer, max_results, video_width, video_height, image_format, barcodeTypes, onBarcodeResult)
 
    while True:
        cv2.imshow(windowName, frame)
        rval, frame = vc.read()
 
        start = time.time()
        try:
            ret = dbr.appendVideoFrame(frame)
        except:
            pass
 
        cost = (time.time() - start) * 1000
        print('time cost: ' + str(cost) + ' ms')
 
        # 'ESC' for quit
        key = cv2.waitKey(1)
        if key == 27:
            break
 
    dbr.stopVideoMode()
    dbr.destroy()
    cv2.destroyWindow(windowName)
 
if __name__ == "__main__":
    print("OpenCV version: " + cv2.__version__)
    read_barcode()

Image 2

The dbr module is the Python extension built with Dynamsoft Barcode Reader. We created three methods: startVideoMode(), appendVideoFrame() and stopVideoMode(). The corresponding C/C++ code is as follows.

static PyObject *
startVideoMode(PyObject *self, PyObject *args)
{
    printf("Start the video mode\n");
    CHECK_DBR();
 
    PyObject *callback = NULL;
    int maxListLength, maxResultListLength, width, height, imageformat, iFormat, stride; 
    if (!PyArg_ParseTuple(args, "iiiiiiO", &maxListLength, &maxResultListLength, &width, &height, &imageformat, &iFormat, &callback)) {
        return NULL;
    }
 
    updateFormat(iFormat);
 
    if (!PyCallable_Check(callback)) 
    {
        PyErr_SetString(PyExc_TypeError, "parameter must be callable");
        return NULL;
    }
    else
    {
        Py_XINCREF(callback);         /* Add a reference to new callback */
        Py_XDECREF(py_callback);      /* Dispose of previous callback */
        py_callback = callback;     
    }
 
    ImagePixelFormat format = IPF_RGB_888;
 
    if (imageformat == 0)
    {
        stride = width;
        format = IPF_GRAYSCALED;
    }
    else {
        stride = width * 3;
        format = IPF_RGB_888;
    }
 
    DBR_SetTextResultCallback(hBarcode, onResultCallback, NULL);
 
    int ret = DBR_StartFrameDecoding(hBarcode, maxListLength, maxResultListLength, width, height, stride, format, "");
    return Py_BuildValue("i", ret);
}

appendVideoFrame()

static PyObject *
appendVideoFrame(PyObject *self, PyObject *args)
{
    CHECK_DBR();
 
    PyObject *o;
    if (!PyArg_ParseTuple(args, "O", &o))
        return NULL;
     
    #if defined(IS_PY3K)
    //Refer to numpy/core/src/multiarray/ctors.c
    Py_buffer *view;
    int nd;
    PyObject *memoryview = PyMemoryView_FromObject(o);
    if (memoryview == NULL) {
        PyErr_Clear();
        return -1;
    }
 
    view = PyMemoryView_GET_BUFFER(memoryview);
    unsigned char *buffer = (unsigned char *)view->buf;
    nd = view->ndim;
    int len = view->len;
    int stride = view->strides[0];
    int width = view->strides[0] / view->strides[1];
    int height = len / stride;
    #else
 
    PyObject *ao = PyObject_GetAttrString(o, "__array_struct__");
 
    if ((ao == NULL) || !PyCObject_Check(ao)) {
        PyErr_SetString(PyExc_TypeError, "object does not have array interface");
        return NULL;
    }
 
    PyArrayInterface *pai = (PyArrayInterface*)PyCObject_AsVoidPtr(ao);
     
    if (pai->two != 2) {
        PyErr_SetString(PyExc_TypeError, "object does not have array interface");
        Py_DECREF(ao);
        return NULL;
    }
 
    // Get image information
    unsigned char *buffer = (unsigned char *)pai->data; // The address of image data
    int width = (int)pai->shape[1];       // image width
    int height = (int)pai->shape[0];      // image height
    int stride = (int)pai->strides[0]; // image stride
    #endif
 
    // Initialize Dynamsoft Barcode Reader
    TextResultArray *paryResult = NULL;
 
    // Detect barcodes
    ImagePixelFormat format = IPF_RGB_888;
 
    if (width == stride) 
    {
        format = IPF_GRAYSCALED;
    }
    else if (width == stride * 3) 
    {
        format = IPF_RGB_888;
    }
    else if (width == stride * 4)
    {
        format = IPF_ARGB_8888;
    }
 
    int frameId = DBR_AppendFrame(hBarcode, buffer);
    return 0;
}

stopVideoMode()

static PyObject *
stopVideoMode(PyObject *self, PyObject *args)
{
    printf("Stop the video mode\n");
    if (hBarcode) 
    {
        int ret = DBR_StopFrameDecoding(hBarcode);
        return Py_BuildValue("i", ret);
    }
 
    return 0;
}

You have to call Python code between PyGILState_Ensure() and PyGILState_Release() when executing the callback function on the native thread:

void onResultCallback(int frameId, TextResultArray *pResults, void * pUser)
{
    // Get barcode results
    int count = pResults->resultsCount;
    int i = 0;
 
    // https://docs.python.org/2/c-api/init.html
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();
 
    for (; i < count; i++)
    {
        // https://docs.python.org/2.5/ext/callingPython.html
        PyObject *result = PyObject_CallFunction(py_callback, "ss", pResults->results[i]->barcodeFormatString, pResults->results[i]->barcodeText);
        if (result == NULL) return NULL;
        Py_DECREF(result);
    }
 
    PyGILState_Release(gstate);
    /////////////////////////////////////////////
 
    // Release memory
    DBR_FreeTextResults(&pResults);
}

Source Code

https://github.com/dynamsoft-dbr/python/tree/7.x

Android Barcode Scanning

To build a simple Android barcode scanning app with Android camera API and Dynamsoft Barcode Reader.

cameraView.addFrameProcessor(new FrameProcessor() {
            @SuppressLint("NewApi")
            @Override
            public void process(@NonNull final Frame frame) {
                try {
                    if (isDetected && isCameraOpen) {
                        YuvImage yuvImage = new YuvImage(frame.getData(), ImageFormat.NV21,
                                frame.getSize().getWidth(), frame.getSize().getHeight(), null);
                        if (width == 0) {
                            width = yuvImage.getWidth();
                            height = yuvImage.getHeight();
                            stride = yuvImage.getStrides()[0];
                            reader.setErrorCallback(errorCallback, null);
                            reader.setTextResultCallback(textResultCallback, null);
                            reader.setIntermediateResultCallback(intermediateResultCallback, null);
                            reader.startFrameDecoding(10, 10, width, height, stride, EnumImagePixelFormat.IPF_NV21, "");
                        } else {
                            PublicRuntimeSettings s  = reader.getRuntimeSettings();
                            int frameid = reader.appendFrame(yuvImage.getYuvData());
                            Log.i("FrameId", frameid + "");
                        }
                        if (bUpateDrawBox) {
                            bUpateDrawBox = false;
                            Message message = handler.obtainMessage();
                            Rect imageRect = new Rect(0, 0, width, height);
                            message.obj = imageRect;
                            message.what = 0x001;
                            handler.sendMessage(message);
                        }
                        isDetected = true;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

Image 3

Call startDecodingFrame() to initialize the environment and call appendFrame() repeatedly to add the preview images.

Source Code

https://github.com/dynamsoft-dbr/android-barcode-decode-video

iOS Barcode Scanning

To build a simple iOS barcode scanning app with iOS camera API and Dynamsoft Barcode Reader.

(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
{
   isCurrentFrameDecodeFinished = NO;
   CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
   CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
   int bufferSize = (int)CVPixelBufferGetDataSize(imageBuffer);
   void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
   CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
   NSData * buffer = [NSData dataWithBytes:baseAddress length:bufferSize];
   startRecognitionDate = [NSDate date];
   if (width != (int)CVPixelBufferGetWidth(imageBuffer)) {
       width = (int)CVPixelBufferGetWidth(imageBuffer);
       height = (int)CVPixelBufferGetHeight(imageBuffer);
       stride = CVPixelBufferGetBytesPerRow(imageBuffer);
       [m_barcodeReader setDBRErrorDelegate:self userData:nil];
       [m_barcodeReader setDBRTextResultDelegate:self userData:nil];
       [m_barcodeReader setDBRIntermediateResultDelegate:self userData:nil];
       [m_barcodeReader startFrameDecoding:10 maxResultQueueLength:100 width:width height:height stride:stride format:EnumImagePixelFormatARGB_8888 templateName:@"" error:nil];
   } else {
       [m_barcodeReader appendFrame:buffer];
   }
}

Image 4

Call the following to initialize the environment and call -(NSInteger)appendFrame:(NSData*) bufferBytes repeatedly to add the preview images.

(void)startFrameDecoding:(NSInteger)maxQueueLength
                     maxResultQueueLength:(NSInteger)maxResultQueueLength
                     width:(NSInteger)width
                     height:(NSInteger)height
                     stride:(NSInteger)stride
                     format:(EnumImagePixelFormat)format
                     templateName:(NSString* _Nonnull)templateName
                     error:(NSError* _Nullable * _Nullable)error

Source Code

https://github.com/dynamsoft-dbr/ios-barcode-decode-video

Technical Support

If you have any questions about Dynamsoft Barcode Reader SDK, please feel free to contact support@dynamsoft.com.

Release History

v7.0, 07/12/2019

New

  • Refactored most modules to provide a flexible barcode reading framework that allows for parameter customization suited for a variety of barcode scenarios
  • Enabled access to intermediate results (grayscale image, binarized image, text zone, etc) during the decoding process
  • Added new interfaces to support video decoding, and frame decoding to improve interactive sensitivity
  • Provided methods to terminate the decoding process at different phases such as during binarization, localization or barcode type identification.
  • Added a new barcode localization method, Scan Directly, to reduce decoding time significantly for high-quality images.

Improved

  • Enhanced error messages related to license initiation failure.
  • Improved detailed results for decoded barcodes, including more barcode format specification
  • Improved results output to enable outputting barcode results in the order of confidence level, barcode position or format.

Fix

  • Fixed an issue where the barcode could be calculated incorrectly on some occasions.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Xiao Ling
Technical Writer Dynamsoft
Canada Canada
Xiao Ling is A technical content writer at Dynamsoft, the leading company of document capture and image processing SDKs.
He is in charge of managing Dynamsoft blog site codepool.biz and Dynamsoft GitHub community.


Comments and Discussions

 
-- There are no messages in this forum --
Article
Posted 25 Jul 2019

Stats

3.8K views
4 bookmarked