最近想用 Qt 寫一個界面在 C++ 下調用 Pytorch 模型,整個環境配置下來遇到了不少問題,是以記錄一下。
1. 下載下傳安裝相關檔案
由于我的開發環境是 Win10,是以需要下載下傳的東西挺多的,IDE 用的 Visual Studio 2019,在 這裡 下載下傳社群版,安裝的時候可以選擇需要的編譯器,我裝的是 MSVC v142,vs2019 裡要裝一個 cmake,單獨的 cmake 可下可不下。
VS 中的 cmake.exe 在
D:\Microsoft Visual Studio\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin
路徑下。
使用 cmake 安裝包安裝的會多一個 cmake-gui.exe 可視化程式可以用。
然後下載下傳 Qt5,在 清華源 裡可以直接下載下傳,在頁面右邊的擷取下載下傳連結的應用軟體裡。
這裡選擇 MSVC 2017,雖然 VS 是 2019,但不影響。如果要用 MinGW 重新編譯 OpenCV 的就要裝 MinGW。這裡 可以看他們的差別。
安裝好之後把 Qt 的 bin 檔案夾加入環境變量,我的路徑是
E:\Qt\5.14.2\msvc2017_64\bin
。
接着下載下傳 OpenCV,Windows 平台的安裝包解壓後有
build
和
sources
兩個檔案夾。
build
檔案夾下的就是已經用 VS 編譯好的,可以直接用。
sources
就是源檔案,可以自己按需要編譯。
我沒有重新編譯,直接用原生提供的,網上有說在 Qt 中用 OpenCV 要自己重新編譯,但我目前使用發現沒什麼問題。
這裡給出兩個如何自行編譯的連結。如果需要就重新編譯一下。
- VS 編譯 OpenCV
- MinGW 編譯 OpenCV(需要在安裝 Qt 時安裝 MinGW)
Pytorch 有專門的 C++ 庫,叫 Libtorch,在 官網 上就可以下載下傳到。這裡我選擇的是 cpu 版本的,cuda 版本的還需要額外的配置,先把簡單的搞定。
Python 和 C++ 的 Pytorch 版本最好一樣,免得讀取模型出問題,我這裡統一用了 1.6 版本。
下載下傳完成,解壓後,可以把 libtorch/lib 檔案夾也添加到環境變量裡。
這是我添加的幾個環境變量。
如果出現找不到 dll 的錯誤,大機率是環境變量沒配置好,還有一個更粗暴的方法就是把缺的 dll 檔案放到 exe 檔案夾下。
2. 轉換 Pytorch 模型
這一步需要将 Pytorch 模型轉換成 Torch Script,這東西具體是個啥可以看 官方文檔。
轉換模型可以有兩種方法,第一種是 tracing,使用一個示例輸入來擷取模型結構。
第二種是在模型中添加顯式 annotations,以告知 Torch Script 編譯器可以根據 Torch Script 語言施加的限制直接解析和編譯模型代碼。
2.1 通過 Tracing 轉換
import torch
import torchvision
# 模型執行個體
model = torchvision.models.resnet18()
# 設定一個示例輸入
example = torch.rand(1, 3, 224, 224)
# 使用 torch.jit.trace 生成 torch.jit.ScriptModule
traced_script_module = torch.jit.trace(model, example)
# 儲存轉換後的模型
traced_script_module.save("traced_resnet_model.pt")
2.2 通過 Annotation 轉換
如果模型有特定的控制流,例如控制流依賴于輸入:
import torch
class MyModule(torch.nn.Module):
def __init__(self, N, M):
super(MyModule, self).__init__()
self.weight = torch.nn.Parameter(torch.rand(N, M))
def forward(self, input):
if input.sum() > 0:
output = self.weight.mv(input)
else:
output = self.weight + input
return output
# 然後通過 `torch.jit.script` 将子產品轉換為 ScriptModule。
my_module = MyModule(10,20)
sm = torch.jit.script(my_module)
# 儲存模型
my_module.save("my_module_model.pt")
3. 建立項目
建立項目時我用的不是 vs2019,而是 Qt Creator,因為 Libtorch 給的是 cmake 的建構方式,使用 Qt 建立 cmake 項目時會建立所有需要的東西。
建立一個 widget 應用。
這裡選擇 cmake。
項目建立完成後可以選擇直接用 Qt Creator 寫,也可以用 vs2019 打開 cmake 檔案繼續修改項目。我是改用了 vs2019,畢竟宇宙第一 ide 。
4. 配置 cmake
使用 Qt 建立項目後 cmake 檔案裡已經添加了部分必要的指令,這裡隻需要進行簡單的修改。
下面貼出我的 cmake 檔案,其中有些路徑需要替換成自己的
cmake_minimum_required(VERSION 3.5)
project(SegTool LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(Qt5_DIR E:/Qt/5.14.2/msvc2017_64/lib/cmake/Qt5)
set(Qt5Widgets_DIR E:/Qt/5.14.2/msvc2017_64/lib/cmake/Qt5Widgets)
set(Qt5Gui_DIR E:/Qt/5.14.2/msvc2017_64/lib/cmake/Qt5Gui)
set(Qt5Core_DIR E:/Qt/5.14.2/msvc2017_64/lib/cmake/Qt5Core)
find_package(Qt5 COMPONENTS Widgets Core Gui REQUIRED)
set(OpenCV_DIR "D:/Lib/opencv/build/x64/vc15/lib")
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
set(Torch_DIR "D:/Lib/libtorch-debug-cpu-1.6/share/cmake/Torch")
find_package(Torch REQUIRED)
include_directories(${TORCH_INCLUDE_DIRS})
qt5_wrap_ui(UI_HEADERS mainwindow.ui) # 通過 ui 檔案生成對應的頭檔案
add_executable(${PROJECT_NAME}
main.cpp
mainwindow.cpp
mainwindow.h
${UI_HEADERS}
)
target_link_libraries(${PROJECT_NAME} Qt5::Widgets)
target_link_libraries(${PROJECT_NAME} ${TORCH_LIBRARIES} ${OpenCV_LIBS})
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11)
這裡我設定了 Qt 路徑,因為如果用 Python 的可能 Anaconda 裡也有,但是用 Anaconda 裡的會有問題。
在 vs2019 中打開 CMakeSettings.json 可以看到其他配置資訊,把
顯示進階變量
勾選上可以看到所有 cmake 變量,可以看看設定都對不對。
CMakeLists.txt
裡定義的變量會覆寫 json 檔案裡的。
配置完成後 Ctrl + S 就可以看到 cmake 完成了。
5. 測試
接下來用一段 C++ 代碼來測試是否成功,修改
mainwindow.cpp
檔案(根據你建立時的類命名的)。
代碼裡的模型和圖檔路徑替換成你的就行了。
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <iostream>
#include <opencv2/opencv.hpp>
#undef slots
#include <torch/torch.h>
#include <torch/script.h>
#define slots Q_SLOTS
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
torch::jit::script::Module module;
module = torch::jit::load("model.pt"); // 模型路徑
std::cout << "Succeed in loading model" << std::endl;
cv::Mat image = cv::imread("C:/Users/lan/Desktop/2.jpg", 1); // 圖檔路徑
cv::namedWindow("My Image");
cv::imshow("My Image", image);
}
MainWindow::~MainWindow()
{
delete ui;
}
代碼中有一段
#undef
,不加會發生文法錯誤,不知道為什麼,反正加上就對了。
然後就可以編譯、生成了,編譯的時候會出現很多警告,應該是
#undef
導緻的,可以忽略。
這是全部完成後我的檔案夾裡的檔案
這是運作結果
成功!
所有的準備操作到這裡就結束了,後面就可以開始寫 qt 界面了。