上次實現的物體檢測,藉助了detectron2。html
如今要移植到c++上,detectron2裏面的模型大概是不能用了。java
安裝libtorch,瀏覽器下載很慢,換成wget就很快了,這裏操做了一下給終端設置代理,可是實際用的時候好像沒有代理也很快,不知道。python
libtorch = 1.5ios
例子hello_libtorch,https://pytorch.org/cppdocs/installing.html (輸出一個tensor,能夠用來驗證庫啥的有沒有問題)c++
(好的,忽然jupyter 忽然打不開了。昨晚還好好的,不知道是否是我設置終端代理的緣由。把代理proxychains刪了果真就行了。尷尬git
第二個例子,參考https://github.com/apachecn/pytorch-doc-zh/blob/master/docs/1.0/cpp_export.md,實現一下。github
Tracing方法看起來還算好懂的。另外一種方法適用model裏面有各類基於輸入判斷的。apache
例子裏面的代碼仍是比較落後的,有博客整理了一些坑,1.0到1.5變化不小,這裏建議你們去pytorch官網找例子運行。api
上面的兩個例子,都是加載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文件拆開。