Introduction

Flow

The above image shows the process flow that call a Python funtion from Qt C++ and pass OpenCV Mat image in C++ as python input, finally return a result.

As we all know, it’s very easy to use many machine learning related library in Python program. Although some machine learning librarys also provide C/C++ API, it’s not convenient enough in fast development. In this article, I will show you how to call a Python Function from Qt C++ code to do a image pridication task.

Environment

  • Ubuntu 18.04
  • Qt 5.12.0 LTS
  • OpenCV-4.0.0 C/C++ or newer (Please refer to this post for installation)
  • Python 3.6 in root environment

Preparation

For the OpenCV installation from native building, MUST set python related flag off when compiling, because we don’t want to use this vesion Python OpenCV (NOT compatible), we will install other version later.

-D BUILD_opencv_python2=OFF -D BUILD_opencv_python3=OFF

Installation

Now, install python3-numpy C/C++ library (not install Numpy for Python), so we can include their header file in code:

# it's different from "pip3 install numpy".  DON'T MIX
sudo apt-get install python3-numpy

Next, we will install Python OpenCV. For our case, we MUST use Python in root environment, do NOT use Python with virtual environment, such as Anaconda, virtualenv etc.

sudo apt install python3-pip
# Not all version works. this version is ok. You also can try other version
pip3 install opencv-python==3.1.0.4
# For usage on Python code
pip3 install imutils numpy tensorflow keras

Configuration

Add the following code into *.pro file in the Qt project:

# Python 3.6 native library in ubuntu 18.04
INCLUDEPATH += /usr/include/python3.6m
LIBS += -L/usr/local/lib/python3.6 -lpython3.6m

# OpenCV 4.x.x
INCLUDEPATH += /usr/local/include/opencv4
LIBS += -L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_imgcodecs -lopencv_videoio -lopencv_highgui

# this line is to add python script to Qt project. "python/predication.py" is the script file relative path.
DISTFILES += \
    python/predication.py

Code Usage

Python-OpenCV-Wrapper

This wrapper is a converter between OpenCV C/C++ Mat data and Python-acceptable numpy array data, so we can easily pass OpenCV image argument from C++ to Python.

I apapted this wrapper code from OpenCV source code file opencv/modules/python/src2/cv2.cpp.

You can click here to check the wrapper class file and you can directly copy these two files into your project without modification.

For example, if you want to convert OpenCV Mat to Numpy Array data in C++, you just call:

#include "py_cv_wrapper/pycvconverter.h"
#include "numpy/ndarrayobject.h"

PyObject* pNDArray = nullptr;
pNDArray = pycvt::fromMatToNDArray(imgMat);

Call Python from Qt C++

Include the following header to the header file:

#include "py_cv_wrapper/pycvconverter.h"

// To handle SLOTs error
#pragma push_macro("slots")
#undef slots
#include "Python.h"
#pragma pop_macro("slots")

#include "numpy/ndarrayobject.h"

// declare the python objects
PyObject* pModule = nullptr;
PyObject* pFunc = nullptr;
PyObject* pParam = nullptr;
PyObject* pNDArray = nullptr;
PyObject* pResult = nullptr;

1. Initialize your Python function

You can initialize the Ptyhon script anytime before the code call the function.

void initPy{
    // Initilaize Python
    Py_Initialize();
    // set system path to find correct python script
    string chdir_cmd = string("sys.path.append(\'../python\')");
    const char* cstr_cmd = chdir_cmd.c_str();

    // use sys to locate the script 
    PyRun_SimpleString("import sys");
    PyRun_SimpleString(cstr_cmd);

    // import module(predication.py), it's a python script file name
    pModule = PyImport_ImportModule("predication");

    if (!pModule){
        qDebug() << "get module failed!";
    }

    // get "main" function from the module, should match the function name in the Python script
    pFunc = PyObject_GetAttrString(pModule, "main");

    if (!pFunc){
        qDebug() << "get func failed!";
    }
}

2. Call the Python function

Now you can call the Python function anytime after initialization:

void callPyFunction(cv::Mat imageMat){
    pNDArray = pycvt::fromMatToNDArray(imageMat);
    // counld add more PyObj arguments before "NULL" indicator
    pResult = PyObject_CallFunctionObjArgs(pFunc, pNDArray, NULL);

    /* This method is to pass non-object para
    pParam = Py_BuildValue("(s)", "MSG from Qt");
    pResult = PyEval_CallObject(pFunc, pParam);
    */

    if(pResult){
        if(PyArg_Parse(pResult, "(sf)", &resLabel, &resConfidence))
        {
            // resLabel: the class predicated, resConfidence: confidence level of the predication
            qDebug() << resLabel << ":" << resConfidence;
        }
    }
}

3. Decrement your Python Object

When the program finish calling the Python function, you need to finalize them:

void closePy(){
    // Decrement the reference count for python object, prevent memory leak

    // PyObject must NOT be NULL. you may add if check.
    Py_DECREF(pModule);
    //Py_DECREF(pParam);
    Py_DECREF(pFunc);
    Py_DECREF(pNDArray);
    Py_DECREF(pResult);

    Py_Finalize();
}

You can click here to see how do I used them in my project.

Python Code

import cv2 # version = 3.1.0.4
import numpy as np
import tensorflow as tf
import keras
from keras import backend as K

# yes, this one is the "main" function called in above cpp file.
def main(nd_data):
    (label, confidence) = predication(nd_data)
    return (label, confidence)

def predication(nd_data):
    # process the nd_data
    # ML predication task using tf and keras
    # ......

    return (label, confidence)

You can click here to check the Python part.

Now, all the steps are done, you should be more clear. If you want to acclerate the machine learning predication speed using a NVIDIA graphics card, you can refer to this post on how to set up.