Using OpenCV to Build Python Barcode Reader for macOS

This article is about how to use OpenCV and Dynamsoft Barcode Reader SDK to create a Python barcode reader on macOS.

How to Install OpenCV on macOS

Use ‘sw_vers’ to check macOS system version information:

check mac version

Install Homebrew.

If you have already installed Homebrew, just update it. When running ‘brew update’, you may see following errors.

Error: /usr/local/Library/brew.sh: line 32: /usr/local/Library/ENV/scm/git: No such file or directory.

To fix the error, run:

cd "$(brew --repository)" && git fetch && git reset --hard origin/master

Error: Fetching /usr/local/Library/Taps/flutter/homebrew-flutter failed!

To repair the error, run:

brew untap flutter/flutter

The next step is to install Python and NumPy. Python is pre-installed on macOS. The default version is not compatible with the latest OpenCV. Therefore, you need to install the latest Python using Homebrew.

brew install python python3 numpy
echo 'import site; site.addsitedir("/usr/local/lib/python2.7/site-packages")' >> /Users/xiao/Library/Python/2.7/lib/python/site-packages/homebrew.pth

Use command ‘python –version’ to check the current version. If it is not the latest version, you can edit .bash_profile and export the path:

vim ~/.bash_profile
export PATH=/usr/local/Cellar/python/2.7.13/bin:$PATH
source ~/.bash_profile

Install OpenCV:

brew tap homebrew/science
brew install opencv3

Python Barcode Reader for macOS

Get libDynamsoftBarcodeReader.dylib and relevant header files from DBR-Libs.zip.

To link the dynamic library and use barcode reading APIs, we need to write code in C/C++. Include the header files:

#include <Python.h>
#include "If_DBR.h"
#include "BarcodeFormat.h"
#include "BarcodeStructs.h"
#include "ErrorCode.h"
#include <ndarraytypes.h>

Where is ndarraytypes.h?
Use command ‘find / -name ndarraytypes.h’ to find it:

/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/numpy/core/include/numpy/ndarraytypes.h
/usr/local/Cellar/numpy/1.13.0/lib/python2.7/site-packages/numpy/core/include/numpy/ ndarraytypes.h
/usr/local/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h

Get native image data that decoded by OpenCV Python API:

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

    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

Define BITMAPINFOHEADER structure. The DWORD defined by Microsoft is unsigned long, but here it is unsigned int. The size of the type should be 4 bytes.

typedef unsigned int DWORD;
typedef int LONG;
typedef unsigned short WORD;

#pragma pack(push)
#pragma pack(1)

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;

#pragma pack(pop)

Construct a buffer with bitmap header info for barcode detection.

int dib_header_size = sizeof(BITMAPINFOHEADER);

char *total = (char *)malloc(size + dib_header_size); // buffer size = image size + header size
memset(total, 0, size + dib_header_size);
BITMAPINFOHEADER bitmap_info = {dib_header_size, width, height, 0, 24, 0, size, 0, 0, 0, 0};
memcpy(total, &bitmap_info, dib_header_size);

// Copy image data to buffer from bottom to top
char *data = total + dib_header_size;
int stride = pai->strides[0];
int i = 1;
for (; i <= height; i++) {
    memcpy(data, buffer + stride * (height - i), stride);
    data += stride;
}
int iRet = DBR_DecodeBuffer((unsigned char *)total, size + dib_header_size, &ro, &pResults);

Get and return barcode 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);

Configure setup.py for building Python extension:

from distutils.core import setup, Extension
 
module_dbr = Extension('dbr', 
                        sources = ['dbr.c'], 
                        include_dirs=['/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/numpy/core/include/numpy', './include'],
                        library_dirs=['./lib'],
                        libraries=['DynamsoftBarcodeReader'])
 
setup (name = 'DynamsoftBarcodeReader',
        version = '1.0',
        description = 'Python barcode extension',
        ext_modules = [module_dbr])

Build and install dbr.so:

python setup.py build install

Write a simple Python barcode detection app:

import cv2
from dbr import *
import sys
import os.path

initLicense("D426ABF246933C82A16D537FC46C064F")
frame = cv2.imread(fileName, cv2.CV_LOAD_IMAGE_COLOR)
results = decodeBuffer(frame)

Note: when using imread, you have to set second parameter CV_LOAD_IMAGE_COLOR. The native API only works for a color image.

OpenCV Python barcode reader for macOS

Source Code

https://github.com/dynamsoft-dbr/mac-opencv