Qt+yolov8目标识别

这是一个基于ONNX Runtime的YOLOv8目标检测项目,支持CPU和GPU加速,使用Qt框架构建图形化界面。

摄像头实时画面识别

视频文件识别,能正常识别目标:红绿灯,人,公交,巴士,摩托车 等

YOLOv8推理引擎 核心检测算法实现

ONNX Runtime 1.20.1 - 支持CPU和GPU两个版本

OpenCV 4.5.4 - 图像处理和计算机视觉

tl-expected- 错误处理库

cpp 复制代码
QT += core gui widgets
CONFIG += c++17 console

CONFIG += WIN_MSVC
#CONFIG += LINUX_X86

CONFIG += CPU
#CONFIG += GPU

##############WIN_MSVC###############
CONFIG(WIN_MSVC){
DESTDIR = ./bin_win/

#opecv
INCLUDEPATH += $$PWD\ThirdParty\opencv454\include
CONFIG(debug,debug|release): LIBS += $$PWD\ThirdParty\opencv454\x64\vc16\lib\opencv_world454d.lib
CONFIG(release,debug|release): LIBS += $$PWD\ThirdParty\opencv454\x64\vc16\lib\opencv_world454.lib

#onnxruntime
CONFIG(CPU){
DEFINES += CPU
INCLUDEPATH += $$PWD\ThirdParty\onnxruntime-win-x64-1.20.1\include
LIBS += $$PWD\ThirdParty\onnxruntime-win-x64-1.20.1\lib\onnxruntime.lib
}

CONFIG(GPU){
DEFINES += GPU
INCLUDEPATH += $$PWD\ThirdParty\onnxruntime-win-x64-gpu-1.20.1\include
LIBS += $$PWD\ThirdParty\onnxruntime-win-x64-gpu-1.20.1\lib\onnxruntime.lib
}
}

##############LINUX_X86###############
CONFIG(LINUX_X86){
DESTDIR = ./bin_linux/

#opecv
INCLUDEPATH += $$PWD\ThirdParty\opencv454\include
LIBS += -lopencv_core -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_video -lopencv_videoio -lopencv_calib3d

#onnxruntime
CONFIG(CPU){
DEFINES += CPU
INCLUDEPATH += $$PWD\ThirdParty\onnxruntime-win-x64-1.20.1\include
LIBS += -lonnxruntime
}

CONFIG(GPU){
DEFINES += GPU
INCLUDEPATH += $$PWD\ThirdParty\onnxruntime-win-x64-gpu-1.20.1\include
LIBS += -lonnxruntime
}
}
######################################

SOURCES += \
    inference.cpp \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    inference.h \
    mainwindow.h \
    tl-expected.hpp

FORMS += \
    mainwindow.ui

MOC_DIR = tmp/moc
RCC_DIR = tmp/rcc
UI_DIR = tmp/ui
OBJECTS_DIR = tmp/obj
cpp 复制代码
#include "inference.h"
#include <filesystem>
#include <fstream>
#include <codecvt> // macos必须,否则提示String2WString函数报错

namespace fs = std::filesystem;

std::vector<std::string> YoloV8::st_classes_;


tl::expected<bool, std::string> YoloV8::create_session(const std::string &model_path)
{
    // 检查模型文件是否存在
    if (!fs::exists(model_path))  return tl::unexpected("模型文件不存在!");

    // 创建Ort环境
    env_ = Ort::Env(ORT_LOGGING_LEVEL_WARNING, "Yolo");
    Ort::SessionOptions session_options;

    // 添加CUDA执行提供程序
#ifdef GPU
    OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0);
#endif

    // 设置图优化级别
    session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
    // 设置线程数
    session_options.SetIntraOpNumThreads(5);
    // 设置日志级别
    session_options.SetLogSeverityLevel(ORT_LOGGING_LEVEL_WARNING);

    // 创建会话
    std::wstring model_path_w = String2WString(model_path);
    session_ = Ort::Session(env_, model_path_w.c_str(), session_options);

    Ort::AllocatorWithDefaultOptions allocator;
    // 获取输入节点名称
    size_t input_count = session_.GetInputCount();
    for (size_t i = 0; i < input_count; i++)
        input_node_names_.emplace_back(session_.GetInputNameAllocated(i, allocator));

    // 获取输出节点名称
    size_t output_count = session_.GetOutputCount();
    for (size_t i = 0; i < output_count; i++)
        output_node_names_.emplace_back(session_.GetOutputNameAllocated(i, allocator));

    // 创建运行选项
    options_ = Ort::RunOptions{nullptr};

    return true;
}

