【Yolov8部署】 VS2019+opencv-dnn CPU环境下部署目标检测模型

news2025/4/2 6:19:08

文章目录

  • 前言
  • 一、导出yolov8模型为onnx文件
  • 二、VS2019配置及opencv环境配置
  • 三、opencv部署
  • 总结


前言

本文主要研究场景为工业场景下,在工控机与工业相机环境中运行的视觉缺陷检测系统,因此本文主要目的为实现c++环境下,将yolov8已训练好的检测模型部署在opencv中通过cpu进行检测运算

一、导出yolov8模型为onnx文件

Yolo官方支持onnx的使用,直接使用yolo的官方库即可
配置好训练权重文件路径,输出参数按自己需求查手册配置

from ultralytics import YOLO

# Load a model
#model = YOLO("yolov8n.pt")  # Load an official model
model = YOLO(r"D:\deep_learning\YOLOv8.2\runs\train\exp9\weights\best.pt") # Load a custom trained model

# Export the model
success = model.export(format="onnx", opset=11, simplify=True)

输出成功的文件会在权重文件同目录下
在这里插入图片描述
这里生成的文件为11.6MB,过小的onnx文件可能是生成失败产生的
在这里插入图片描述
获取了onnx文件后接下来配置调用onnx文件的部分

二、VS2019配置及opencv环境配置

VS2019在微软官方网站上下载即可

opencv的版本有很多,但经过多次测试发现在cpu场景下要成功调用dnn模块至少需要4.8.0版本
opencv 4.5.4版本
opencv 4.5.5版本
opencv 4.7.0版本
经测试都会在读取onnx文件时发生致命错误

cv::dnn::readNetFromONNX(modelPath);

该条函数在调用时会直接抛出异常终止程序

0x00007FFC088CB699(位于 OpencvYoloYest.exe 中)有未经处理的异常: 
Microsoft C++ 异常: cv::Exception,位于内存位置 0x0000002AF32FB5B0 处。

opencv需要在官网下载,官网提供X64的release包可以直接下载提取使用
也可通过cmake编译cuda版opencv或是x86的动态库来使用
opencv官方releases下载地址
选取Windows版下载即可,或下载Sources包使用cmake手动编译

在这里插入图片描述

本文仅记录采用opencv 4.8.0版本

opencv的环境配置主要分为两部分
系统变量设置:
在这里插入图片描述
在这里插入图片描述
VS2019动态库配置:
注意C++标准需要选择为ISO C++17标准

在这里插入图片描述
包含目录与库目录配置

在这里插入图片描述
在链接器的输入附加依赖项中添加opencv动态库
注意Debug版本需要将该opencv_world480.lib改为opencv_world480d.lib

在这里插入图片描述
跨平台或老旧工控机需要x86版本则需通过cmake手动编译win32版本
至此环境配置就完成了,接下来是库的调用,即在opencv中的部署

三、opencv部署

本文提供代码,同时也可通过yolo官方处下载代码
链接与代码如下:
YOLOv8-CPP-Inference
inference.cpp

#include "inference.h"

Inference::Inference(const std::string onnxModelPath, const cv::Size modelInputShape= cv::Size(640, 640), const std::string classesTxtFile= "classes.txt", const bool runWithCuda=false)
{
    modelPath = onnxModelPath;
    modelShape = modelInputShape;
    classesPath = classesTxtFile;
    cudaEnabled = runWithCuda;

    loadOnnxNetwork();
     //loadClassesFromFile(); The classes are hard-coded for this example
}

