本文旨在记录本人在Windows下,用VS2015实现C++调用Python版的Pytorch模型过程中,踩过的坑,方便你我他!
个人环境:Win10+CUDA10+VS2015+PyTorch1.1+Python36
本文实例:CenterNet
论文翻译:https://blog.csdn.net/u011681952/article/details/90901379
关于环境搭建,本文不会详细描述,网上博客一大堆,只简单叙述下:
1)CUDA安装:本文默认有GPU
2)Anaconda安装:
A)Windows下,常用Pytorch或Tensorflow等,建议使用Anaconda来管理包
B)直接安装python,用pip来管理Pytorch,import torch 容易出现下面问题:
from torch._C import *
ImportError: DLL load failed: 找不到指定的模块
我尝试过很多方法,没能解决,放弃了
3)一些基础库安装:如python、opencv、numpy等等
4)Pycharm安装:windows下python代码调试工具,挺好用
4)Pytorch安装
5)根据CenterNet的demo提示,缺啥库安装啥库
1)依赖包安装:
打开Anaconda prompt - -> cd到进入CenterNet目录
pip install -r requirements.txt
2)DCNv2编译
官网给出的CenterNet,自带的DCNv2是linux下torch0.4.1版本的,在windows下编译不了,还有torch版本冲突,会报出
cffi包的问题:
问题1:
ImportError: torch.utils.ffi is deprecated. Please use cpp extensions instead.
问题2:
from ._ext import dcn_v2 as _backend
ModuleNotFoundError: No module named 'models.networks.DCNv2._ext'
解决链接:https://github.com/xingyizhou/CenterNet/issues/7
根据解决链接,直接替换DCNv2,并修改dcn_v2_cuda.cu
//extern THCState *state;
THCState *state = at::globalContext().lazyInitCUDA(); // Modified
编译:
编译方案A:进入DCNv2,直接运行make.sh
make.sh中:
python setup.py build develop
这样编译的linux下so库或windows下pyd库文件,在当前文件夹DCNv2中
当然在DCNv2文件夹不变的情况下,运行centernet是正常的
若需要打包移植或so文件地址变动就会出问题,可以参见方案B
编译方案B:若为了方便移植,修改make.sh运行:
python3 setup.py build
python3 setup.py install
#可删除本地无用文件
rm -rf build
rm -rf dist
rm -rf DCNv2.egg-info
它是将其安装为依赖包了,编译好后,在pycharm里会看到它
3)external编译
进入external目录
python setup.py build_ext --inplace
编译结果
4)cl.exe问题
上面2,3编译中,若出现:
cl.exe 找不到的问题
解决:
A)确定安装VS2015时选择了C++,若没有,再次安装VS2015,选择modify模式,勾选上C++安装即可
B)将cl.exe所在目录(自己的vs2015安装路径下找)加入到系统path目录
到此应该解决所有问题,也可能有些小问题,我给忘了(本文是事后写的)
这里就是下载模型、设置路径什么的,不多说;
用我自己训练的COCO模型(ubuntu下训练的),展示下,只训练了55 epoch(两个1080ti都跑了5天多,时间关系没继续训练)
这部分才是本文要吐槽的重点,知识点其实不难,只是本人之前没有用过C++和Python的混编,在完成这个任务时遇到了一些坑,差点把我整吐了
1)在vs2015中新建一个win32控制台应用程序(x64,Debug模式,方便调试)
2)配置:
A)头文件,库目录,配置
因为我程序中要用到numpy,所以这里也加了numpy头文件目录(当然还有opencv配置,根据自己情况而定)
B)库
安装python时,没有生成python36_d.dll,网友称可以复制python36.dll改为python36_d.dll,我尝试了没有问题(当然直接安装python是可以生成python36_d.dll的)
复制python36.dll到工程exe同级目录
C)环境变量:PYTHONHOME
将python.exe所在目录设为PYTHONHOME,重启电脑;这步还是很重要的
1)头文件:
#include
#include
#include "Python.h"
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "numpy/ndarrayobject.h"
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
2)main函数:
main函数中,实现了C++调用pytorch模型需要的步骤,包括初始化,python模块加载,python类加载,类方法调用,函数调用,参数设置等
这里相当于实现的是centernet的demo.py中的__main__函数
int main(int argc, wchar_t **argv)
{
PyObject* pOpt = NULL;
PyObject* pDict = NULL;
PyObject* pClass = NULL;
PyObject* pInit = NULL;
PyObject* pInstance = NULL;
PyObject* pName = NULL;
PyObject* pMod = NULL;
PyObject* pFunc = NULL;
PyObject* pParm = NULL;
PyObject* pRetVal = NULL;
//初始化python解释器
Py_Initialize();
if (!Py_IsInitialized()) {
return -1;
}
PySys_SetArgv(argc, argv);
//加载运行目录
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('E:/MyProjects/PyCharm/CenterNet/src')");
PyRun_SimpleString("sys.path.append('E:/MyProjects/PyCharm/CenterNet/src/lib')");
//opt 初始化化
// Load the module object
//加载opts.py
pOpt = PyImport_ImportModule("opts");
if (!pOpt)
{
return -1;
}
//因为是opts class,则用类加载方法
// pDict is a borrowed reference
pDict = PyModule_GetDict(pOpt);
// Build the name of a callable class
const char* initClassName = "opts";
pClass = PyDict_GetItemString(pDict, initClassName);
// Create an instance of the class
if (PyCallable_Check(pClass))
{
pInstance = PyObject_CallObject(pClass, NULL);
}
// Call a method of the class with no parameters
const char* initFuncName = "init";
pInit = PyObject_CallMethod(pInstance, initFuncName, NULL);
//加载demo.py
//demo
pName = PyUnicode_FromString("demo");
pMod = PyImport_Import(pName);
if (!pMod)
{
return -1;
}
const char* demoFuncName = "demo"; //这是此py文件模块中被调用的函数名字
pFunc = PyObject_GetAttrString(pMod, demoFuncName);
if (!pFunc)
{
return -2;
}
//参数设置
pParm = PyTuple_New(1);
PyTuple_SetItem(pParm, 0, pInit);
pRetVal = PyEval_CallObject(pFunc, pParm);
return 0;
}
3)效果展示
4)小补充:PySys_SetArgv(argc, argv)
在mian函数中,有argc, argv这两个参数,像上面用没有问题;
但在实际应用中,我们可能需要将init重新封装为C++类函数,我们可能不需要传参,这时也没有argc, argv这两个参数,就会报错,可以这样解决:
wchar_t **argv_tmp;
wchar_t *tmp = (wchar_t *)"xxx";
argv_tmp = &tmp;
PySys_SetArgv(0, argv_tmp);
在调试过程中,调用python时,出错了,一般是不会抛出具体错误提示的,只会看到这样的提示:
我的办法是在出错的那句代码后调用下一PyRun_SimpleString,如:
PyRun_SimpleString("import sys");
这样,在调试时,dos窗口会抛出具体错误信息,根据错误信息修改bug
在C++调用python过程中,不避免需要为其传参,上面main函数也有展示,这里有些参考博客;可以看到,传一些数字,字符串等比较简单
但我们常常需要传入图像,如Mat对象,就有点麻烦了;对于不熟悉C++和Python混编的我,差点吐了;最终找到一篇文档,才完美解决,直接写一个转换函数,将Mat图像转化为PyObject对象
PyObject* convertImage(const cv::Mat& image) {
//2D image with 3 channels.
npy_intp dimensions[3] = { image.rows, image.cols, image.channels() };
//image.dims = 2 for a 2D image, so add another dimension for channels.
return PyArray_SimpleNewFromData(image.dims + 1, (npy_intp*)&dimensions, NPY_UINT8, (void *)image.data);
}
将CenterNet应用到自己的场景下,在本任务中,我们成功将其迁移到公司项目,效果非常不错,相较之前一些经典目标检测算法,效果杠杠的,漏检低,误检低,预测框的精度高
因涉及公司项目,这里就不描述太多