最近由于项目需求需要爱VTM中调用python脚本,记录一下大致的使用过程
一、编译
编译x265/VTM/HM时候,需要在CMakeLists.txt中添加如下语句
# python3
include_directories("D:/Anaconda/include/")
link_libraries("D:/Anaconda/libs/python36.lib")
因为我下载的是Anaconda,所以这里需要包含的是anaconda下的路径。添加完成后,正常编译就行
问题:
由于Anaconda中没有python36_d.lib,因此会导致无法使用VS进行Debug,一个解决方法是可以复制一份python36.lib并重命名为python36_d.lib,之后再将其加入到CmakeLists.txt中:
# python3
include_directories("C:/Users/QWH/Anaconda3/include/")
link_libraries("C:/Users/QWH/Anaconda3/libs/python36.lib")
link_libraries("C:/Users/QWH/Anaconda3/libs/python36_d.lib")
二、C++调用Python的脚本
编译完成后,用VS2017打开工程
1、添加python头文件
#include<Python.h>
2、在使用python库函数之前应该初始化python接口
Py_Initialize();
与之对应的是,调用完python脚本之后,记得释放python接口:
Py_Finalize();
注意:初始化和释放这两个函数整个程序中只能调用一次,所以需要放在整个程序最开始和最后的地方,如果出现多次调用的情况,则可能会发生访问冲突或者找不到.dll等的错误。
3、切换python脚本下的工作路径
PyRun_SimpleString("import sys");
PyRun_SimpleString("import os");
PyRun_SimpleString("os.chdir('./Net')");
PyRun_SimpleString("sys.path.append('./')");
这里使用os.chdir切换到python脚本所在的路径,然后使用sys.path.append('./')将当前目录添加到工作路径下
调用完成之后,使用os.chdir切换回到之前的工作目录下
4、加载模块和函数
m_pMod = PyImport_ImportModule("testnet");
if (!m_pMod)
{
PyErr_Print();
}
m_pFunc = PyObject_GetAttrString(m_pMod, "run");
if (!m_pFunc)
{
PyErr_Print();
}
这里我调用的python的脚本名称是testnet.py文件,所以使用PyImport_ImportModule函数将该python文件调用进来,然后使用PyObject_GetAttrString调用相应的函数,这里的run是定义在testnet.py文件中的python函数名
注意:我们每次引用完脚本和函数之后,需要使用PyErr_Print()函数检查是否出错;
5、设置参数,并将参数传递给python函数,调用python函数
PyObject *pReturn = NULL;
PyObject *pInputRec = PyList_New(TuWidth * TuHeight);
PyObject *pParam = PyTuple_New(3);
for (int row = 0; row < TuHeight; row++)
{
for (int col = 0; col < TuWidth; col++)
{
PyList_SetItem(pInputRec, row * TuWidth + col, Py_BuildValue("i", NNRecon[row * TuWidth + col]));
}
}
PyTuple_SetItem(pParam, 0, pInputRec);
PyTuple_SetItem(pParam, 1, Py_BuildValue("i", TuWidth));
PyTuple_SetItem(pParam, 2, Py_BuildValue("i", TuHeight));
pReturn = PyEval_CallObject(m_pFunc, pParam);//调用python中函数
PyList_Check(pReturn);
我们可以使用PyList_New函数定义一个python列表,PyTuple_New函数定义一个python元组,Py_BuildValue函数将C++类型的数转换为python对象,"i"表示转换后的格式(整型),除了"i"还有以下格式:
- "s" (string) [char *] :将C字符串转换成Python对象,如果C字符串为空,返回NONE。
- "s#" (string) [char *, int] :将C字符串和它的长度转换成Python对象,如果C字符串为空指针,长度忽略,返回NONE。
- "z" (string or None) [char *] :作用同"s"。
- "z#" (string or None) [char *, int] :作用同"s#"。
- "i" (integer) [int] :将一个C类型的int转换成Python int对象。
- "b" (integer) [char] :作用同"i"。
- "h" (integer) [short int] :作用同"i"。
- "i" (integer) [long int] :将C类型的long转换成Pyhon中的int对象。
- "c" (string of length 1) [char] :将C类型的char转换成长度为1的Python字符串对象。
- "d" (float) [double] :将C类型的double转换成python中的浮点型对象。
- "f" (float) [float] :作用同"d"。
- "O&" (object) [converter, anything] :将任何数据类型通过转换函数转换成Python对象,这些数据作为转换函数的参数
使用PyList_SetItem函数可以设置列表中的元素值,第一个参数是所要设置的列表,第二个参数指的是列表的索引值,第三个参数指的是所要设置的值。同理PyTuple_SetItem函数可以设置元组中的值
设置完参数之后,使用PyEval_CallObject函数可以调用python的函数并传递参数,其第一个参数指的是所要调用的函数,第二个参数指的是函数的参数。
因为我定义的run函数返回的是一个python列表,所以获得python返回值之后,调用PyList_Check函数判断是否是一个Python List(列表)
6、解析返回值,将python对象转换为C++类型
for (int row = 0; row < TuHeight; row++)
{
for (int col = 0; col < TuWidth; col++)
{
PyArg_Parse(PyList_GetItem(pReturn, row * TuWidth + col), "i", &NNRecon[row * TuWidth + col]);
}
}
PyList_GetItem函数可以获得python列表中的对应索引处的值,第一个参数表示python列表,第二个参数表示索引。
使用PyArg_Parse函数可以将python对象转换为C++类型的值,其第一个参数表示python对象,第二个参数表示转换之后的C++类型格式,第三个参数转换后的值赋给的变量(必须加上&符号)
7、结束
Py_DECREF(pInputRec);
Py_DECREF(pReturn);
Py_DECREF(pParam);
Py_Finalize();
整个python调用结束之后,使用 Py_DECREF(PyObject*)函数释放资源,方便python对象垃圾回收。