Python Barcode Decoding on Non-Python Created Thread

Dynamsoft Barcode Reader 7.0 brings a set of thread-based APIs for continuous frame management and corresponding barcode decoding. It extremely simplifies the programming complexity, especially for Python. It is known that Python’s GIL (Global Interpreter Lock) affects the performance in a multi-threaded scenario. Running computation intensive tasks in Python thread cannot improve the Python app performance. If you create a Python barcode scanner app with OpenCV and Dynamsoft Barcode Reader 6.x, Python multiprocessing is the only way for getting a high camera frame rate. With the thread-based APIs of Dynamsoft Barcode Reader 7.x, your Python apps will not be limited by GIL anymore. This tutorial shares how to integrate the thread-based C/C++ APIs into Python barcode extension.

The New Barcode Decoding APIs for Video Frames

To read barcodes from continuous frames, you need to use following APIs:

  • DBR_StartFrameDecoding()
  • DBR_StopFrameDecoding()
  • DBR_AppendFrame()
  • DBR_SetTextResultCallback()

DBR_StartFrameDecoding()

DBR_API int DBR_StartFrameDecoding ( void * barcodeReader,
const int maxListLength,
const int maxResultListLength,
const int width,
const int height,
const int stride,
const ImagePixelFormat format,
const char * pTemplateName
)

Starts a new thread to decode barcodes from the inner frame queue.

Parameters:

[in]	barcodeReader	Handle of the barcode reader instance. 
[in]	maxListLength	The max count of frames waiting for decoding. 
[in]	maxResultListLength	The max count of frames whose results (text result/localization result) will be kept for further reference. 
[in]	width	The width of the frame image in pixels. 
[in]	height	The height of the frame image in pixels. 
[in]	stride	The stride of the frame image (also called scan width). 
[in]	format	The image pixel format used in the image byte array. 
[in]	pTemplateName	The template name.

Returns:

Returns error code. Returns 0 if the function operates successfully, otherwise call DBR_GetErrorString to get detail message. Possible returns are: DBR_OK; DBRERR_FRAME_DECODING_THREAD_EXISTS; DBRERR_PARAMETER_VALUE_INVALID; DBRERR_NULL_POINTER;

Code Snippet:

void* barcodeReader = DBR_CreateInstance();
DBR_InitLicense(barcodeReader, "t0260NwAAAHV***************");
int errorCode = DBR_StartFrameDecoding(barcodeReader, 2, 10, 1024, 720, 720, IPF_BINARY, "");
DBR_DestroyInstance(barcodeReader);

DBR_StopFrameDecoding()

DBR_API int DBR_StopFrameDecoding ( void * barcodeReader )

Stops the frame decoding thread created by StartFrameDecoding.

Parameters:

[in]	barcodeReader	Handle of the barcode reader instance.

Returns:

Returns error code. Returns 0 if the function operates successfully, otherwise call DBR_GetErrorString to get detail message. Possible returns are: DBR_OK; DBRERR_STOP_DECODING_THREAD_FAILED;

Code Snippet:

void* barcodeReader = DBR_CreateInstance();
DBR_InitLicense(barcodeReader, "t0260NwAAAHV***************");
int errorCode = DBR_StopFrameDecoding(barcodeReader);
DBR_DestroyInstance(barcodeReader);

DBR_AppendFrame()

DBR_API int DBR_AppendFrame ( void * barcodeReader,
unsigned char * pBufferBytes
)

Append a frame image buffer to the inner frame queue.

Parameters:

[in]	barcodeReader	Handle of the barcode reader instance. 
[in]	pBufferBytes	The array of bytes which contain the image data.

Returns:

Returns the Id of the appended frame

Code Snippet:

void* barcodeReader = DBR_CreateInstance();
DBR_InitLicense(barcodeReader, "t0260NwAAAHV***************");
int frameId = DBR_AppendFrame(barcodeReader, pBufferBytes);
DBR_DestroyInstance(barcodeReader);

DBR_SetTextResultCallback()

DBR_API int DBR_SetTextResultCallback ( void * barcodeReader,
CB_TextResult cbFunction,
void * pUser
)

Sets call back function to process text results generated during frame decoding.

Parameters:

[in]	barcodeReader	Handle of the barcode reader instance. 
[in]	cbFunction	Call back function. 
[in]	pUser	Customized arguments passed to your function.

Returns:

Returns error code. Returns 0 if the function operates successfully, otherwise call DBR_GetErrorString to get detail message. Possible returns are: DBR_OK; DBRERR_FRAME_DECODING_THREAD_EXISTS;

Code Snippet:

void TextResultFunction(int frameId, TextResultArray *pResults, void * pUser)
{
//TODO add your code for using test results
}
void* barcodeReader = DBR_CreateInstance();
DBR_InitLicense(barcodeReader, "t0260NwAAAHV***************");
DBR_SetTextResultCallback(barcodeReader, TextResultFunction, NULL);
DBR_StartFrameDecoding(barcodeReader, 2, 10, 1024, 720, 720, IPF_BINARY, "");

How to Integrate C/C++ Thread-based APIs to Python Extension

Get the source code of Python barcode module built with Dynamsoft Barcode Reader 6.x.

Since the new APIs are thread-based, a callback function is required for returning the barcode results. Can we call Python function directly via C/C++ thread? No! Here is what you should do according to the Python online documentation:
non python thread
The code snippet of calling Python function from the native thread is as follows:

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

Save a Python callback function and start a thread pool to process barcode detection events:

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

Append frames to a buffer queue:

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

Stop the thread pool:

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

Python Barcode Reader

Create a Python file without Python threading or multiprocessing modules:

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

Run the script. We can see the time cost of invoking appendVideoFrame() function can be ignored:

Python barcode reader

Dynamsoft Barcode Reader 7.0 is coming soon. If you are interested in the new version, stay tuned to Dynamsoft Barcode Reader homepage.

Source Code

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