std::vector<Detection> Inference::runInference(const cv::Mat& input)
{
    cv::Mat modelInput = input;
    if (letterBoxForSquare && modelShape.width == modelShape.height)
        modelInput = formatToSquare(modelInput);

    cv::Mat blob;
    cv::dnn::blobFromImage(modelInput, blob, 1.0 / 255.0, modelShape, cv::Scalar(), true, false);
    net.setInput(blob);

    std::vector<cv::Mat> outputs;
    net.forward(outputs, net.getUnconnectedOutLayersNames());

    //cv::Mat cpuOutput;
    //outputs[0].copyTo(cpuOutput);  // 将数据从 GPU 复制到 CPU 的 cv::Mat 对象中

    //float* data = reinterpret_cast<float*>(outputs.data);  // 将数据赋值给 float* 指针

    int rows = outputs[0].size[1];
    int dimensions = outputs[0].size[2];

    bool yolov8 = true;
    // yolov5 has an output of shape (batchSize, 25200, 85) (Num classes + box[x,y,w,h] + confidence[c])
    // yolov8 has an output of shape (batchSize, 84,  8400) (Num classes + box[x,y,w,h])
    if (dimensions > rows) // Check if the shape[2] is more than shape[1] (yolov8)
    {
        yolov8 = true;
        rows = outputs[0].size[2];
        dimensions = outputs[0].size[1];

        outputs[0] = outputs[0].reshape(1, dimensions);
        cv::transpose(outputs[0], outputs[0]);
    }

    //if (cv::cuda::getCudaEnabledDeviceCount() > 0) { // 检查是否启用了GPU计算
    //    
    //    cv::cuda::GpuMat gpuData(outputs[0]);  // 将 GPU 数据包装到 cv::cuda::GpuMat 中
    //    cv::Mat cpuData;
    //    gpuData.download(cpuData);  // 将 GPU 数据下载到 CPU 的 cv::Mat 中
    //    float* data = (float*)cpuData.data;  // 获取 CPU 上的数据指针
    //}
    //else { // 在没有启用GPU计算时,直接使用CPU内存中的数据指针
    //    data = (float*)outputs[0].data;
    //}

    //float* data = (float*)outputs[0].data;
    //************************GPU和CPU的数据交换
    //cv::UMat umatData = outputs[0].getUMat(cv::ACCESS_READ);
    //cv::Mat cpuData;
    //umatData.copyTo(cpuData);
    //********************************

    //float* data = (float*)cpuData.data;
    float* data = (float*)outputs[0].data;

    float x_factor = modelInput.cols / modelShape.width;
    float y_factor = modelInput.rows / modelShape.height;

    std::vector<int> class_ids;
    std::vector<float> confidences;
    std::vector<cv::Rect> boxes;

    for (int i = 0; i < rows; ++i)
    {
        if (yolov8)
        {
            float* classes_scores = data + 4;

            cv::Mat scores(1, classes.size(), CV_32FC1, classes_scores);
            cv::Point class_id;
            double maxClassScore;

            minMaxLoc(scores, 0, &maxClassScore, 0, &class_id);

            if (maxClassScore > modelScoreThreshold)
            {
                confidences.push_back(maxClassScore);
                class_ids.push_back(class_id.x);

                float x = data[0];
                float y = data[1];
                float w = data[2];
                float h = data[3];

                int left = int((x - 0.5 * w) * x_factor);
                int top = int((y - 0.5 * h) * y_factor);

                int width = int(w * x_factor);
                int height = int(h * y_factor);

                boxes.push_back(cv::Rect(left, top, width, height));
            }
        }
        else // yolov5
        {
            float confidence = data[4];

            if (confidence >= modelConfidenceThreshold)
            {
                float* classes_scores = data + 5;

                cv::Mat scores(1, classes.size(), CV_32FC1, classes_scores);
                cv::Point class_id;
                double max_class_score;

                minMaxLoc(scores, 0, &max_class_score, 0, &class_id);

                if (max_class_score > modelScoreThreshold)
                {
                    confidences.push_back(confidence);
                    class_ids.push_back(class_id.x);

                    float x = data[0];
                    float y = data[1];
                    float w = data[2];
                    float h = data[3];

                    int left = int((x - 0.5 * w) * x_factor);
                    int top = int((y - 0.5 * h) * y_factor);

                    int width = int(w * x_factor);
                    int height = int(h * y_factor);


                }
            }
        }

        data += dimensions;
    }

    std::vector<int> nms_result;
    cv::dnn::NMSBoxes(boxes, confidences, modelScoreThreshold, modelNMSThreshold, nms_result);

    std::vector<Detection> detections{};
    for (unsigned long i = 0; i < nms_result.size(); ++i)
    {
        int idx = nms_result[i];

        Detection result;
        result.class_id = class_ids[idx];
        result.confidence = confidences[idx];

        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<int> dis(100, 255);
        result.color = cv::Scalar(dis(gen),
            dis(gen),
            dis(gen));

        result.className = classes[result.class_id];
        result.box = boxes[idx];

        detections.push_back(result);
    }

    return detections;
}

