Setting up Qt Projects with CMake and Linking External Libraries
In the past few days, I attempted to develop a Windows application that utilized the Qt libraries for the GUI portion and libtorch and OpenCV for core calculations. The project was managed using cmake and compiled using the MSVC compiler. However, during development, I encountered several issues. In this article, I will detail these problems and provide solutions to fix them.
What is cmake and Qt, libtorch…
- CMake is an open-source build system generator that is used to manage the build process of software projects. It is a cross-platform tool that generates build files for various operating systems, including Windows, Linux, and macOS. CMake allows developers to write build scripts in a simple and portable way, which can be used to build and manage software projects of different sizes and complexities.
- Qt is a cross-platform application development framework used for creating desktop, mobile, and embedded applications. One of the major advantages of Qt is its cross-platform support, allowing developers to create applications that can run on multiple operating systems such as Windows, Linux, macOS, and mobile platforms like Android and iOS. Qt also provides a rich set of GUI widgets and provides easy access to many platform-specific features.
- Libtorch is a C++ library for PyTorch, an open-source machine learning framework. Libtorch allows developers to use PyTorch in C++ applications, enabling high-performance machine learning inference on CPUs and GPUs. Libtorch provides a simple and flexible API for loading and running PyTorch models in C++.(In next session I will show you how to export PyTorch model and load it in C++ code)
- OpenCV (Open Source Computer Vision) is an open-source library of programming functions mainly aimed at real-time computer vision tasks, such as image processing, object detection, and machine learning. (In my project is just for load image)
Environment needed
- C++17 or higher
- OpenCV 4.7.0 or higher
- LibTorch 1.9.0 or higher
- Qt 6.6.0 or higher
- MSVC compiler (from VS2022 or higher) :hammer:
Export model from PyTorch and Load in C++ code
In python train model code:
# I use the res2next50 model as training model
model = timm.create_model('res2next50', pretrained=False)
# --------------- after trained ---------------
# first use a input
image = Image.open("/kaggle/input/lung-and-colon-cancer-histopathological-images/lung_colon_image_set/lung_image_sets/lung_n/lungn1007.jpeg")
# make a prg image to tenser with size 1X3X224X224
image_tfm = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
])
img = image_tfm(image)
img = img[None, :]
# do a forward and export model
traced_script_module = torch.jit.trace(model, img)
traced_script_module.save("resnet_model.pt")
Load model in C++ and how to run it
#include <torch/script.h>
#include <opencv2/opencv.hpp>
int main(){
// load module from enter path
torch::jit::script::Module model;
try {
// Deserialize the ScriptModule from a file using torch::jit::load().
model = torch::jit::load(model_path);
model.eval();
}
catch (const c10::Error& e) {
std::cerr << "error loading the model\n";
std::cerr << e.what();
}
// input image
cv::Mat image;
try {
// Load image using OpenCV
image = cv::imread(image_path);
// Check if image was loaded successfully
if (image.empty()) {
throw std::runtime_error("Failed to load image");
}
// Convert BGR to RGB format
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
// resize to 224*224
cv::resize(image, image, cv::Size(224, 224));
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return -1;
}
// Create a vector of inputs.
std::vector<torch::jit::IValue> inputs;
// convert image to tenser
auto tensor_image =
torch::from_blob(image.data, { image.rows, image.cols, 3 }, torch::kByte)
.permute({ 2, 0, 1 });
tensor_image = tensor_image.unsqueeze(0).to(torch::kFloat) / 255.0;
// add tensor image to inputs
inputs.push_back(tensor_image);
// Execute the model and turn its output into a tensor.
at::Tensor output = model.forward(inputs).toTensor();
// softmax to tenser that make sum of tensor item equal to 1
output = torch::softmax(output, 1);
}
In previous code has three3️⃣ points must notice:
If you download the libtorch release version, you must build your project as a release version. If you build your project as a debug version, the program will not work. The running error will be as follows:
error loading the model open file failed because of errno 42 on fopen: Illegal byte sequence, file path: Exception raised from RAIIFile at C:\actions-runner\_work\pytorch\pytorch\builder\windows\pytorch\caffe2\serialize\file_adapter.cc:27 (most recent call first): 00007FFB4C58F66200007FFB4C58F600 c10.dll!c10::Error::Error [<unknown file> @ <unknown line number>] 00007FFB4C58F2AA00007FFB4C58F250 c10.dll!c10::detail::torchCheckFail [<unknown file> @ <unknown line number>] 00007FFAFA62FF4300007FFAFA62FE50 torch_cpu.dll!caffe2::serialize::FileAdapter::FileAdapter [<unknown file> @ <unknown line number>] . [ INFO:[email protected]] global plugin_loader.impl.hpp:67 cv::plugin::impl::DynamicLib::libraryLoad load D:\Qt_project\build-LCHIC-Desktop_Qt_6_6_0_MSVC2019_64bit-Debug\bin\calc_core\opencv_core_parallel_openmp470_64d.dll => FAILED [ INFO:[email protected]] global plugin_loader.impl.hpp:67 cv::plugin::impl::DynamicLib::libraryLoad load opencv_core_parallel_openmp470_64d.dll => FAILED Press <RETURN> to close this window...
In the previous C++ code, on line 29, I converted the image format from BGR to RGB. By default, OpenCV loads images in BGR mode (blue, green, red), whereas the
Image.open
function in Python training code loads images in RGB format.In the previous C++ code, on line 54, I got the output for all probabilities. How can I get one specific probability and convert it to a C++ type (e.g.,
int
,float
, etc.)? The output size is 1xN.// get index 0 probability float pro = output[0][0].item<float>(); // get the max probability idx int max_pro_idx = torch::argmax(output).item<int>();
create shared library in windows
In my project, I used the Qt libraries for the GUI portion and the libtorch and OpenCV libraries for the core calculations. Therefore, I want to create a shared library called model
that links the PyTorch and OpenCV libraries together.
# import libtorch
set(CMAKE_PREFIX_PATH "replease to your libtorch installed folder📂")
find_package(Torch REQUIRED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}")
# import opencv this need installed opencv and add environment path
find_package(OpenCV REQUIRED)
include_directories( ${OpenCV_INCLUDE_DIRS} )
# create shared libary model and link need lib
add_library(model SHARED model.cpp)
target_include_directories(model INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
set_target_properties(model PROPERTIES
PUBLIC_HEADER include/model.h
POSITION_INDEPENDENT_CODE 1
)
target_link_libraries(model PUBLIC "${TORCH_LIBRARIES}" "${OpenCV_LIBS}")
# cpoy linked dll lib to build folder
if (MSVC)
add_custom_command(TARGET model POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:model> $<TARGET_FILE_DIR:model>
COMMAND_EXPAND_LISTS
)
file(GLOB TORCH_DLLS "${TORCH_INSTALL_PREFIX}/lib/*.dll")
add_custom_command(TARGET model
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${TORCH_DLLS}
$<TARGET_FILE_DIR:model>)
endif (MSVC)
# call install scrpit in build folder with
# cmake --install ./
# install scrpit, install all lib
install(TARGETS model
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(DIRECTORY $<TARGET_FILE_DIR:model>/ DESTINATION bin
FILES_MATCHING PATTERN "*.dll"
PATTERN "CMakeFiles" EXCLUDE
)
But when I link to this shared library In GUI portion and build my project:
target_link_libraries(myexe PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
model
)
The error occurred, the cmake tell me cannot found model.lib
. This error can be fixed by add this to you cmake project:
# use make cmake in windows can create shared libary with msvc
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1)
Qt portion issues
After successfully creating the shared library and including the header file, I encountered a new issue where the compiler reported many errors in the libtorch library header files. It was quite frustrating! After searching for similar issues, I discovered that the issue was caused by QT slots. To fix this issue, I needed to include the header file as follows:
#undef slots
#include <model.h>
#define slots Q_SLOTS
Package your application and make installer
After finishing the project, the next step is to package it along with the necessary environment. My project uses Qt, libtorch, and OpenCV, and there are several ways to achieve this, my way is as follows:
First, I copied the necessary environment libraries to the build folder📂
# cpoy linked dll lib to build folder
if (MSVC)
add_custom_command(TARGET model POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:model> $<TARGET_FILE_DIR:model>
COMMAND_EXPAND_LISTS
)
file(GLOB TORCH_DLLS "${TORCH_INSTALL_PREFIX}/lib/*.dll")
add_custom_command(TARGET model
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${TORCH_DLLS}
$<TARGET_FILE_DIR:model>)
endif (MSVC)
Second, I used a CMake install script to install them.
# install scrpit, install all lib and model
install(TARGETS model
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(DIRECTORY $<TARGET_FILE_DIR:model>/ DESTINATION bin
FILES_MATCHING PATTERN "*.dll"
PATTERN "CMakeFiles" EXCLUDE
)
install(FILES "${PROJECT_SOURCE_DIR}/model/resnet_model.pt"
DESTINATION bin/model
)
install(DIRECTORY "${PROJECT_SOURCE_DIR}/dataset/"
DESTINATION bin/dataset
)
The Qt portion may be more difficult, but fortunately, Qt provides a CMake script that we can use to automatically call windeployqt.exe
when running the CMake install script.
# The command is defined in the Core component of the Qt6 package
find_package(Qt6 REQUIRED COMPONENTS Core)
qt_standard_project_setup()
qt_generate_deploy_app_script(
TARGET LCHIC
OUTPUT_SCRIPT deploy_script
NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${deploy_script})
After completing the above steps, I ran the command prompt as an administrator and changed the directory to the project build folder. Then, I ran cmake --install ./
command. By default, the program and the necessary libraries will be installed in C:/Program Files (x86)/your_project_name
.
The next step is to package an installer, and Inno Setup could be a good choice. It provides a good GUI that makes it easy to create your application installer. If you want the user to not see the publisher as ‘Unknown’ when they install the application, you also need digital signing certificates and configure the signing tool. However, it is not free now.
My project structure
D:\QT_PROJECT\LCHIC
| CMakeLists.txt
| CMakeLists.txt.user
|
+---dataset
| lungaca1007.jpeg
| lungn1007.jpeg
| lungscc1007.jpeg
|
+---model
| resnet_model.pt
|
+---src
| | CMakeLists.txt
| |
| +---calc_core
| | | CMakeLists.txt
| | | model.cpp
| | |
| | \---include
| | model.h
| |
| \---gui
| | CMakeLists.txt
| | main.cpp
| | mainwindow.cpp
| | mainwindow.h
| | mainwindow.ui
| |
| \---res
| app_win32.rc
| favicon.ico
|
\---test
CMakeLists.txt
test_core.cpp