vs2010c++调用python

  • 前言
  • 过程
  • 1.vs2010导入python库
  • 2.代码实践
  • 3.打包


前言

想要在c++中部署训练好的深度学习模型方法可能有很多种,但是在低版本c++中部署深度学习模型似乎方法比较单一。由于进入项目时,大部分代码都是在vs2010上编译的,需要在程序中调用深度学习模型进行图片识别,采用调用python接口的方式进行部署及封装。vs2010版本为32位,python版本为python3.7.5 32位。除此以外需要安装cython,pyinstaller和vs2017,这三个工具是为了将python程序直接转译为可执行代码,加快运行速度用的。

过程

1.vs2010导入python库

这部分我就不写了,网上太多的例子。不过需要注意一点的是,如果vs2010是debug模式就要导入对应的dll,如python37_d.dll等,这个在单独安装python的时候会有一个选项,默认是不安装,这个需要自己选上才会在安装文件夹中出现。如果是安装anaconda的,这个可能没有。

要注意在编译的时候,会提示出现缺少inttypes.h头文件的错误,网上会有大量资源提供inttypes.h下载,但是要下载对应自己vs2010版本的文件,同时对于vs2010版,还需要安装对应版本的boost库,这时候才能正常调用python接口,我的boost库的版本是1.55版。这里我提供一个下载资源,有需要的自己下:

https://gitee.com/liuzhic_enfi/cxx_call_python.git

2.代码实践

这一块其实写的人不少,但有些方法不同意,而且很容易给后期打包的时候出现bug。
首先是启动阶段,如果是需要用到循环识别多图,这部分代码要写到循环外,如果要封装为函数,记得要返回里面模型的指针变量。

Py_SetPythonHome(L"./");//这里是python的安装目录
Py_Initialize();
if (Py_IsInitialized()==0)
{
	printf("init python failed");
}
PyEval_InitThreads(); 
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");//这里需要调用python文件的地址
model = load_model();

其中load_model()函数里面的内容是

PyObject* load_model()
{
	PyObject* pModule = PyImport_ImportModule("testpython1");//python文件名
	PyObject *pDict = NULL;
    pDict = PyModule_GetDict(pModule);
	PyObject* pFunc = NULL;
	pFunc = PyDict_GetItemString(pDict, "build"); //def func函数名
	PyObject* model =PyObject_CallObject(pFunc, NULL);
	return model;
}

循环读图中最重要的内容是将图片所在地址copy到numpy所在地址
这里附上for循环里面cv::Mat格式转为adarray格式的代码:

cv::Mat img = color_list[0].clone();
//cv::imshow("img",img);
//cv::waitKey(0);
cv::cvtColor(img,img,cv::COLOR_BGR2RGB);
int iChannels = img.channels();
int iRows = img.rows;
int iCols = img.cols * 3;

uchar* CArrays = new uchar[img.rows * img.cols * 3];

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];//
	}
}
import_array1(-1);
npy_intp Dims[3] = { img.rows, img.cols, 3 }; //
PyObject *PyArray = PyArray_SimpleNewFromData(3, Dims, NPY_UBYTE, CArrays);
//delete []CArrays ;
//CArrays =nullptr;
PyObject *ArgList = PyTuple_New(2);//传入python的type必须为tuple
PyTuple_SetItem(ArgList, 0, PyArray);//将图片导入tuple第0项
PyTuple_SetItem(ArgList, 1, model);//将模型导入tuple第1项

这一部分我一直心里感觉不舒服,是因为我只能用逐个遍历像素的方式传递地址,真正大佬都用std::memcpy()函数,不知后期有没有大佬能够用这个函数提升一下这段代码整体效率。
附:今天我把这段遍历像素的for循环改了,可以提升一部分代码效率:
原:

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

替换为:

std::memcpy(CArrays,img.ptr<uchar>(0),int(img.rows * img.cols * 3));

然后是向python函数导入变量和导出结果部分的代码:

PyObject* pModule = PyImport_ImportModule("testpython1");//文件名
PyObject *pDict = NULL;
pDict = PyModule_GetDict(pModule);
PyObject* pFunc2 = NULL;
pFunc2 = PyDict_GetItemString(pDict, "run"); //def func函数名
PyObject* pp2 = PyObject_CallObject(pFunc2, ArgList);  //传递参数,并得到结果
int res = 0;
PyArg_Parse(pp2, "i", &res);//结果转为int型
delete []CArrays ;
CArrays =nullptr;

现在循环内部就完成了,跳出循环后注意添加:

Py_Finalize();

3.打包

c++的打包让我一直很难受,因为不像python中的pyinstaller工具,可以自动找到并复制库文件(不过也是,python库文件放置那么统一,找不到也难)。这个打包时需要将python安装文件里面的大部分内容(主要是安装文件夹中的那些文件以及所用库的文件),boost库放置在工程文件夹中的bin文件夹中,这里我先提前声明,因为这里面我只知道一部分有用,剩下的文件我也不清楚,所以我就把所有文件考到bin文件夹下了。python自身代码可以利用pyinstaller打包为文件夹形式,并将文件夹内所有文件考到bin文件夹下。
如果移植到其他电脑上发现没有调用python接口,注意代码中这句:

Py_SetPythonHome(L"./");//这里是python的安装目录

设置python环境在本文件夹就可以了,基本上这句没问题都能启动,后期找不到的文件也会报错提示。这里用pyinstaller打包python的原因不仅仅是它会将程序编译为可执行文件,同时还会自动配置环境,像onnxruntime本来不支持win7环境,但是用pyinstaller打包封装后的程序是可以在win7上运行的。