void Inference::loadClassesFromFile()
{
    std::ifstream inputFile(classesPath);
    if (inputFile.is_open())
    {
        std::string classLine;
        while (std::getline(inputFile, classLine))
            classes.push_back(classLine);
        inputFile.close();
    }
}

void Inference::loadOnnxNetwork()
{
    net = cv::dnn::readNetFromONNX(modelPath);
    if (cudaEnabled)
    {
        std::cout << "\nRunning on CUDA" << std::endl;
        net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
        net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);
    }
    else
    {
        std::cout << "\nRunning on CPU" << std::endl;
        net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
        net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
    }
}

cv::Mat Inference::formatToSquare(const cv::Mat& source)
{
    int col = source.cols;
    int row = source.rows;
    int _max = MAX(col, row);
    cv::Mat result = cv::Mat::zeros(_max, _max, CV_8UC3);
    source.copyTo(result(cv::Rect(0, 0, col, row)));
    return result;
}





inference.h

#ifndef INFERENCE_H
#define INFERENCE_H

// Cpp native
#include <fstream>
#include <vector>
#include <string>
#include <random>

// OpenCV / DNN / Inference
#include <opencv2/imgproc.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>

//Detection结构体用来保存目标检测的结果实例
struct Detection
{
    int class_id{ 0 };//整形变量用来存储检测到的目标的类别,默认值为0
    std::string className{};//字符串变量用来存储检测到的目标的名称,默认值为空字符串
    float confidence{ 0.0 };//目标检测的置信度(即对目标存在的确定程度)。默认值为0.0。
    cv::Scalar color{};//OpenCV库中的Scalar类型变量,用于存储颜色信息。它可以表示RGB、BGR或灰度颜色空间中的颜色
    cv::Rect box{}; //cv::Rect 类型包含四个成员变量:x、y、width 和 height
};

//Infrence类用来执行目标检测
class Inference
{
public:
    //构造函数(modelInputShape是指模型的大小,默认为640,640;classesTxtFile是类别名称的文本文件路径(可选参数,默认为空字符串);runWithCuda是一个布尔值,指示是否使用CUDA加速运行(可选参数,默认为true)。
    Inference(std::string onnxModelPath, cv::Size modelInputShape, std::string classesTxtFile, bool runWithCuda);
    //公有成员函数,用于执行目标检测推断。它接受一个cv::Mat类型的输入图像,并返回一个std::vector<Detection>类型的检测结果。该函数将执行目标检测算法,将检测到的目标信息封装到Detection结构体中,并将所有检测结果存储在一个向量中。
    std::vector<Detection> runInference(const cv::Mat& input);

    //私有成员函数,用于内部操作
private:
    //loadClassesFromFile函数从文本文件中加载类别名称
    void loadClassesFromFile();
    //loadOnnxNetwork函数加载ONNX模型
    void loadOnnxNetwork();
    //formatToSquare函数将输入图像调整为正方形形状。
    cv::Mat formatToSquare(const cv::Mat& source);

    //这些是私有成员变量
    std::string modelPath{};//存储模型文件路径
    std::string classesPath{};//类别文件路径
    bool cudaEnabled{};//CUDA加速的状态

