前言

由于python高度的封装性和易用性,类似于matlab语言,在算法开发过程中比其他语言有更大的优势。但是在图像处理算开发中,笔者认为还是python比matlab更加简单。

笔者在学习数字图像处理过程中,最先接触到的便是开源的视觉图像库opencv。

在笔者早期开发图像处理算法过程中,通常是使用c++版本的opencv处理图像,得到需要的特征,然后把这些特征保存成txt,在matlab中绘图查看特征的分布,然后在matlab中进行处理,得到满意的结果后,把matlab代码用c++重新实现一遍。这个过程可以参考笔者原来做的https://blog.csdn/iamqianrenzhan/article/details/83537779线缆颜色顺序识别的算法。

但是这一过程有个弊端,在c++中使用opencv进行图像预处理并存入txt的特征是固定的,在matlab中处理如果发现效果不理想,需要更改在c++中进行图像预处理程序,这样势必在两个开发环境间来回切换,开发过程极不流畅。

最近在学习python的过程中,发现其可以直接使用opencv,并且其绘图的一些语法和matlab很类似,最重要的是c++调用python的方法不像调用matlab那样复杂。

所以就有了此文,在c++中调用python。

了解了在c++中调用python,以后开发视觉算法可以直接在python中进行,然后在c++中调用。至于采用这种方法的效率,可以暂时先不考虑。事实上,经过测试,满足一般需求绰绰有余,毕竟10ms和20ms的延迟人还感觉不到。

c++中调用python

pyhton模块初始化

path是pyhton模块所在路径,一定不能错。

bool pythonInit(string path)
{
    Py_Initialize();
    if (!Py_IsInitialized())
    {
        cout << "Py_Initialize failed." << endl;
        return false;
    }

    // 将Python工作路径切换到待调用模块所在目录,一定要保证路径名的正确性
    string chdir_cmd = string("sys.path.append(\"") + path + "\")";
    const char *cstr_cmd = chdir_cmd.c_str();
    PyRun_SimpleString("import sys");
    PyRun_SimpleString(cstr_cmd);
    return true;
}

pyhton函数加载

PyObject *pythonLoadModuleAndFunction(string moduleName, string functionName)
{
    //加载模块
    PyObject *pModule = PyImport_ImportModule(moduleName.c_str());
    if (!pModule) // 加载模块失败
    {
        cout << "[ERROR] Python get module failed." << endl;
        return pModule;
    }
    cout << "[INFO] Python get module succeed." << endl;

    //加载函数
    PyObject *pFunc = PyObject_GetAttrString(pModule, functionName.c_str()); //print_hello
    if (!pFunc || !PyCallable_Check(pFunc))                                  // 验证是否加载成功
    {
        cout << "[ERROR] Can't find function" << endl;
        return pFunc;
    }
    cout << "[INFO] Get function succeed 123." << endl;

    return pFunc;
}

c++中把opencv的Mat传递给python

PyObject *MatToArray(Mat src)
{
    import_array();
    clock_t start, finish;
    start = clock();

    cv::Mat img = src;
    //imread("1.bmp"); // CV_LOAD_IMAGE_COLOR

    PyObject *PythonArray = PyTuple_New(1);

    auto sz = img.size();
    int x = sz.width;
    int y = sz.height;
    int z = img.channels();
    uchar *CArrays = new uchar[x * y * z];
    int iChannels = img.channels();
    int iRows = img.rows;
    int iCols = img.cols * iChannels;

    // if (img.isContinuous())
    // {
    //     iCols *= iRows;
    //     iRows = 1;
    // }

    uchar *p;
    int id = -1;
    for (int i = 0; i < iRows; i++)
    {
        // get the pointer to the ith row
        p = img.ptr<uchar>(i);
        // operates on each pixel
        for (int j = 0; j < iCols; j++)
        {
            CArrays[++id] = p[j]; //连续空间
        }
    }

    npy_intp Dims[3] = {y, x, z};
    PyObject *PyArray = PyArray_SimpleNewFromData(3, Dims, NPY_UBYTE, CArrays);
    PyTuple_SetItem(PythonArray, 0, PyArray);

    finish = clock();
    //cout << "\n赋值为" << (double)(finish - start) / CLOCKS_PER_SEC << "秒!" << endl;

    return PythonArray;
}

调用是可以这样:

Mat mat;
pythonCallFunction(pFunc, MatToArray(mat));

python 文件中函数:

import cv2
def showimg(img):
	cv2.imshow('img', img)
	cv2.waitKey(0)

python函数参数制作

python函数参数就是一个元组,有几个参数,元组中就有几个元素。
比如python需要三个整数参数,可以这样写:

