开个坑,libtorch + opencv部署
libtorch1.5部署windows平台的深度学习应用
最近部署windows平台的深度学习应用,纯cpu,走了不少弯路,打算开个帖子,好好聊一聊
代码部分,暂时不公开,涉及公司机密。抽空写个简化版本的,分别给出分类、分割、检测(我想大家肯定碰到过很多问题)
libtorch想说production不容易
- 基本概念
我也不说废话了,我们做成应用,怎么都离不开windows。然而,并不是所有电脑都带cuda,所以优先选择的,当然是cpu版本。毕竟产品化的时候,还是要考虑性价比。
准备工作如下:
1.1 下载对应的libtorch,我这里用了1.5,这里得注意,release/debug两个版本都要下载,debug包里面包含你需要的pdb文件,很重要,因为你一定会crash的。release速度快了很多。(发现个问题,我用onnxruntime运行模型,速度比libtorch快了好几倍,我想应该是libtorch哪里做的有问题,官方论坛有个帖子,我反正没怎么看懂libtorch does not initialize OpenMP/MKL by default)
1.2 配置vs2017工程,这个我就不说了,没什么好说的。(cuda版本看起来必须使用cmake,不然你总是无法link到cuda的库)
1.3 然后你就开始写代码吧 - 应用架构
2.1 我这里,部署模型设计成class的结构,这样在构造class的时候,就需要传入模型文件。对外的话,提供inference接口,当然,最好的做法,我觉得,可以设计成多个接口,比如classify/segment/detect,每个接口返回值各不相同,输入参数当然都是一致的,比如,就是个cv::Mat& img这样的,我们要做的img归一化之类的,都在这些接口里面统一处理掉,方便应用端调用嘛。
2.2 封装成dll,这个就是另一回事儿了,我们晚点再说。 - 编译与环境准备
3.1 libtorch 1.5需要c++14,所以,你不得不使用vs2017。当然,你碰到一些老古董的工程还是使用vs2008之类的,那就坑爹了。你只能自己单独用vs2017做好dll,给vs2008用了,辛苦一点。
3.2 Cpu起码i5以上吧,内存别不舍得,怎么也得16G。(什么?16G都没有?那你做什么人工智能) - 部署方案(source code/dll)
4.1 最直接了当,当然是把源码集成到应用端,这个根据个人喜好,自己决定了。
4.2 当然,你肯定不希望自己辛辛苦苦写的前后处理就这么送人,对吧,那么,你就要封装成dll了,封装三个基本的接口:init, inference, destroy。init需要返回一个void指针,让应用端代持,inference的时候,需要用户传入刚刚代持的void指针,destroy也要传入void指针,释放你创建的整个模型内存。
python opencv 与 c++ opencv的差异(TMD非常坑人)
- 关键问题
1.1 opencv roi 的内存不连续问题:
很多时候,我们会对opencv读取的图片,选取其中的一部分,比如你做语义分割,训练的模型一般不会直接输入一整张大图的,对吧,太大了,模型跑不动的。所以我们会裁切,那么我们就会这么做
cv::Mat img = cv::imread("test.jpg");
cv::Mat partImg = img(cv::Rect(20,30,w,h))
//别忘了mat转float,并做归一化,因为你做的模型肯定只认得float型数据
以上就是我们习惯的做法,看起来也没什么问题,我们常年在pytorch中都是类似这种操作,习惯了,紧接着,我们肯定就会这样做
auto inputData = torch::from_blob(partImg.data, {1, h, w, 3}, torch::kFloat32);
std::vector<torch::jit::IValue> inputs;
inputs.push_back(inputData);
好了,你的噩梦从此开始了。你的预测结果怎么都不对,而且还错得很奇怪,感觉找不到规律。你可能从头开始查了输入数据,发现跟python没差别,你查了归一化后的tensor值,发现也是跟python端一致,就是不知道为什么。
此时……你正在奔溃之际,你应该好好想一想,我哪里做的跟python那边不一样?
细想,细想,你会发现,你在Mat中使用了ROI,那么这个到底有什么差异呢?
roi只是在同一个mat中标记了一块区域,这个roi对于opencv一族的函数,都是起作用的。但是,你别忘记了,torch::from_blob访问的,是mat存数据的起始指针,那么,你设置的roi当然就被无视了,所以不管你怎么做,你输入的图片数据,都是从(0,0)开始的一段固定大小**{1, h, w, 3}**
既然知道问题了,我们来改一下,很简单,一句话解决,别眨眼
cv::Mat img = cv::imread("test.jpg");
cv::Mat partImg = img(cv::Rect(20,30,w,h)).clone();
好了,到此,问题全部解决。
P.S batch forward
我发现这个问题,很多人会关心,代码呢,官方确实也没有那么直观地给出来,我这里就贴一段我写好的,希望能给大家有所帮助
std::vector<cv::Mat> imgVec;
std::vector<torch::Tensor> imgTensor;
for (int i = 0; i < imgVec.size(); i++) {
auto temp = torch::from_blob(imgVec.at(i).data, { 1, height, weight, channel}, torch::kFloat32);
temp = temp.permute({ 0,3,1,2 });
imgTensor.push_back(temp);
}
c10::ArrayRef<torch::Tensor> batchImg(imgTensor);
torch::Tensor outBatchTensor = torch::cat(batchImg, 0);
std::vector<torch::jit::IValue> inputs;
inputs.push_back(outBatchTensor);
//这里,你就可以forward了。
下篇预告
下次,我们来讲讲,我们怎么在cpu端加速我们做的这些模型,没有GPU,能快一点,是一点嘛,see you.