    //字符串向量,用于存储目标检测的类别名称。默认情况下,它包含了一些通用的目标类别名称
    std::vector<std::string> classes{ "bright_collision", "dark_collision" };
    //std::vector<std::string> classes{ "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush" };

    //这是一个OpenCV库中的Size2f类型变量,用于存储模型的输入形状(宽度和高度)。
    cv::Size2f modelShape{};

    //设置目标检测的阈值
    float modelConfidenceThreshold{ 0.50 };//目标置信度的阈值
    float modelScoreThreshold{ 0.45 };//目标得分的阈值
    float modelNMSThreshold{ 0.50 };//非最大抑制的阈值

    //布尔变量,指示是否使用letterbox技术将输入图像调整为正方形形状
    bool letterBoxForSquare = true;

    //该类封装了目标检测推断的相关操作和参数,通过调用构造函数和成员函数,你可以加载模型、执行推断,并获取目标检测的结果
    cv::dnn::Net net;//penCV库中的Net类型变量,用于存储加载的目标检测网络模型
};

#endif // INFERENCE_H

main.cpp

#include "inference.h"
#include "opencv_yolo.h"

int main(int argc, char *argv[])
{
	    //main中调用
    //执行视频检测算法和处理
    bool runOnGPU = false;
    int deviceId = 0; // 指定要使用的GPU设备的索引
    //cv::cuda::setDevice(deviceId);	//本机为amd卡,无法安装cuda所以此语句屏蔽
     //1. 设置你的onnx模型
     //注意,在这个例子中类别是硬编码的,'classes.txt'只是一个占位符。
    Inference inf("D:/VsEnvironment/dataSet/best.onnx", cv::Size(640, 640), "classes.txt", runOnGPU); // classes.txt 可以缺失

    string imgpath = "D:/VsEnvironment/dataSet/deep_screw_roi/test/images/Image_20241126164807270.bmp";
    cv::Mat frame = imread(imgpath);

    std::vector<Detection> output = inf.runInference(frame);
    int detections = output.size();
    std::cout << "Number of detections: " << detections << std::endl;
    for (int i = 0; i < detections; ++i)
    {
        Detection detection = output[i];
        cv::Rect box = detection.box;
        cv::Scalar color = detection.color;
        //根据类别不同,显示不同的颜色
        if (detection.class_id == 0) {
            color = cv::Scalar(0, 255, 0); // 红色 (B, G, R)
        }
        else if (detection.class_id == 1) {
            color = cv::Scalar(0, 0, 255); // 红色 (B, G, R)
        }
        else {
            color = cv::Scalar(255, 0, 0); // 红色 (B, G, R)
        }
        // Detection box
        cv::rectangle(frame, box, color, 2);
        // Detection box text
        std::string classString = detection.className + ' ' + std::to_string(detection.confidence).substr(0, 4);
        cv::Size textSize = cv::getTextSize(classString, cv::FONT_HERSHEY_DUPLEX, 1, 2, 0);
        cv::Rect textBox(box.x, box.y - 40, textSize.width + 10, textSize.height + 20);
        cv::rectangle(frame, textBox, color, cv::FILLED);
        cv::putText(frame, classString, cv::Point(box.x + 5, box.y - 10), cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(0, 0, 0), 2, 0);
    }
    cv::namedWindow("Inference", cv::WINDOW_NORMAL); // 创建具有可调整大小功能的窗口
    cv::imshow("Inference", frame); // 在窗口中显示图像

    cv::destroyAllWindows();
    


}

总结

如下为成功运行例程,检测图像涉及科研项目,此处不便展示

在这里插入图片描述
以后还将完成VS2019+opencv+onnxruntime cpu与gpu的cuda加速环境下部署目标检测模型的测试,同时上传csdn文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2325330.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3.30学习总结 Java包装类+高精度算法+查找算法

