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](mk:@MSITStore:E:\Program%20Files%20(x86)\Dynamsoft\Barcode%20Reader%207.0\Documents\DBR_API_References.chm::/group___function_pointer.html#gaf6d149103ce7ea7f17add82b6ce35554)
_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: 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('DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==')
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:
Dynamsoft Barcode Reader 7.0 is coming soon. If you are interested in the new version, stay tuned to Dynamsoft Barcode Reader homepage.