C++ 桌面应用程序
项目概述

主要功能

文件结构
头文件
daoai_world_example.h: 定义了主窗口类daoai_world_example,包含UI组件和槽函数的声明。
inference.h: 声明了图像和模型处理相关的函数。
ui_daoai_world_example.h: 声明了Ui_daoai_world_exampleClass类,构建UI主窗口和控件。
源文件
daoai_world_example.cpp: 实现了主窗口类的构造函数、析构函数和槽函数。
mainwindow.cpp: 程序入口,创建并显示主窗口。
inference.cpp: 实现了图像和模型处理相关的函数,包括图像加载、推理、结果绘制和保存。
依赖项
使用指南
环境设置


编译和运行
- 打开项目属性,配置Debugging,“Inherit from parent or project defaults“ 取消勾选,Environment 目标路径包含:$(SolutionDir)\bin;$(SolutionDir)\3rdparty;C:\OpenCV\opencv\build\x64\vc16\bin;C:\Qt\6.6.1\msvc2019_64\bin。

- 配置VC++ Directories,Include Directories 目标路径包含:C:\OpenCV\opencv\build\include\opencv2;C:\OpenCV\opencv\build\include。Library Directories 目标路径包含:C:\OpenCV\opencv\build\x64\vc16lib。

- 配置C/C++ General,Additional Include Directories 目标路径包含:$(SolutionDir)Jsonincludeinclude;$(SolutionDir)include。

- 配置Linker General,Additional Library Directories 目标路径包含:$(SolutionDir)bin。

- 配置Linker General,Additional Dependencies 目标路径包含:daoai_dl_sdk.lib;opencv_world4100.lib。


