VS2026+QT6.9+ONNX+OPENCV+YOLO11(目标检测)(详细注释)(附测试模型和图像)
目录
一、前言
二、代码详解
三、完整项目下载
一、前言
(1)前言
本次项目使用YOLO11对目标进行检测,具体实现了模型加载、图像加载、ONNX Runtime环境初始化、图像预处理和后处理、检测框的绘制、图像显示等功能。代码注释详细。
模型使用python中训练成pt,然后转为onnx模型,标注工具使用ISAT进行标注。本次项目还提供了测试模型和测试图像,请仅作学习使用。
模型只有一个功能,检测电容负极标识和非负极标识,如果使用自己的模型,替换即可。
模型的具体参数可以使用:netron.app 进行查看
本次使用的 1106_YOLO11_ONNX_FP32.onnx 模型具体信息如下:
输入大小:640x640
类别:{0: 'front', 1: 'reverse'}
参数:batch': 1, 'half': False, 'dynamic': False, 'simplify': True, 'opset': 12, 'nms': True
输出张量:[1 ,3,6]
如果使用自己的模型:比如你的输出向量为[1 ,300,6],请在代码中修改以适配。

模型和测试图像在打包的根目录中查看

(2)项目准备工作
下载并安装opencv 4.12.0:opencv官网

下载onnxruntime-win-x64-1.16.0库:onnxruntime-win-x64-1.16.0 (CPU版本兼容性挺好,最新的1.23.1应该也是可以用的,如果有库了可以先试试不同版本的)

(2)项目中添加头文件和静态库lib和动态库dll
包含目录中添加:
C:\opencv\build\include\opencv2
C:\opencv\build\include
C:\Users\Administrator\Desktop\onnxruntime-win-x64-1.16.0\include

库目录中添加:
C:\opencv\build\x64\vc16\lib
C:\Users\Administrator\Desktop\onnxruntime-win-x64-1.16.0\lib

链接器中输入:
onnxruntime.lib
onnxruntime_providers_shared.lib
opencv_world4120d.lib

项目的x64->Debug中要添加这两个dll动态库,不然会报0xc000007b应用程序无法启动的错误


(3)YOLO11目标检测运行效果