// 对输入的图像进行预处理
tl::expected<cv::Mat, std::string> YoloV8::pre_process(cv::Mat &img, cv::Size img_size)
{
    // 如果输入的图像为空,则返回一个错误信息
    if (img.empty())
    {
        return tl::unexpected("图片为空");
    }
    // 获取输入图像的宽度和高度的最大值
    int max_side = std::max(img.cols, img.rows);
    // 计算输入图像的缩放比例
    resize_scales_ = max_side / static_cast<float>(img_size.width);
    // 创建一个与输入图像大小相同的空白图像,填充值为114, 114, 114
    cv::Mat tmp(max_side, max_side, CV_8UC3, cv::Scalar(114, 114, 114));
    // 将输入图像复制到空白图像中
    img.copyTo(tmp(cv::Rect(0, 0, img.cols, img.rows)));
    // 将空白图像转换为深度学习模型所需的输入格式
    cv::Mat res = cv::dnn::blobFromImage(tmp, 1.0 / 255.0, img_size, cv::Scalar(0, 0, 0), true, false, CV_32F);
    // 返回转换后的图像
    return res;
}

std::vector<DlResult> YoloV8::post_process(std::vector<Ort::Value> &outputs, float conf_threshold, float iou_threshold)
{
    // 定义一个存储结果的向量
    std::vector<DlResult> vec_results;
    // 获取输出的类型信息
    Ort::TypeInfo type_info = outputs.front().GetTypeInfo();
    // 获取输出的张量类型和形状信息
    auto tensor_info = type_info.GetTensorTypeAndShapeInfo();
    // 获取输出的形状
    std::vector<int64_t> output_node_dims = tensor_info.GetShape();
    // 获取输出的数据
    auto output = outputs.front().GetTensorMutableData<float>(); // 8400

    // 输出的形状是 [1, 84, 8400]
    int stride_num = output_node_dims[2];        // 8400
    int signal_result_num = output_node_dims[1]; // 84

    // 定义存储类别、置信度和框的向量
    std::vector<int> class_ids;
    std::vector<float> confidences;
    std::vector<cv::Rect> boxes;

    // 为存储类别、置信度和框的向量预留空间
    class_ids.reserve(stride_num / 8);
    confidences.reserve(stride_num / 8);
    boxes.reserve(stride_num / 8);

    // 将输出的数据转换为矩阵
    cv::Mat raw_data = cv::Mat(signal_result_num, stride_num, CV_32F, output).t();

    // 获取矩阵的指针
    float *data = raw_data.ptr<float>(0);

    // 遍历每个输出
    for (int i = 0; i < stride_num; ++i)
    {

        // 找到置信度最高的类别
        auto max_it = std::max_element(data + 4, data + 80); // std::max_element返回指向最大元素的迭代器
        float max_class_socre = *max_it;
        int max_idx = std::distance(data + 4, max_it);

        // 如果置信度大于阈值,则存储类别、置信度和框
        if (max_class_socre > conf_threshold)
        {
            confidences.push_back(max_class_socre);
            class_ids.push_back(max_idx);
            float x = data[0];
            float y = data[1];
            float w = data[2];
            float h = data[3];

            // 计算框的位置和大小
            int left = int((x - 0.5 * w) * resize_scales_);
            int top = int((y - 0.5 * h) * resize_scales_);

            int width = int(w * resize_scales_);
            int height = int(h * resize_scales_);

            // 存储框
            boxes.emplace_back(left, top, width, height);
        }
        // 移动到下一个输出
        data += signal_result_num;
    }
    // 进行非极大值抑制
    std::vector<int> nms_result;
    cv::dnn::NMSBoxes(boxes, confidences, conf_threshold, iou_threshold, nms_result);
    // 将结果存储到向量中
    for (int i = 0; i < nms_result.size(); ++i)
    {
        int idx = nms_result[i];
        DlResult result;
        result.class_id = class_ids[idx];
        result.confidence = confidences[idx];
        result.box = boxes[idx];
        vec_results.push_back(result);
    }
    // 返回结果
    return vec_results;
}

