LibTorch_物体检测_RCNN
转载:https://www.cnblogs.com/zherlock/p/12771136.html
上次实现的物体检测,借助了detectron2。
现在要移植到c++上,detectron2里面的模型大概是不能用了。
安装libtorch,浏览器下载很慢,换成wget就很快了,这里操作了一下给终端设置代理,但是实际用的时候好像没有代理也很快,不知道。
libtorch = 1.5
例子hello_libtorch,https://pytorch.org/cppdocs/installing.html (输出一个tensor,可以用来验证库啥的有没有问题)
(好的,突然jupyter 突然打不开了。昨晚还好好的,不知道是不是我设置终端代理的原因。把代理proxychains删了果然就好了。尴尬
第二个例子,参考https://github.com/apachecn/pytorch-doc-zh/blob/master/docs/1.0/cpp_export.md,实现一下。
Tracing方法看起来还算好懂的。另一种方法适用model里面有各种基于输入判断的。
例子里面的代码还是比较落后的,有博客整理了一些坑,1.0到1.5变化不小,这里建议大家去pytorch官网找例子运行。
上面的两个例子,都是加载resnet18的,下面加载rcnn系列的,真的坑,真的坑。
OK, 切换到物体检测模型,https://pytorch.org/docs/master/torchvision/models.html#object-detection-instance-segmentation-and-person-keypoint-detection,试了一下pre_trained faster rcnn,巨慢,我印象里detectron2里面的maskrcnn也没有这么慢啊,咋回事,那个就5,6秒,重启了一次,好像速度变正常了。
python下序列化模型,注意这里不是trace方法,是script方法, rcnn不能用trace方法来序列化,原因的话,没看懂。
model = fasterrcnn_resnet50_fpn(pretrained=True)
model.eval()
traced_model = torch.jit.script(model)
接下来就都是坑了。首先,莫名其妙的需要torchvision了,libtorch不够用了。而且这个torchvision要自己从源码编译出来的,不是安装pytorch自带的torchvision,这里这些包的关系有点混乱,笔者也还没搞清楚。
主要参考
https://github.com/pytorch/vision/issues/1849
https://github.com/pytorch/vision/pull/1407
前面1849这个issue的例子,就是个完整的过程。
#c++代码
1 #include <torch/script.h> // One-stop header. 2 #include <iostream> 3 #include <memory> 4 5 #include <iostream> 6 #include "torch/script.h" 7 #include "torch/torch.h" 8 #include "torchvision/vision.h" 9 #include "torchvision/ROIAlign.h" 10 #include "torchvision/ROIPool.h" 11 #include "torchvision/empty_tensor_op.h" 12 #include "torchvision/nms.h" 13 #include <cuda.h> 14 15 using namespace std; 16 17 static auto registry = 18 torch::RegisterOperators() 19 .op("torchvision::nms", &nms) 20 .op("torchvision::roi_align(Tensor input, Tensor rois, float spatial_scale, int pooled_height, int pooled_width, int sampling_ratio) -> Tensor", 21 &roi_align) 22 .op("torchvision::roi_pool", &roi_pool) 23 .op("torchvision::_new_empty_tensor_op", &new_empty_tensor); 24 25 26 int main(int argc, const char* argv[]) { 27 if (argc != 2) { 28 std::cerr << "usage: example-app <path-to-exported-script-module>\n"; 29 return -1; 30 } 31 32 33 torch::jit::script::Module module; 34 try { 35 // Deserialize the ScriptModule from a file using torch::jit::load(). 36 module = torch::jit::load(argv[1]); 37 } 38 catch (const c10::Error& e) { 39 std::cerr << "error loading the model\n"; 40 return -1; 41 }
可以看到头文件里,有很多libtorch里面没有的库,抬头是torchvision。一想,torchvision,不是安装pytorch的时候就一起安装了吗,跑到conda对应目录下面找,不好意思,并没有这些头文件。但是去官方的github下面,是有这些个头文件的,好像就少了个csrc文件夹,然后这些库文件都在这个目录下。那去https://github.com/pytorch/vision/下载源码,然后编译安装,不幸的是,官方给的编译方法也失败了,这个当时没记报错,大家能直接编译的应该也OK的。这个下载和编译的过程,在https://github.com/pytorch/vision/issues/1849里面已经提到了,我按它的做法来了是Ok的,总之选择一个位置安装编译之后的torchvision库,在include文件夹下面,是能找到上面出现的头文件像vision.h。底下的cmake指令,如果用cuda,要加一个cuad支持,变成cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch -DCMAKE_INSTALL_PREFIX=/where/to/install/torchvision -DCMAKE_BUILD_TYPE=Release [-DWITH_CUDA=ON] ..
1 git clone https://github.com/pytorch/vision.git 2 cd vision 3 mkdir build && cd build 4 cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch -DCMAKE_INSTALL_PREFIX=/where/to/install/torchvision -DCMAKE_BUILD_TYPE=Release .. 5 cmake --build . -j86 cmake --install .
接下来,修改cmakelist, 因为原本,只要链接libtorch库,现在多了一个torchvison,这个地方笔者花了很多时间,本来cmake就不太会用。先看一下https://github.com/pytorch/vision/issues/1849 里面的方法。cmakelist写成
1 cmake_minimum_required(VERSION 3.0 FATAL_ERROR) 2 project(custom_ops) 3 4 set(pybind11_DIR /path/to/pybind11/share/cmake/pybind11) 5 6 find_package(Torch REQUIRED) 7 find_package(TorchVision REQUIRED) 8 9 add_executable(example-app example-app.cpp) 10 target_link_libraries(example-app "${TORCH_LIBRARIES}" TorchVision::TorchVision) 11 set_property(TARGET example-app PROPERTY CXX_STANDARD 14)
他们用find package加上运行时指定cmake的prefix,就可以顺利找到torchvision然后链接。前面只链接libtorch的时候有,cmake命令是
cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch .. #现在增加一个torchvision的路径 cmake -DCMAKE_PREFIX_PATH="/path/to/libtorch;/where/to/install/torchvision" ..
然后理论上能顺利编译。但是笔者这里不行,cmake的运行是顺利的,没有说find package(torchvison)失败,但是make过程,一直提示头文件找不到,然后笔者各种百度加问同学,决定还是暴力的把所需的库链接上去。
哪个头文件提示找不到,就去torchvison和libtorch下找到对应的目录,然后include_directories。(PS, PYTHON.h找不到我还是有点惊讶的。)
include_directories("/home/xxxx/torchvision/include") include_directories("/home/xxxx/SLAM/libtorch/libtorch/include/torch/csrc/api/include/") include_directories("/home/xxxx/SLAM/libtorch/libtorch/include/") include_directories("/home/xxxx/anaconda3/envs/detectron2/include/python3.7m/")
只是把头文件目录链接进来还不够,毕竟真正的库还没找到
target_link_libraries(example-app "${TORCH_LIBRARIES}" TorchVision::TorchVision /home/xxxx/torchvision/lib/libtorchvision.so)
这里把我们指明的libtorchvision.so文件放进来。torchvision::torchvision是find_package找到的包,我在这里没管,可能删掉也不影响了。
然后在编译运行,ok了。出现了很多warning,先不管。回顾一下,为什么resnet不需要这些麻烦的步骤,似乎是因为rcnn存在多个输出,label啊,score啊,方框啊等等,所以才导致开发人员要通过一些策略,来使其运行,就是static auto registry =这段代码的含义,更深刻的我也解释不了了,期待专业人士吧。换句话说,libtorch这里自身还没有开发得很完善,可能再等几个版本这些东西都不需要了。这里笔者是因为自身pytorch用的觉得更方便顺手,所以一开始就选择了pytorch下的c++框架,或许tensorflow那边开发的更完善也说不定。对了,上面的指令我都是在cpu环境测试的,用gpu的朋友,有些地方要设定cuda之类的,可以在上面的参考链接里找,印象里就编译otrchvision里面有一个。
上面的步骤,才刚刚让模型能成功加载出来,我们的目标是,让模型识别一张图片并读取输出。
把main里面的代码修改成这样,测试一下模型能不能顺利输出。笔者这里都是可以的了,就是warning比较多。
if (argc != 2) { std::cerr << "usage: example-app <path-to-exported-script-module>\n"; return -1; } torch::jit::getProfilingMode() = false; torch::jit::getExecutorMode() = false; torch::jit::setGraphExecutorOptimize(false); // Deserialize the ScriptModule from a file using torch::jit::load(). torch::jit::script::Module module = torch::jit::load(argv[1]); std::cout << "load is good" <<std::endl; torch::Tensor tensor_image = torch::ones({3,480,640}); std::cout << "tensor image is good" <<std::endl; c10::List<Tensor> images = c10::List<Tensor>({tensor_image, tensor_image}); std::cout << "list tensor image is good" <<std::endl; std::vector<torch::jit::IValue> inputs; inputs.emplace_back(images); torch::jit::IValue output = module.forward(inputs); std::cout << "output is good" <<std::endl;
接下来,读取一张图片,把它变成image tensor,或者说,读取opencv的一个frame,把它变成image tensor。
然后就是大坑,imread出问题了,imread未定义的报错。这个错github上也看到人讨论了,讲是ABI11的问题,官方给的libtorch没有ABI-11,但是opencv是需要这个的,什么是abi11,百度一下,反正我是没太看明白。
关键在于,libtorch1.5版本,官方已经开始提供有ABI11支持的libtorch库了,两种我都下载了,之前一直测试的都是没有ABI-11支持的,那我想,遇到这个bug不是换一个库就行了吗,简单。
并不行,换成了abi-11的那个库,make 的时候torchvison 开始报错说找不到torch里面的函数,就很怪,也没搜到相关资料;这里笔者失误了,现在想起来应该先测试torch和opencv共存的问题,torchvision报错之后再解决,当时比较快的就去找别的方案了。
最后找了一圈的方案是,把opencv卸了重装,编译opencv的时候把abi11去掉。细节参考https://blog.csdn.net/a1040193597/article/details/105011008
(手动删opencv也删了半天,,以前安装的目录根本找不到。。装了3.4.0之后,还不知道orbslam能不能运行了,希望可以。)
opencv这边一直没出什么bug,重装之后,程序就能运行了。稍微在源码里看了下API,大概知道咋回事了
1 Mat frame = cv::imread("1.png", IMREAD_COLOR); 2 cvtColor(frame, frame, CV_BGR2RGB); //转换色彩通道 3 frame.convertTo(frame, CV_32FC3, 1.0f / 255.0f); 4 auto tensor_image = torch::from_blob(frame.data, {frame.rows, frame.cols, frame.channels()}); 5 //换chw; 6 tensor_image = tensor_image.permute({2, 0, 1}); 7 //cout << "tensor shape " << tensor_image <<endl; 8 9 //禁止一些服务加速模型推理的 10 torch::jit::getProfilingMode() = false; 11 torch::jit::getExecutorMode() = false; 12 torch::jit::setGraphExecutorOptimize(false); 13 14 // Deserialize the ScriptModule from a file using torch::jit::load(). 15 torch::jit::script::Module module = torch::jit::load(argv[1]); 16 17 c10::List<Tensor> images = c10::List<Tensor>({tensor_image}); 18 std::vector<torch::jit::IValue> inputs; 19 inputs.emplace_back(images); 20 torch::jit::IValue output = module.forward(inputs); 21 //怪怪的,output确实和最原始的rcnn不一样,里面多了一个空的{}; 22 23 auto out1 = output.toTuple(); 24 auto dets0 = out1->elements().at(0); 25 26 auto dets1 = out1->elements().at(1).toList().get(0).toGenericDict() ; 27 28 at::Tensor masks = dets1.at("scores").toTensor(); 29 at::Tensor labels = dets1.at("labels").toTensor(); 30 at::Tensor boxes = dets1.at("boxes").toTensor(); 31 32 int label = labels[10].item().toInt(); 33 cout << "labels is " << label << endl; 34 35 float c1 = boxes[0][0].item().toFloat(); 36 float c2 = boxes[0][1].item().toFloat(); 37 float c3 = boxes[0][2].item().toFloat(); 38 float c4 = boxes[0][3].item().toFloat(); 39 40 cout << "box 0 is " << c1 << "---" << c2 << "---" << c3 << "---" << c4 << "---" << endl;
几个重要的API就是,from_blob,把mat转tensor,permute改一下维度顺序,比较奇怪的是,最后output的输出是,一个tuple里面包含了一个空的字典和一个list,list里面再是一个dict,反正层层解外套后,能提取出我们想要的数据,转化成c++类型的api也在上面了。
性能的评估:
运行时间在我的笔记本cpu上要5s左右,加载模型要2s.
c++和python对同一图片的计算结果是一致的。
中间还有个要注意的是,model序列化之前要进行一次model.eval(),要不然存下来不能推理,这个容易忘。
set(CMAKE_PREFIX_PATH
"XXX/libtorch"
)
//注意这里填自己解压libtorch时的路径
,可以不用每次命令写prefix了。哇,要是多懂一点cmake,这两天可以省很多时间吧。
https://www.cnblogs.com/geoffreyone/p/10827010.html 里面提到了一个不用imerad读取图片的API,然后不会报错,但是冲突的API应该包括imshow还有几个,所以大家可以考虑,这里作者也说新版本(2019.5)就可以了,不知道我这个更新的版本为什么还有问题。可能这个作者都是自己源码编译的,很有可能,libtorch源码编译也是一种方案,我没去试。
5.4更新
libtorch结合orbslam一起运行,把物体识别写成一个模块。
遇到几个问题
1.编译时候c++14的选项,libtorch必须要c++14。cmakelist里面加上
add_definitions(-std=c++14)
2.
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘nms(at::Tensor const&, at::Tensor const&, double)’中:
InstanceDetect.cc:(.text+0x2e0): `nms(at::Tensor const&, at::Tensor const&, double)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x1d30):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘PSROIPool_forward(at::Tensor const&, at::Tensor const&, float, int, int)’中:
InstanceDetect.cc:(.text+0x5a0): `PSROIPool_forward(at::Tensor const&, at::Tensor const&, float, int, int)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x2000):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘ROIPool_forward(at::Tensor const&, at::Tensor const&, double, long, long)’中:
InstanceDetect.cc:(.text+0x870): `ROIPool_forward(at::Tensor const&, at::Tensor const&, double, long, long)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x22e0):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘PSROIAlign_forward(at::Tensor const&, at::Tensor const&, float, int, int, int)’中:
InstanceDetect.cc:(.text+0xb50): `PSROIAlign_forward(at::Tensor const&, at::Tensor const&, float, int, int, int)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x25c0):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘ROIAlign_forward(at::Tensor const&, at::Tensor const&, double, long, long, long, bool)’中:
InstanceDetect.cc:(.text+0xe30): `ROIAlign_forward(at::Tensor const&, at::Tensor const&, double, long, long, long, bool)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x28b0):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘ROIPool_backward(at::Tensor const&, at::Tensor const&, at::Tensor const&, float, int, int, int, int, int, int)’中:
InstanceDetect.cc:(.text+0x1130): `ROIPool_backward(at::Tensor const&, at::Tensor const&, at::Tensor const&, float, int, int, int, int, int, int)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x2bc0):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘PSROIPool_backward(at::Tensor const&, at::Tensor const&, at::Tensor const&, float, int, int, int, int, int, int)’中:
InstanceDetect.cc:(.text+0x1430): `PSROIPool_backward(at::Tensor const&, at::Tensor const&, at::Tensor const&, float, int, int, int, int, int, int)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x2ed0):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘ROIAlign_backward(at::Tensor const&, at::Tensor const&, float, int, int, int, int, int, int, int, bool)’中:
InstanceDetect.cc:(.text+0x1730): `ROIAlign_backward(at::Tensor const&, at::Tensor const&, float, int, int, int, int, int, int, int, bool)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x1a10):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘PSROIAlign_backward(at::Tensor const&, at::Tensor const&, at::Tensor const&, float, int, int, int, int, int, int, int)’中:
InstanceDetect.cc:(.text+0x1a40): `PSROIAlign_backward(at::Tensor const&, at::Tensor const&, at::Tensor const&, float, int, int, int, int, int, int, int)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x31e0):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘roi_align(at::Tensor const&, at::Tensor const&, double, long, long, long, bool)’中:
InstanceDetect.cc:(.text+0x4ea0): `roi_align(at::Tensor const&, at::Tensor const&, double, long, long, long, bool)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x7dd0):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘new_empty_tensor(at::Tensor const&, c10::List<long>)’中:
InstanceDetect.cc:(.text+0x4f50): `new_empty_tensor(at::Tensor const&, c10::List<long>)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x7e80):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘roi_pool(at::Tensor const&, at::Tensor const&, double, long, long)’中:
InstanceDetect.cc:(.text+0x4fc0): `roi_pool(at::Tensor const&, at::Tensor const&, double, long, long)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x7ef0):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘ps_roi_align(at::Tensor const&, at::Tensor const&, double, long, long, long)’中:
InstanceDetect.cc:(.text+0x5060): `ps_roi_align(at::Tensor const&, at::Tensor const&, double, long, long, long)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x7f90):第一次在此定义
CMakeFiles/ORB_SLAM2.dir/src/InstanceDetect.cc.o:在函数‘ps_roi_pool(at::Tensor const&, at::Tensor const&, double, long, long)’中:
InstanceDetect.cc:(.text+0x5110): `ps_roi_pool(at::Tensor const&, at::Tensor const&, double, long, long)'被多次定义
CMakeFiles/ORB_SLAM2.dir/src/System.cc.o:System.cc:(.text+0x8040):第一次在此定义
3. 参考https://github.com/jiexiong2016/GCNv2_SLAM/issues/2,noabi的libtorch和dbow还要pangolin有了冲突。
../lib/libORB_SLAM2.so:对‘pangolin::Split(std::string const&, char)’未定义的引用
../lib/libORB_SLAM2.so:对‘pangolin::BindToContext(std::string)’未定义的引用
../lib/libORB_SLAM2.so:对‘DBoW2::FORB::toString(cv::Mat const&)’未定义的引用
../lib/libORB_SLAM2.so:对‘pangolin::CreateWindowAndBind(std::string, int, int, pangolin::Params const&)’未定义的引用
../lib/libORB_SLAM2.so:对‘DBoW2::FORB::fromString(cv::Mat&, std::string const&)’未定义的引用
../lib/libORB_SLAM2.so:对‘pangolin::CreatePanel(std::string const&)’未定义的引用
尝试编译一个no abi的dbow2,然后报错,似乎是强制的。
换个思路,abi11本来就是更好的选项。回到前面的问题,用了abi11的libtorch,为什么torchvision会出现链接错误,啊,是因为编译torchvision的时候选择了那个no-abi的libtorch。重新编译一个版本,OK了,abi的问题,现在所有的库都可以上有abi的版本了。
回到错误2,重复定义的error,检查了一下,torchvision里面有几个,头文件,是包含了函数的定义的,在多cpp的工程下,就会出现这个问题。这个和ifndef是无关的。
最直接的策略,把头文件和cpp文件拆开。
每一个不曾起舞的日子,都是对生命的辜负。
But it is the same with man as with the tree. The more he seeks to rise into the height and light, the more vigorously do his roots struggle earthward, downward, into the dark, the deep - into evil.
其实人跟树是一样的,越是向往高处的阳光,它的根就越要伸向黑暗的地底。----尼采