PyObject *pArgs = PyTuple_New(3);
PyTuple_SetItem(pArgs, 0, Py_BuildValue(“i”, 1)); 
PyTuple_SetItem(pArgs, 1, Py_BuildValue(“i”, 2)); 
PyTuple_SetItem(pArgs, 2, Py_BuildValue(“i”, 3));

python需要字典参数

PyObject *pDict = PyDict_New(); //创建字典类型变量 
PyDict_SetItemString(pDict, “Name”, Py_BuildValue(“s”, “Zhangsan”)); //往字典类型变量中填充数据 
PyDict_SetItemString(pDict, “Address”, Py_BuildValue(“s”, “BeiJing”));
PyObject *pArgs = PyTuple_New(1); 
PyTuple_SetItem(pArgs, 0, pDict)

同理,python需要数组参数,可以使用PyList_New 函数

python返回参数解析

python如果多个参数返回,也是元组对象。

环境配置相关

windows

1.编译时选择的64位和32位要和python版本匹配。一般环境都配置好,但是出现“无法解析的外部函数”,多半就是位数不对。
2.python3需要修改object.h和pyconfig.h 文件,pyhton2没有测试。
3.在qt中调用python还需要修改object.h中slots变量定义的地方,这个问题只有python3有。

qt中pro文件配置:

INCLUDEPATH+= D:\Python\Lib\site-packages\numpy\core\include  \
              D:\Python\include

CONFIG(debug, debug|release) {
message("debug")
LIBS += D:\Python\libs\python36_d.lib   \
        D:\Python\libs\python3_d.lib    
} else {
message("release")
LIBS += D:\Python\libs\python36.lib \
        D:\Python\libs\python3.lib
}

Ubuntu:

在qt中调用python还需要修改object.h中slots变量定义的地方,这个问题只有python3有。

Ubuntu中cmake配置CmakeList.txt:

# find required opencv
find_package(OpenCV 3.2 REQUIRED)

# find required python
find_package(PythonLibs 3.6 REQUIRED)

# directory of opencv headers
include_directories(${OpenCV_INCLUDE_DIRS})

# directory of python headers
include_directories(${PYTHON_INCLUDE_DIRS})
include_directories("./")

# directory of opencv library
link_directories(${OpenCV_LIBRARY_DIRS})
link_directories(${PYTHON_LIBRARY_DIRS})

Ubuntu中QT配置:

INCLUDEPATH+=/usr/include   \
	/usr/include/python3.6m \
	/home/qian/.local/lib/python3.6/site-packages/numpy/core/include/


if(contains(DEFINES,ARM)){
	message("compile for arm linux")
	LIBS += /usr/lib/aarch64-linux-gnu/libmysqlclient.so
	LIBS += /usr/lib/libopencv_*.so.3.3
}else{
	message("compile for amd linux")
	LIBS += /usr/lib/x86_64-linux-gnu/libmysqlclient.so
	LIBS += /usr/lib/x86_64-linux-gnu/libopencv_*.so.3.2
	LIBS += /usr/lib/python3.6/config-3.6m-x86_64-linux-gnu/libpython3.6.so
}

一些测试结论

一个python函数导入c++后可以被调用多次,但是不能在不用保护措施的前提下在多线程中多次调用。

可以在一次初始化后导入多个python函数。

程序执行到导入python模块函数返回值是0x0,说明该python模块不在搜索路径,可以在初始化后添加或者直接把自定义的python文件放到exe同级目录下。

程序执行到导入python模块函数直接崩溃,有可能是在该python模块中import某些模块导致的。

pyhton正常执行,但程序返回值是-1,可能是python函数中有错误,最常见的是读取文件路径等。

调试过程中遇到的一些问题

问题1

在windows中,链接时报告
1>pythonIniti.obj : error LNK2019: 无法解析的外部符号 __imp___Py_NegativeRefcount,该符号在函数 “public: __thiscall boost::python::api::object_base::~object_base(void)” (??1object_base@api@python@boost@@QAE@XZ) 中被引用
1>pythonIniti.obj : error LNK2001: 无法解析的外部符号 __imp___Py_RefTotal

解决方法:
修改两个头文件
1 注释掉object.h
//#define Py_TRACE_REFS
2 pyconfig.h
//# define Py_DEBUG

问题2

不管在windows还是Ubuntu平台,在QT中调用python3时,在object.h中有一行slots会出错,这是因为QT宏和python3中变量命名冲突,可修改object文件,在slots变量定义前取消slots宏定义,之后重新定义slots为Q_SLOTS。

当然还有一些比较小的问题,这都可以在网上找到解决办法。

更多推荐

QT和cmake工程中实现c++调用python具体实现,环境配置以及常见问题