// 定义一个函数,用于将Ort::AllocatedStringPtr类型的向量转换为const char*类型的向量
std::vector<const char *> YoloV8::get_name_data(std::vector<Ort::AllocatedStringPtr> &names)
{
    // 定义一个const char*类型的向量,用于存储转换后的数据
    std::vector<const char *> res;
    // 遍历names向量中的每一个元素
    for (const auto &name : names)
    {
        // 将每一个元素转换为const char*类型,并添加到res向量中
        res.push_back(name.get());
    }
    // 返回转换后的向量
    return res;
}

tl::expected<std::vector<DlResult>, std::string> YoloV8::run_session(cv::Mat &img, cv::Size input_size, float confidence_threshold, float iou_threshold)
{
    // 预处理图像
    auto ex_img = pre_process(img, input_size);
    // 如果预处理失败,返回错误信息
    if (!ex_img)
    {
        return tl::unexpected(ex_img.error());
    }

    // 定义输入张量的形状
    std::vector<int64_t> input_shape = {1, 3, input_size.height, input_size.width};

    // 创建输入张量
    Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
        Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU), ex_img.value().ptr<float>(0), 3 * input_size.height * input_size.width, input_shape.data(), input_shape.size());

    // 运行会话
    auto output_tensor = session_.Run(options_, get_name_data(input_node_names_).data(), &input_tensor, input_node_names_.size(), get_name_data(output_node_names_).data(),
                                      output_node_names_.size());

    // 后处理输出张量
    auto outputs = post_process(output_tensor, confidence_threshold, iou_threshold);

    // 返回后处理结果
    return outputs;
}


void YoloV8::draw_boxes(cv::Mat &img, std::vector<DlResult> &results)
{
    if (st_classes_.size() == 0)
        return;
    for (auto &re : results)
    {
        cv::RNG rng(cv::getTickCount());
        cv::Scalar color(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));

        cv::rectangle(img, re.box, color, 3);

        float confidence = floor(100 * re.confidence) / 100;
        std::string label = st_classes_[re.class_id] + " " +
                            std::to_string(confidence).substr(0, std::to_string(confidence).size() - 4);

        cv::rectangle(
            img,
            cv::Point(re.box.x, re.box.y - 25),
            cv::Point(re.box.x + label.length() * 15, re.box.y),
            color,
            cv::FILLED);

        cv::putText(
            img,
            label,
            cv::Point(re.box.x, re.box.y - 5),
            cv::FONT_HERSHEY_SIMPLEX,
            0.75,
            cv::Scalar(0, 0, 0),
            2);
    }
}

主要功能特性

  • **CPU推理**: 使用标准ONNX Runtime

  • **GPU推理**: 支持CUDA加速

  1. 完整的检测流水线

**预处理** (`pre_process`)

  • 图像尺寸调整到640x640

  • Letterbox填充保持宽高比

  • 像素值归一化

**推理** (`run_session`)

  • ONNX模型前向推理

  • 批量处理支持

**后处理** (`post_process`)

  • 非极大值抑制(NMS)

  • 置信度过滤

  • 边界框坐标转换

  1. 可视化功能
  • 检测框绘制

  • 类别标签显示

  • 置信度分数展示

内存管理

  • 使用ONNX Runtime的内存分配器

  • 智能指针管理资源生命周期

  • 避免内存泄漏

性能优化

  • 支持GPU加速推理

  • 批量处理能力

  • 高效的图像预处理pipeline

适用场景

这个项目特别适合:

  • **实时目标检测应用**

  • **工业质检系统**

  • **监控分析系统**

  • **教学和研究项目**

项目提供了完整的从模型加载到结果可视化的端到端解决方案,是学习和部署YOLOv8模型的优秀起点。