Raspberry Pi Barcode Scanner in Python

Previously, I wrote an article Raspberry Pi Barcode Scanner with Webcam and Python illustrating how to build a simple barcode scanner using Dynamsoft Barcode Reader SDK and OpenCV from scratch. The method decodeFile() was used for detecting barcodes from an image file. To use the API, you have to firstly write image buffer that obtained by OpenCV API to a file. Because the I/O operation takes too much time, this API is not suitable for real-time barcode detection from webcam video stream. Considering this scenario, I have added a new Python API decodeBuffer(). In this article, I will illustrate how to create and use the new API.

Raspberry Pi Barcode Scanner in Python

Testing Environment

  • Device: Raspberry Pi 3
  • Operating System: RASPBIAN JESSIE WITH PIXEL

Prerequisites

  • Dynamsoft Barcode Reader for Raspberry Pi
  • Python 2.7.0
  • OpenCV 3.0.0
  • Raspberry Pi 2 or 3
  • USB webcam

Building and Installation

How to Build OpenCV on Raspberry Pi

  1. Download and extract the source code.
  2. Install dependencies:
    sudo apt-get install cmake
    sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev
    sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
    sudo apt-get install libxvidcore-dev libx264-dev
    sudo apt-get install python-dev
  3. Setting up the build:
    cd ~/opencv-3.0.0/
    mkdir build
    cd build
    cmake -D CMAKE_BUILD_TYPE=RELEASE \
                    -D CMAKE_INSTALL_PREFIX=/usr/local \
                    -D INSTALL_C_EXAMPLES=ON \
                    -D INSTALL_PYTHON_EXAMPLES=ON \
                    -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib-3.0.0/modules \
                    -D BUILD_EXAMPLES=ON ..
  4. Compile and install OpenCV:
    make -j4
    sudo make install

The shared library will be located at /usr/local/lib/python2.7/dist-packages/cv2.so.

How to build the Python extension with Dynamsoft Barcode Reader SDK

  1. Download and extract the SDK package.
  2. Create a symbolic link for libDynamsoftBarcodeReader.so:
    sudo ln –s <Your dbr path>/lib/libDynamsoftBarcodeReader.so /usr/lib/libDynamsoftBarcodeReader.so
  3. Open setup.py and modify the paths of include and lib files:
    include_dirs=["/usr/lib/python2.7/dist-packages/numpy/core/include/numpy", "<Your dbr path>/include"],
    library_dirs=['<Your dbr path>/lib'],
  4. Build the extension:
    sudo python setup.py build install

How to implement the decodeBuffer method?

Because the source code is transplanted from Windows edition, we have to define following types and structure:

typedef unsigned long DWORD;
typedef long LONG;
typedef unsigned short WORD;

typedef struct tagBITMAPINFOHEADER {
  DWORD biSize;
  LONG biWidth;
  LONG biHeight;
  WORD biPlanes;
  WORD biBitCount;
  DWORD biCompression;
  DWORD biSizeImage;
  LONG biXPelsPerMeter;
  LONG biYPelsPerMeter;
  DWORD biClrUsed;
  DWORD biClrImportant;
} BITMAPINFOHEADER;

Convert the numpy data from Python to C in decodeBuffer(). Besides that, we have to construct a native buffer for barcode reading:

#include <ndarraytypes.h>

static PyObject *
decodeBuffer(PyObject *self, PyObject *args)
{
    PyObject *o;
    if (!PyArg_ParseTuple(args, "O", &o))
        return NULL;

    PyObject *ao = PyObject_GetAttrString(o, "__array_struct__");
    PyObject *retval;

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

    // Construct data with header info and image data 
    char *buffer = (char*)pai->data; // The address of image data
    int width = pai->shape[1];       // image width
    int height = pai->shape[0];      // image height
    int size = pai->strides[0] * pai->shape[0]; // image size = stride * height
    char *total = (char *)malloc(size + 40); // buffer size = image size + header size
    memset(total, 0, size + 40);
    BITMAPINFOHEADER bitmap_info = {40, width, height, 0, 24, 0, size, 0, 0, 0, 0};
    memcpy(total, &bitmap_info, 40);

    // Copy image data to buffer from bottom to top
    char *data = total + 40;
    int stride = pai->strides[0];
    int i = 1;
    for (; i <= height; i++) {
        memcpy(data, buffer + stride * (height - i), stride);
        data += stride;
    }

    // Dynamsoft Barcode Reader initialization
    __int64 llFormat = (OneD | QR_CODE | PDF417 | DATAMATRIX);
    int iMaxCount = 0x7FFFFFFF;
    ReaderOptions ro = {0};
    pBarcodeResultArray pResults = NULL;
    ro.llBarcodeFormat = llFormat;
    ro.iMaxBarcodesNumPerPage = iMaxCount;
    printf("width: %d, height: %d, size:%d\n", width, height, size);
    int iRet = DBR_DecodeBuffer((unsigned char *)total, size + 40, &ro, &pResults);
    printf("DBR_DecodeBuffer ret: %d\n", iRet);
    free(total); // Do not forget to release the constructed buffer 
    
    // Get results
    int count = pResults->iBarcodeCount;
    pBarcodeResult* ppBarcodes = pResults->ppBarcodes;
    pBarcodeResult tmp = NULL;
    retval = PyList_New(count); // The returned Python object
    PyObject* result = NULL;
    i = 0;
    for (; i < count; i++)
    {
        tmp = ppBarcodes[i];
        result = PyString_FromString(tmp->pBarcodeData);
        printf("result: %s\n", tmp->pBarcodeData);
        PyList_SetItem(retval, i, Py_BuildValue("iN", (int)tmp->llFormat, result)); // Add results to list
    }
    // release memory
    DBR_FreeBarcodeResults(&pResults);

    Py_DECREF(ao);
    return retval;
}

Raspberry Pi Barcode Scanner

How to set video frame rate, frame width, and frame height?

You can refer to the Property identifier:

  • CV_CAP_PROP_FRAME_WIDTH: Width of the frames in the video stream.
  • CV_CAP_PROP_FRAME_HEIGHT: Height of the frames in the video stream.
  • CV_CAP_PROP_FPS: Frame rate.

If failed to use the property identifier, set the value directly as follows:

vc = cv2.VideoCapture(0)
vc.set(5, 30)  #set FPS
vc.set(3, 320) #set width
vc.set(4, 240) #set height

How to use the API decodeBuffer()?

while True:
        cv2.imshow(windowName, frame)
        rval, frame = vc.read();
        results = decodeBuffer(frame)
        if (len(results) > 0):
            print "Total count: " + str(len(results))
            for result in results:
                print "Type: " + types[result[0]]
                print "Value: " + result[1] + "\n"

        # 'ESC' for quit
        key = cv2.waitKey(20)
        if key == 27:
            break

How to run the Raspberry Pi Barcode Scanner?

  1. Connect a USB webcam to Raspberry Pi 2 or 3.
  2. Run app.py:
    python app.py

Source Code

https://github.com/dynamsoftlabs/raspberrypi-python-barcode