包装类&#xff1a; 基本数据类型对应的引用数据类型。 基本数据类型&#xff1a;在内存中记录的是真实的值。 八种包装类的父类都是Object类。 对象之间不能直接进行计算。 JDK5之后可以把int和integer看成一个东西&#xff0c;因为会进行内部优化。自动装箱和自动拆箱。 …

3月30号

// 1.toString 返回对象的字符串表示形式Object objnew Object();String str1obj.toString();System.out.println(str1);//java.lang.Objectb4c966a// 核心逻辑: // 当我们打印一个对象的时候,底层会调用对象的toString方法,把对象变成字符串 // 然…

Java——输入,循环,BigInteger,拷贝,排序

读取输入 打印输出到“ 标准输出流”&#xff08;即控制台窗口&#xff09;是一件非常容易的事情&#xff0c;只要 调用System.out.println 即可。然而&#xff0c;读取“ 标准输人流” System.in就没有那么简单了。要想通 过控制台进行输人&#xff0c;首先需要构造一个Scann…

Elasticsearch客户端工具初探--kibana

1 Kibana简介 Kibana是Elastic Stack&#xff08;ELK&#xff09;中的可视化工具&#xff0c;用于对Elasticsearch中存储的数据进行搜索、分析和可视化展示。它提供了直观的Web界面&#xff0c;支持日志分析、业务监控、数据探索等功能&#xff0c;广泛应用于运维监控、安全分析…

ollama在win10安装、使用、卸载

目录 前置&#xff1a; 1 下载ollama 2 安装 3 配置环境变量&#xff0c;设置模型存储位置 4 使用 5 卸载 前置&#xff1a; 1 在打算安装ollama之前&#xff0c;需要先检查电脑当前状态是否能使用ollama。确认条件满足再进行安装操作。 2 https://github.com/ollama/…

查看iphone手机的使用记录-克魔实战

如何查看 iOS 设备近期的详细使用数据 在日常使用手机时&#xff0c;了解设备的运行状态和各项硬件的使用情况可以帮助分析耗电情况、优化应用使用方式。iOS 设备提供了一些数据记录&#xff0c;能够显示应用的启动和关闭时间、后台运行情况&#xff0c;以及应用在使用过程中调…

[Lc5_dfs+floodfill] 简介 | 图像渲染 | 岛屿数量

目录 0.floodfill算法简介 1.图像渲染 题解 2.岛屿数量 题解 之前我们在 bfs 中有介绍过[Lc15_bfsfloodfill] 图像渲染 | 岛屿数量 | 岛屿的最大面积 | 被围绕的区域&#xff0c;现在我们来看看 dfs 又是如何解决的呢 0.floodfill算法简介 floodfill算法又叫洪水灌溉或者…

AI-Sphere-Butler之如何使用腾讯云ASR语音识别服务

环境&#xff1a; AI-Sphere-Butler WSL2 英伟达4070ti 12G Win10 Ubuntu22.04 腾讯云ASR 问题描述&#xff1a; AI-Sphere-Butler之如何使用腾讯云ASR语音识别服务&#xff0c;本地硬件配置不高的情况&#xff0c;建议使用云服务商的ASR 解决方案&#xff1a; 1.登…

Qwen最新多模态大模型:Qwen2.5-Omni介绍与快速入门

一、模型技术突破&#xff1a;重新定义多模态交互 近日&#xff0c;Qwen2.5-Omni正式发布了&#xff01; 这是Qwen系列中全新的旗舰级端到端多模态大模型&#xff0c;专为全面的多模式感知设计&#xff0c;无缝处理包括文本、图像、音频和视频在内的各种输入&#xff0c;同时…

【Golang】第十一弹------反射

&#x1f381;个人主页&#xff1a;星云爱编程 &#x1f50d;所属专栏&#xff1a;【Go】 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 长风破浪会有时&#xff0c;直挂云帆济沧海 目录 1.反射基本介绍 2.反射重要的函数和概念 3.反射应用场景 4.反…

C#里使用libxl的对齐/边框/颜色