功能说明
主窗口
- 文本输入框: 用于输入图像和模型文件路径。
按钮:
pushButton: 触发推理。
pushButton_load: 加载图像文件。
- pushButton_load_2: 加载模型文件。
标签: 显示加载的图像和推理结果。
图像加载和显示
- on_pushButton_load_clicked: 打开文件对话框选择图像文件,并在label_3上显示。
on_pushButton_load_model_clicked: 打开文件对话框选择模型文件,并在文本框中显示路径。
推理
on_pushButton_clicked: 执行推理流程,调用executeInference函数进行模型推理,并显示结果图像。
日志记录
appendLog: 将日志信息添加到日志窗口。
源码解析
inference.cpp/主窗口类
文件包含的库
该文件包含了标准C++库、OpenCV库、JSON处理库(nlohmann/json)和深度学习SDK(dlsdk)的头文件。
1#include <windows.h>
2#include <iostream>
3#include <thread>
4#include <future>
5#include <fstream>
6#include <vector>
7#include <string>
8#include <opencv2/opencv.hpp>
9#include <nlohmann/json.hpp>
10#include <dlsdk/model.h>
11#include "daoai_world_example.h"
命名空间
使用std命名空间和cv命名空间,以便简化代码中的命名。
1using namespace std;
2using namespace cv;
JSON别名
为nlohmann的JSON库起别名为json,方便在代码中使用。
1// json another name
2using json = nlohmann::json;
createDirectory 函数
此函数用于创建指定路径的目录。它接受两个参数:目录路径和daoai_world_example实例的指针。 若目录已存在或创建成功,会在控制台和UI日志中记录相关信息。
1// Create "./Data" folder
2void createDirectory(const std::string& directoryPath, daoai_world_example* example) {
3 if (!CreateDirectoryA(directoryPath.c_str(), NULL)) {
4 if (GetLastError() == ERROR_ALREADY_EXISTS) {
5 std::cout << "Directory already exists: " << "\"" + directoryPath + "\"" << std::endl;
6 example->appendLog("Directory already exists: " + QString::fromStdString("\"" + directoryPath + "\""));
7 }
8 else {
9 std::cerr << "Failed to create directory: " << GetLastError() << std::endl;
10 example->appendLog("Failed to create directory: " + QString::number(GetLastError()));
11 }
12 }
13 else {
14 std::cout << "Directory created successfully: " << directoryPath << std::endl;
15 example->appendLog("Directory created successfully: " + QString::fromStdString(directoryPath));
16 }
17}
drawShapes 函数
此函数用于在图像上绘制多边形和关键点。它从JSON数据中读取形状信息,并根据形状的标签选择不同颜色进行绘制。
1// Draw polygon and keypoint
2void drawShapes(cv::Mat& image, const json& jsonData) {
3 for (const auto& shape : jsonData["shapes"]) {
4 if (shape["shape_type"] == "polygon") {
5 // Processing polygon
6 std::vector<cv::Point> points;
7 for (const auto& point : shape["points"]) {
8 points.emplace_back(cv::Point(static_cast<int>(point[0]), static_cast<int>(point[1])));
9 }
10
11 // Select color
12 cv::Scalar color(255, 255, 255);
13 if (shape["label"].is_string() && !shape["label"].empty()) {
14 if (shape["label"] == "fan") {
15 color = cv::Scalar(0, 0, 255); // Red
16 }
17 else if (shape["label"] == "zheng") {
18 color = cv::Scalar(0, 255, 0); // Green
19 }
20 else {
21 color = cv::Scalar(255, 0, 0); // Blue
22 }
23 }
24
25 // Creates a group of points to draw polygons
26 std::vector<std::vector<cv::Point>> pts = { points };
27
28 // Draw polygon
29 cv::polylines(image, pts, true, color, 2);
30 }
31 else if (shape["shape_type"] == "point") {
32 // Processing keypoint
33 cv::Point center(static_cast<int>(shape["points"][0][0]), static_cast<int>(shape["points"][0][1]));
34
35 // Select color
36 cv::Scalar color(255, 255, 255);
37 if (shape["label"].is_string() && !shape["label"].empty()) {
38 char firstChar = shape["label"].get<std::string>().at(0);
39 if (firstChar == 'z' || firstChar == 'Z') {
40 color = cv::Scalar(0, 255, 0); // Green
41 }
42 else if (firstChar == 'f' || firstChar == 'F') {
43 color = cv::Scalar(0, 0, 255); // Red
44 }
45 else {
46 color = cv::Scalar(255, 0, 0); // Blue
47 }
48 }
49
50 // Draw keypoint
51 cv::circle(image, center, 5, color, cv::FILLED);
52 }
53 }
54}
drawImage 函数
此函数负责读取JSON文件和图像文件,调用drawShapes函数在图像上绘制形状,然后将修改后的图像保存到指定路径。
1int drawImage(const std::string& jsonPath, const std::string& imagePath, const std::string& savePath, daoai_world_example* example) {
2 // Open and parse the JSON file
3 std::ifstream inputFile(jsonPath);
4 if (!inputFile.is_open()) {
5 std::cerr << "Cannot open " << jsonPath << std::endl;
6 example->appendLog("Cannot open " + QString::fromStdString(jsonPath));
7 return 1;
8 }
9
10 json jsonData;
11 inputFile >> jsonData;
12
13 // Read image file
14 cv::Mat image = cv::imread(imagePath);
15 if (image.empty()) {
16 std::cerr << "Cannot read " << imagePath << std::endl;
17 example->appendLog("Cannot read " + QString::fromStdString(imagePath));
18 return 1;
19 }
20
21 // Draw shape
22 drawShapes(image, jsonData);
23
24 // Display image
25 // cv::imshow("Image with Shapes and KeyPoints", image);
26 // cv::waitKey(0);
27
28 // Save image
29 if (cv::imwrite(savePath, image)) {
30 std::cout << "Image has been saved as: " << "\"" + savePath + "\"" << std::endl;
31 example->appendLog("Image has been saved as: " + QString::fromStdString("\"" + savePath + "\""));
32 }
33 else {
34 std::cerr << "Cannot save image " << savePath << std::endl;
35 example->appendLog("Cannot save image " + QString::fromStdString(savePath));
36 }
37
38 return 0;
39}
readPngImageFile 函数
此函数用于读取PNG图像文件并将其转换为uint8_t类型的向量。
1/*
2 * @brief Reads PNG image file into a vector of uint8_t.
3 *
4 * @param filePath The path to the PNG file.
5 * @return std::vector<uint8_t> A vector containing the uint8 data from the image file.
6 * Returns an empty vector if the file could not be opened.
7 */
8std::vector<uint8_t> readPngImageFile(const std::string& filePath) {
9 cv::Mat image = cv::imread(filePath, cv::IMREAD_UNCHANGED);
10
11 /*
12 if (image.empty()) {
13 std::cerr << "Could not read image file " << filePath << std::endl;
14 return {};
15 }
16 */
17
18 if (image.empty()) {
19 throw std::runtime_error("Could not read image file " + filePath);
20 return {};
21 }
22
23 std::vector<uint8_t> imageBuffer(image.data, image.data + image.total() * image.elemSize());
24 return imageBuffer;
25}
readImageBinaryFile 函数
此函数用于读取二进制图像文件并将其转换为uint8_t类型的向量。
1/*
2 * @brief Reads binary image file into a vector of uint8_t.
3 *
4 * @param filePath The path to the binary file.
5 * @return std::vector<uint8_t> A vector containing the binary data from the image file.
6 * Returns an empty vector if the file could not be opened.
7 */
8std::vector<uint8_t> readImageBinaryFile(const std::string& filePath) {
9 std::ifstream inputFile(filePath, std::ios::binary);
10 if (!inputFile) {
11 std::cerr << "Cannot open " << filePath << "\n";
12 return {};
13 }
14
15 std::vector<uint8_t> buffer((std::istreambuf_iterator<char>(inputFile)), std::istreambuf_iterator<char>());
16 inputFile.close();
17
18 return buffer;
19}
inference 函数
此函数负责初始化深度学习模型,读取图像数据,进行推理并将结果写入JSON文件。
1int inference(const std::string& modelPath, const std::string& imagePath, const std::string& jsonPath, daoai_world_example* example) {
2 try
3 {
4 DaoAI::DeepLearning::initialize();
5 example->appendLog("DeepLearning: Initialize succeed!");
6
7 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
8 // This part of the code demonstrating on an image from a binary file, you can replace the image_buffer by your own image data (i.e.: data buffer from opencv)
9 const int image_height = 1200;
10 const int image_width = 1920;
11
12 std::vector<uint8_t> image_buffer = readPngImageFile(imagePath);
13 if (image_buffer.size() == 0)
14 {
15 std::cerr << "Image buffer is not read properly, returns an empty vector" << std::endl;
16 example->appendLog("Image buffer is not read properly, returns an empty vector");
17 return 1;
18 }
19 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
20
21 // init model
22 DaoAI::DeepLearning::Model model;
23 example->appendLog("Initialized deeplearning model");
24
25 // load model
26 model.load(modelPath, DaoAI::DeepLearning::Device_Type::GPU, -1);
27 example->appendLog("Loaded deeplearning model");
28
29 DaoAI::DeepLearning::Image daoai_image(image_height, image_width, DaoAI::DeepLearning::Image::Type::BGR, &image_buffer[0]);
30
31 // get inference
32 auto prediction = model.inferenceJSON(daoai_image);
33 example->appendLog("Inference completed");
34
35 // write to json file
36 std::ofstream fout(jsonPath);
37 fout << prediction << std::endl;
38 fout.close();
39
40 // unload model
41 model.unload();
42 }
43 catch (const std::exception& e)
44 {
45 std::cerr << e.what() << std::endl;
46 return 1;
47 }
48 return 0;
49}
executeInference 函数
此函数单线程执行推理任务。它创建必要的目录,调用inference函数进行推理,并在图像上绘制结果。
1// Single-threaded, may cause the cursor to become unresponsive during inference
2std::string executeInference(const std::string& dataPath, const std::string& modelPath, daoai_world_example* example) {
3
4 example->appendLog("Satrt execute inference");
5
6 // get root path to the model and image
7 std::string data_path = dataPath;
8 std::string model_zip_path = modelPath;
9 std::string root = "./Data";
10 std::string json_path = root + "/daoai_1.json";
11 std::string image_path = root + "/daoai_1.jpg";
12
13 // The 'example' is a valid pointer to daoai_world_example instance
14 //daoai_world_example* example = new daoai_world_example(); // instantiation
15 createDirectory(root, example);
16
17 int result = inference(model_zip_path, data_path, json_path, example); // get the return value of inference function
18
19 if (!result) {
20 drawImage(json_path, data_path, image_path, example);
21 }
22 else {
23 return "1";
24 }
25
26 //delete example; // Delete example
27 return image_path;
28}
executeInference_multi 函数
此函数多线程执行推理任务。它通过线程异步执行目录创建和推理过程,并在推理完成后在图像上绘制结果。
1// Multi-threaded, the cursor can do other things during inference
2std::string executeInference_multi(const std::string& dataPath, const std::string& modelPath, daoai_world_example* example) {
3 // create a promise object
4 std::promise<int> promise;
5
6 // getting the future object
7 std::future<int> future = promise.get_future();
8
9 example->appendLog("Satrt execute inference");
10
11 // get root path to the model and image
12 std::string data_path = dataPath;
13 std::string model_zip_path = modelPath;
14 std::string root = "./Data";
15 std::string json_path = root + "/daoai_1.json";
16 std::string image_path = root + "/daoai_1.jpg";
17
18 // The 'example' is a valid pointer to daoai_world_example instance
19 //daoai_world_example* example = new daoai_world_example(); // instantiation
20 std::thread t_createDirectory(createDirectory, root, example);
21 t_createDirectory.join();
22
23 // Create a thread that passes three strings and the daoai_world_example instance
24 std::thread t_inference([&promise, model_zip_path, data_path, json_path, example]() {
25 int result = inference(model_zip_path, data_path, json_path, example);
26 promise.set_value(result); // Set the return value at the end of the thread
27 });
28 t_inference.join();
29 int result = future.get(); // get the return value of a thread function
30
31 if (!result) {
32 std::thread t_drawImage(drawImage, json_path, data_path, image_path, example);
33 t_drawImage.join();
34 }
35 else {
36 return "1";
37 }
38
39 //delete example; // Delete example
40 return image_path;
41}
daoai_world_example.cpp/推理相关函数
文件包含的库
此文件包含了项目的头文件、Qt库文件和推理功能头文件。
1#include "daoai_world_example.h"
2#include "ui_daoai_world_example.h"
3#include <inference.h>
4#include <QFileDialog>
5#include <QPixmap>
6#include <QDebug>
7#include <QEventLoop>
8#include <QTimer>
9#include <string>
构造函数与析构函数
构造函数初始化UI组件,并设置按钮点击事件的连接。析构函数用于清理资源。
1daoai_world_example::daoai_world_example(QWidget* parent)
2 : QMainWindow(parent), scaleFactor(1.0)
3{
4 ui.setupUi(this);
5
6 // Set the window icon
7 setWindowIcon(QIcon("icon/01.ico"));
8
9 disconnect(ui.pushButton, &QPushButton::clicked, this, &daoai_world_example::on_pushButton_clicked);
10 disconnect(ui.pushButton_load, &QPushButton::clicked, this, &daoai_world_example::on_pushButton_load_clicked);
11 disconnect(ui.pushButton_load_2, &QPushButton::clicked, this, &daoai_world_example::on_pushButton_load_model_clicked);
12 connect(ui.pushButton, &QPushButton::clicked, this, &daoai_world_example::on_pushButton_clicked);
13 connect(ui.pushButton_load, &QPushButton::clicked, this, &daoai_world_example::on_pushButton_load_clicked);
14 connect(ui.pushButton_load_2, &QPushButton::clicked, this, &daoai_world_example::on_pushButton_load_model_clicked);
15
16 // initialize the mouse press state
17 mousePressed = false;
18}
19
20daoai_world_example::~daoai_world_example() {
21}
appendLog 函数
此函数将日志消息追加到UI的日志文本框中。
1void daoai_world_example::appendLog(const QString& logMessage) {
2 ui.textEditLog->append(logMessage);
3}
on_pushButton_clicked 函数
此函数处理推理按钮的点击事件,调用executeInference函数执行推理任务,并显示推理结果图像。
1void daoai_world_example::on_pushButton_clicked() {
2
3 qDebug() << "PushButton Clicked";
4 QString fileName = QString::fromStdString(executeInference(ui.lineEdit->text().toStdString(), ui.lineEdit_2->text().toStdString(), this));
5 if (!fileName.isEmpty()) {
6
7 pixmap.load(fileName);
8 if (!pixmap.isNull()) { // check that the image loaded successfully
9 scaleFactor = 1.0; // reset the scaling factor
10 ui.label->setPixmap(pixmap);
11 //ui.label->resize(pixmap.size()); // resize the QLabel to the image size
12 ui.label->setScaledContents(true); // adapt the image to the size of the QLabel
13 ui.label->update(); // Force repaint
14 QEventLoop loop;
15 QTimer::singleShot(0, &loop, &QEventLoop::quit);
16 loop.exec();
17 }
18 else {
19 appendLog("Inference result failed to load");
20 }
21
22 }
23 else {
24 appendLog("Inference failed");
25 }
26}
on_pushButton_load_clicked 函数
此函数处理加载图像按钮的点击事件,打开文件对话框选择图像文件,并显示选择的图像。
1void daoai_world_example::on_pushButton_load_clicked() {
2
3 qDebug() << "PushButton Load Clicked ";
4 QString fileName = QFileDialog::getOpenFileName(this, "Open Image File", "", "Images (*.png *.xpm *.jpg)");
5 if (!fileName.isEmpty()) {
6 ui.lineEdit->setText(fileName); // Displays the file path in QLineEdit
7
8 pixmap.load(fileName);
9 if (!pixmap.isNull()) { // check that the image loaded successfully
10 scaleFactor = 1.0; // reset the scaling factor
11 ui.label_3->setPixmap(pixmap);
12 //ui.label->resize(pixmap.size()); // resize the QLabel to the image size
13 ui.label_3->setScaledContents(true); // adapt the image to the size of the QLabel
14 ui.label_3->update(); // Force repaint
15 QEventLoop loop;
16 QTimer::singleShot(0, &loop, &QEventLoop::quit);
17 loop.exec();
18 appendLog("Image loaded successfully");
19 }
20 else {
21 appendLog("Image loaded fail");
22 }
23
24 }
25 else {
26 // The user is prompted that the image path is invalid
27 ui.lineEdit->setText("Invalid image path");
28 }
29
30}
on_pushButton_load_model_clicked 函数
此函数处理加载图像按钮的点击事件,打开文件对话框选择图像文件,并显示选择的图像。
1void daoai_world_example::on_pushButton_load_model_clicked() {
2
3 qDebug() << "PushButton Load Model Clicked ";
4 QString fileName = QFileDialog::getOpenFileName(this, "Open Model File", "", "Zip Files (*.zip);;Model Files (*.pt)");
5 if (!fileName.isEmpty()) {
6 ui.lineEdit_2->setText(fileName); // Displays the file path in QLineEdit
7 appendLog("Model loaded successfully");
8
9 }
10 else {
11 // The user is prompted that the image path is invalid
12 ui.lineEdit_2->setText("Invalid image path");
13 }
14
15}
鼠标事件处理函数
这四个函数处理鼠标按下、移动、释放和滚轮事件,实现图像的拖动和缩放功能。
1void daoai_world_example::mousePressEvent(QMouseEvent* event) {
2 if (event->button() == Qt::LeftButton) {
3 lastPos = event->position().toPoint(); // Get the position where the mouse is pressed
4 mousePressed = true;
5 }
6}
7
8void daoai_world_example::mouseMoveEvent(QMouseEvent* event) {
9 if (mousePressed) {
10 QPoint currentPos = event->position().toPoint(); // Get the current position of the mouse
11 int dx = currentPos.x() - lastPos.x();
12 int dy = currentPos.y() - lastPos.y();
13 lastPos = currentPos;
14
15 // Moving image
16 QPoint new_pos = ui.label->pos() + QPoint(dx, dy);
17 ui.label->move(new_pos);
18 }
19}
20
21void daoai_world_example::mouseReleaseEvent(QMouseEvent* event) {
22 if (event->button() == Qt::LeftButton) {
23 mousePressed = false;
24 }
25}
26
27void daoai_world_example::wheelEvent(QWheelEvent* event) {
28 // Check if the cursor is inside the QLabel and add a buffer
29 const int buffer = -100; // Adjust this value to increase the buffer size
30 QRect labelGeometry = ui.label->geometry().adjusted(-buffer, -buffer, buffer, buffer);
31
32 QPoint cursorPos = mapFromGlobal(QCursor::pos());
33 if (!labelGeometry.contains(cursorPos)) {
34 return; // If the cursor is not inside the QLabel or within the buffer, it returns and the wheel event is not handled
35 }
36
37 // Get the rolling Angle of the roller in 1/8 degree
38 int delta = event->angleDelta().y();
39
40 // zoom factor
41 qreal scaleFactorDelta = (delta > 0) ? 1.15 : 1 / 1.15;
42
43 // Update the scaling factor
44 qreal newScaleFactor = scaleFactor * scaleFactorDelta;
45 newScaleFactor = qMax(0.1, qMin(newScaleFactor, 4.0));
46
47 // Calculate the change in the scaling ratio
48 qreal factorChange = newScaleFactor / scaleFactor;
49
50 // Update the scaling factor
51 scaleFactor = newScaleFactor;
52
53 // zoom image
54 QSize newSize = pixmap.size() * scaleFactor;
55 QPixmap scaledPixmap = pixmap.scaled(newSize, Qt::KeepAspectRatio);
56 ui.label->setPixmap(scaledPixmap);
57 ui.label->resize(scaledPixmap.size());
58
59 // Resize the QLabel to the window size to fit the scaled image
60 ui.label->adjustSize();
61
62 // Converts the mouse position from window coordinates to image coordinates
63 QPoint imgPos = cursorPos - ui.label->pos();
64
65 // The new image position is computed to keep the cursor position constant in the image part
66 QPointF newLabelPos = cursorPos - imgPos * factorChange;
67 ui.label->move(newLabelPos.toPoint());
68}
示例运行
日志和错误处理
程序会在日志窗口中显示操作日志和错误信息,便于用户跟踪和调试。如遇到问题,可根据日志信息排查原因。
许可证
此项目遵循MIT许可证。详见LICENSE文件。
此项目所引用的DaoAI深度学习SDK需要授权,请联系我们获取授权文件。