二、代码详解
QtWidgetsApplication10.h
#pragma once#include <QtWidgets/QMainWindow>
#include "ui_QtWidgetsApplication10.h"
#include <opencv2/opencv.hpp> // OPENCV 头文件(版本号:4.12.0)
#include <onnxruntime_cxx_api.h> // ONNX Runtime 头文件(CPU版本)(版本号:onnxruntime-win-x64-1.16.0)
#include <vector> // C++11 容器库
#include <string> // C++11 字符串库
#include <memory> // C++11 智能指针
#include <chrono> // C++11 高精度的时间库
#include <algorithm> // C++11 操作容器中的元素// 检测结果结构体
struct Detection {cv::Rect box;float conf;int classId;
};class QtWidgetsApplication10 : public QMainWindow
{Q_OBJECTpublic:QtWidgetsApplication10(QWidget* parent = nullptr);~QtWidgetsApplication10();private:// ONNX Runtime 相关成员变量std::unique_ptr<Ort::Env> env;std::unique_ptr<Ort::Session> session;Ort::SessionOptions sessionOptions;Ort::RunOptions runOptions;// 模型输入输出信息std::vector<std::string> inputNamesStr;std::vector<std::string> outputNamesStr;std::vector<const char*> inputNames;std::vector<const char*> outputNames;std::vector<std::vector<int64_t>> inputNodeDims;std::vector<std::vector<int64_t>> outputNodeDims;// 模型配置std::string modelPath = "1106_YOLO11_ONNX_FP32.onnx";std::string imagePath = "TEST_2.png";int inpHeight = 640;int inpWidth = 640;// 预定义类别颜色或从配置文件中获取std::vector<cv::Scalar> colors = {cv::Scalar(0, 255, 0), // 绿色cv::Scalar(0, 0, 255), // 红色};// 图像和检测结果cv::Mat currentImage;cv::Mat displayImage;std::vector<Detection> currentDetections;// 模型加载bool loadYOLOv11Model(const std::string& model_path);void initializeONNXRuntime(); // 初始化ONNX Runtime环境void printModelInfo(); // 打印模型信息// 图像加载bool loadImage(const std::string& imagePath);// 图像预处理cv::Mat preprocessImage(const cv::Mat& image);// 图像后处理std::vector<Detection> postprocessResults(const std::vector<Ort::Value>& outputTensors,const cv::Size& originalSize,float confThreshold = 0.25f,float iouThreshold = 0.45f);// 目标检测std::vector<Detection> detectObjects(const cv::Mat& image);// 绘制检测结果void drawDetections(cv::Mat& image, const std::vector<Detection>& detections);void updateDisplay(); // 更新显示在label中void autoDetect(); // 检测函数private:Ui::QtWidgetsApplication10Class ui;
};
QtWidgetsApplication10.cpp
#include "QtWidgetsApplication10.h"
#include <QMessageBox>
#include <QDebug>
#include <QFile>QtWidgetsApplication10::QtWidgetsApplication10(QWidget* parent): QMainWindow(parent)
{ui.setupUi(this);// 初始化ONNX Runtime并加载模型try {// 修改为你的YOLO模型绝对路径if (loadYOLOv11Model(modelPath)) {// 修改为你的测试图像绝对路径if (loadImage(imagePath)) {// 执行目标检测autoDetect(); }}else {QMessageBox::critical(this, "错误", "YOLOv11模型加载失败!");}}catch (const std::exception& e) {QMessageBox::critical(this, "异常", QString("模型加载异常: %1").arg(e.what()));}
}QtWidgetsApplication10::~QtWidgetsApplication10()
{session.reset();env.reset();
}// 自动检测函数
void QtWidgetsApplication10::autoDetect()
{if (currentImage.empty() || !session) {return;}// 执行目标检测auto start = std::chrono::high_resolution_clock::now(); // 开始计时currentDetections = detectObjects(currentImage); // 执行目标检测auto end = std::chrono::high_resolution_clock::now(); // 结束计时auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); // 计算耗时// 绘制检测结果displayImage = currentImage.clone();drawDetections(displayImage, currentDetections);updateDisplay();// 更新状态qDebug() << "检测完成,发现" << currentDetections.size() << "个目标,耗时:" << duration.count() << "ms";
}// 加载图像
bool QtWidgetsApplication10::loadImage(const std::string& imagePath)
{currentImage = cv::imread(imagePath);if (currentImage.empty()) {qDebug() << "无法加载图像文件:" << imagePath.c_str();return false;}currentDetections.clear();// 创建显示副本displayImage = currentImage.clone();updateDisplay();qDebug() << "图像加载成功,尺寸:" << currentImage.cols << "x" << currentImage.rows;return true;
}// 目标检测
std::vector<Detection> QtWidgetsApplication10::detectObjects(const cv::Mat& image)
{if (!session) {qDebug() << "模型未加载!";return {};}try {// 保存原始图像尺寸cv::Size originalSize = image.size();// 预处理图像cv::Mat processedImage = preprocessImage(image);// 创建输入Tensorsize_t inputTensorSize = processedImage.total() * processedImage.channels();std::vector<float> inputTensorValues(inputTensorSize);// 将图像数据复制到inputTensorValues中(HWC -> CHW)std::vector<cv::Mat> channels(3);cv::split(processedImage, channels);size_t channelSize = processedImage.rows * processedImage.cols;for (int i = 0; i < 3; ++i) {std::memcpy(inputTensorValues.data() + i * channelSize,channels[i].data, channelSize * sizeof(float));}Ort::MemoryInfo memoryInfo = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault);std::vector<Ort::Value> inputTensors;inputTensors.push_back(Ort::Value::CreateTensor<float>(memoryInfo,inputTensorValues.data(),inputTensorValues.size(),inputNodeDims[0].data(),inputNodeDims[0].size()));// 执行推理auto start = std::chrono::high_resolution_clock::now();std::vector<Ort::Value> outputTensors = session->Run(runOptions,inputNames.data(),inputTensors.data(),inputTensors.size(),outputNames.data(),outputNames.size());auto end = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);qDebug() << "推理时间:" << duration.count() << "ms";// 后处理结果return postprocessResults(outputTensors, originalSize);}catch (const std::exception& e) {qDebug() << "推理错误:" << e.what();return {};}
}// 图像预处理
cv::Mat QtWidgetsApplication10::preprocessImage(const cv::Mat& image)
{cv::Mat resized, floatImage;// 调整图像尺寸cv::resize(image, resized, cv::Size(inpWidth, inpHeight));// 转换颜色空间 BGR -> RGBcv::cvtColor(resized, resized, cv::COLOR_BGR2RGB);// 归一化到 [0,1] 并转换为floatresized.convertTo(floatImage, CV_32FC3, 1.0 / 255.0);return floatImage;
}// 图像后处理
std::vector<Detection> QtWidgetsApplication10::postprocessResults(const std::vector<Ort::Value>& outputTensors, const cv::Size& originalSize,float confThreshold, float iouThreshold)
{std::vector<Detection> detections;if (outputTensors.empty()) {qDebug() << "输出张量为空";return detections;}// 获取输出数据const float* outputData = outputTensors[0].GetTensorData<float>();auto outputShape = outputTensors[0].GetTensorTypeAndShapeInfo().GetShape();// 输出形状: [1, 3, 6] - [batch, num_detections, (x, y, w, h, conf, class)]int numDetections = static_cast<int>(outputShape[1]);int detectionSize = static_cast<int>(outputShape[2]);qDebug() << "=== 后处理信息 ===";qDebug() << "输出形状: [" << outputShape[0] << ", " << outputShape[1] << ", " << outputShape[2] << "]";qDebug() << "检测数量:" << numDetections; //3qDebug() << "检测维度:" << detectionSize; //6qDebug() << "原始图像尺寸:" << originalSize.width << "x" << originalSize.height;qDebug() << "模型输入尺寸:" << inpWidth << "x" << inpHeight;// 计算缩放比例float scaleX = static_cast<float>(originalSize.width) / inpWidth;float scaleY = static_cast<float>(originalSize.height) / inpHeight;qDebug() << "缩放比例 - X:" << scaleX << " Y:" << scaleY;int validDetections = 0;for (int i = 0; i < numDetections; ++i) {const float* detection = outputData + i * detectionSize;float conf = detection[4];// 跳过低置信度的检测if (conf < confThreshold) continue;validDetections++;// 直接使用原始坐标点float x1 = detection[0];float y1 = detection[1];float x2 = detection[2];float y2 = detection[3];int classId = static_cast<int>(detection[5]);// 输出原始坐标信息qDebug() << "检测" << validDetections << " - 原始坐标: ("<< x1 << ", " << y1 << ", " << x2 << ", " << y2<< ") 置信度: " << conf << " 类别: " << classId;// 如果需要将坐标映射回原始图像尺寸float orig_x1 = x1 * scaleX;float orig_y1 = y1 * scaleY;float orig_x2 = x2 * scaleX;float orig_y2 = y2 * scaleY;qDebug() << "检测" << validDetections << " - 映射后坐标: ("<< orig_x1 << ", " << orig_y1 << ", " << orig_x2 << ", " << orig_y2 << ")";// 确保坐标在图像范围内orig_x1 = std::max(0.0f, std::min(orig_x1, static_cast<float>(originalSize.width)));orig_y1 = std::max(0.0f, std::min(orig_y1, static_cast<float>(originalSize.height)));orig_x2 = std::max(0.0f, std::min(orig_x2, static_cast<float>(originalSize.width)));orig_y2 = std::max(0.0f, std::min(orig_y2, static_cast<float>(originalSize.height)));Detection det;det.box = cv::Rect(static_cast<int>(orig_x1), static_cast<int>(orig_y1),static_cast<int>(orig_x2 - orig_x1), static_cast<int>(orig_y2 - orig_y1));det.conf = conf;det.classId = classId;detections.push_back(det);qDebug() << "检测" << validDetections << " - 最终边界框: ("<< det.box.x << ", " << det.box.y << ", "<< det.box.width << ", " << det.box.height << ")";}qDebug() << "有效检测数量:" << detections.size();qDebug() << "=== 后处理完成 ===";return detections;
}// 绘制检测结果
void QtWidgetsApplication10::drawDetections(cv::Mat& image, const std::vector<Detection>& detections)
{for (const auto& detection : detections) {// 选择颜色(基于类别ID)cv::Scalar color = colors[detection.classId % colors.size()];// 绘制边界框cv::rectangle(image, detection.box, color, 2);// 创建标签std::string label = "Class " + std::to_string(detection.classId);label += " " + std::to_string(detection.conf).substr(0, 4);// 计算标签背景尺寸int baseLine;cv::Size labelSize = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);// 绘制标签背景cv::rectangle(image,cv::Point(detection.box.x, detection.box.y - labelSize.height - baseLine),cv::Point(detection.box.x + labelSize.width, detection.box.y),color, cv::FILLED);// 绘制标签文本cv::putText(image, label,cv::Point(detection.box.x, detection.box.y - baseLine),cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 255, 255), 1);}
}// 更新显示
void QtWidgetsApplication10::updateDisplay()
{if (displayImage.empty()) return;// 将OpenCV图像转换为Qt图像cv::Mat rgbImage;cv::cvtColor(displayImage, rgbImage, cv::COLOR_BGR2RGB);QImage qimg(rgbImage.data, rgbImage.cols, rgbImage.rows,rgbImage.step, QImage::Format_RGB888);QPixmap pixmap = QPixmap::fromImage(qimg);// 在QLabel中显示图像ui.label->setPixmap(pixmap.scaled(ui.label->width(),ui.label->height(),Qt::KeepAspectRatio, Qt::SmoothTransformation));
}// 模型加载函数
bool QtWidgetsApplication10::loadYOLOv11Model(const std::string& model_path)
{try {// 初始化ONNX Runtime环境initializeONNXRuntime();// 使用QString进行路径转换QString qModelPath = QString::fromStdString(model_path);std::wstring wideModelPath = qModelPath.toStdWString();// 创建会话session = std::make_unique<Ort::Session>(*env, wideModelPath.c_str(), sessionOptions);// 获取模型输入输出信息Ort::AllocatorWithDefaultOptions allocator;// 清空之前的存储inputNamesStr.clear();inputNames.clear();inputNodeDims.clear();outputNamesStr.clear();outputNames.clear();outputNodeDims.clear();// 获取输入信息size_t numInputNodes = session->GetInputCount();for (size_t i = 0; i < numInputNodes; i++) {auto inputName = session->GetInputNameAllocated(i, allocator);inputNamesStr.push_back(std::string(inputName.get()));inputNames.push_back(inputNamesStr.back().c_str());Ort::TypeInfo inputTypeInfo = session->GetInputTypeInfo(i);auto inputTensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();auto inputDims = inputTensorInfo.GetShape();inputNodeDims.push_back(inputDims);qDebug() << "输入名称:" << inputNamesStr.back().c_str();qDebug() << "输入维度:";for (auto dim : inputDims) {qDebug() << dim;}}// 获取输出信息size_t numOutputNodes = session->GetOutputCount();for (size_t i = 0; i < numOutputNodes; i++) {auto outputName = session->GetOutputNameAllocated(i, allocator);outputNamesStr.push_back(std::string(outputName.get()));outputNames.push_back(outputNamesStr.back().c_str());Ort::TypeInfo outputTypeInfo = session->GetOutputTypeInfo(i);auto outputTensorInfo = outputTypeInfo.GetTensorTypeAndShapeInfo();auto outputDims = outputTensorInfo.GetShape();outputNodeDims.push_back(outputDims);qDebug() << "输出名称:" << outputNamesStr.back().c_str();qDebug() << "输出维度:";for (auto dim : outputDims) {qDebug() << dim;}}// 从输入维度中获取实际的输入高度和宽度if (!inputNodeDims.empty() && inputNodeDims[0].size() == 4) {inpHeight = static_cast<int>(inputNodeDims[0][2]);inpWidth = static_cast<int>(inputNodeDims[0][3]);qDebug() << "模型输入尺寸:" << inpHeight << "x" << inpWidth;}// 打印模型信息printModelInfo();return true;}catch (const Ort::Exception& e) {qDebug() << "ONNX Runtime错误:" << e.what();return false;}catch (const std::exception& e) {qDebug() << "标准异常:" << e.what();return false;}
}void QtWidgetsApplication10::initializeONNXRuntime()
{env = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_WARNING, "YOLOv11");runOptions = Ort::RunOptions();
}void QtWidgetsApplication10::printModelInfo()
{qDebug() << "=== YOLOv10模型信息 ===";qDebug() << "输入尺寸:" << inpHeight << "x" << inpWidth;qDebug() << "输入节点数量:" << session->GetInputCount();qDebug() << "输出节点数量:" << session->GetOutputCount();for (size_t i = 0; i < inputNamesStr.size(); i++) {qDebug() << "输入" << i << ":" << inputNamesStr[i].c_str();}for (size_t i = 0; i < outputNamesStr.size(); i++) {qDebug() << "输出" << i << ":" << outputNamesStr[i].c_str();}qDebug() << "=========================";
}
三、完整项目下载
通过网盘分享的知识:
QT_ONNX_YOLO_DEMO
QT_ONNX_YOLO_DEMO.rar