一份好的EXCEL文件,通道会有不同的颜色和边框来表示。 以便表示一些重要的信息,这样才能让人们一眼就看到需要关注的信息。 如下面所示: 要显示上面的内容,需要使用下面的例子: private void button12_Click(object sender, EventArgs e){var book = new ExcelBook();if…

软考中级-软件设计师信息安全模块考点解析

一、防火墙技术 内部网络是 安全的可信赖的外部网络是不安全的不可信赖的外部网络和内部网络之间有一个DMZ隔离区&#xff0c; 可以在DMZ隔离区中搭建服务&#xff1a;例如&#xff1a;WEB服务器 安全排序&#xff1a;内网>DMZ>外网 三个发展阶段&#xff1a; 包过滤防…

【蓝桥杯】每日练习 Day 16,17

前言 接下来是这两天的题目&#xff08;昨天主播打完模拟赛感觉身体被掏空所以没有写题目的总结&#xff09;&#xff0c;只有三道题。 一道并查集&#xff0c;一道单调栈和一道单调队列。 奶酪 分析 这是一道模板题&#xff08;连通块&#xff09;&#xff0c;只讲思路。 …

Linux驱动开发--IIC子系统

1.1 简介 I2C 是很常见的一种总线协议&#xff0c; I2C 是 NXP 公司设计的&#xff0c; I2C 使用两条线在主控制器和从机之间进行数据通信。一条是 SCL(串行时钟线)&#xff0c;另外一条是 SDA(串行数据线)&#xff0c;这两条数据线需要接上拉电阻&#xff0c;总线空闲的时候 …

如何应对硬件测试覆盖率不足导致量产故障

硬件测试覆盖率不足导致的量产故障是硬件制造领域的一大痛点。要有效应对&#xff0c;必须从提高测试覆盖率、优化测试方案、引入风险管理机制三个方面入手。其中&#xff0c;优化测试方案尤为关键&#xff0c;应从产品设计阶段开始&#xff0c;通过精确的测试用例规划、详细的…

Centos7 安装 TDengine

Centos7 安装 TDengine 1、简介 官网&#xff1a; https://www.taosdata.com TDengine 是一款开源、高性能、云原生的时序数据库&#xff08;Time Series Database, TSDB&#xff09;, 它专为物联网、车联网、工业互联网、金融、IT 运维等场景优化设计。同时它还带有内建的缓…

Kafka 多线程开发消费者实例

目前&#xff0c;计算机的硬件条件已经大大改善&#xff0c;即使是在普通的笔记本电脑上&#xff0c;多核都已经是标配了&#xff0c;更不用说专业的服务器了。如果跑在强劲服务器机器上的应用程序依然是单线程架构&#xff0c;那实在是有点暴殄天物了。不过&#xff0c;Kafka …

Linux线程池实现

1.线程池实现 全部代码&#xff1a;whb-helloworld/113 1.唤醒线程 一个是唤醒全部线程&#xff0c;一个是唤醒一个线程。 void WakeUpAllThread(){LockGuard lockguard(_mutex);if (_sleepernum)_cond.Broadcast();LOG(LogLevel::INFO) << "唤醒所有的休眠线程&q…

Linux《进程概念(上)》

在之前的Linux学习当中我们已经了解了基本的Linux指令以及基础的开发工具的使用&#xff0c;那么接下来我们就要开始Linux当中一个非常重要的部分的学习——进程&#xff0c;在此进程是我们之后Linux学习的基础&#xff0c;并且通过进程的学习会让我们了解更多的操作系统的相关…

【算法】并查集基础讲解

一、定义 一种树型的数据结构&#xff0c;用于处理一些不相交集合的合并及查询问题。思想是用一个数组表示了整片森林&#xff08;parent&#xff09;&#xff0c;树的根节点唯一标识了一个集合&#xff0c;只要找到了某个元素的的树根&#xff0c;就能确定它在哪个集合里。 …