当前位置:首页 » 《休闲阅读》 » 正文

2024年7月YOLOv8+QT初步搭建目标检测(避坑)

12 人参与  2024年09月23日 19:20  分类 : 《休闲阅读》  评论

点击全文阅读


目录

一、YOLOv8下载和环境搭建(Python版本为3.8)

二、Pycharm中YOLOv8模型导出

1.pytorch模型pt导出为onnx格式

2.官方COCO数据集coco.yaml介绍

3.yolov8的onnx格式介绍

注:使用C++库为17,onnxruntime、opencv等各版本要大于等于官方推荐

三、QT中的配置(UI不介绍了)

1.opencv库和onnxruntime库安装

2.QMake配置

注: +=后面不要有注释以及不要空一行;除最后一个路径的之后都要加\

3.动态链接库导入(注意)

四、QT+opencv视频流获取代码

1.视频捕获

CameraDetect.h

CameraDetect.cpp

2.视频显示(缺乏的类直接include)

五、YOLOv8官方cpp源码介绍和修改

1.(推理)inference.cpp/h(写在注释里)

2.YOLO_V8::TensorProcess需要修改(坑)

3.创建myYoloDetect(目前先试用检测功能,暂不分类)

myYoloDetect.h

myYoloDetect.cpp

代码解释和补充

4.运行调试(意料之内有点卡,我的CPU推理速度很慢)

六、QT打包

1.使用MSVC终端打包

2.使用工具打包工具再次封装


一、YOLOv8下载和环境搭建(Python版本为3.8)

github上下载项目ultralytics:https://github.com/ultralytics/ultralytics,可以选择git clone或者直接下载ZIP解压到本地。本项目采用windows操作系统,使用CPU进行推理(默认操作),IDE为pycharm由于目前yolov8官方已经将requirements.txt换成了pyproject.toml,只能在终端(terminal)使用pip install ultralytics命令安装需要的库(感觉更方便),或者使用命令pdm install pyproject.toml,但是这样路径会安装到项目中而虚拟环境识别不到,故建议使用前者。

二、Pycharm中YOLOv8模型导出

1.pytorch模型pt导出为onnx格式

对于不同平台需要,如嵌入式、移动端等需要的模型需要推理有更快的速度,官方也给出了C++和onnxruntime构建代码例子example,如下:

直接将cpp和h文件拷贝进入qt就可以了,CMakeLists.txt用来配置需要的链接库和路径,在QT中也可以换成QMake(.pro)使用,README.md有指出怎么导出onnx模型,如下(32位)

from ultralytics import YOLO# Load a YOLOv8 modelmodel = YOLO("yolov8n.pt")# Export the model,参数opset为导出版本,dynamic=True表示输入图片是否可以是动态的,反之则限制为一定尺寸,imgsz即是限定尺寸为640x640(没有onnx就pip install onnx)model.export(format="onnx", opset=12, simplify=True, dynamic=False, imgsz=640)

如果你想使用16位浮点数的模型,一般是用NVDIA训练和推理的模型,而可以简化计算量而加快推理,得使用以下代码,导出的模型默认都在工作目录或当前运行的.py文件的同级目录

import onnxfrom onnxconverter_common import float16model = onnx.load(R"YOUR_ONNX_PATH")model_fp16 = float16.convert_float_to_float16(model)onnx.save(model_fp16, R"YOUR_FP16_ONNX_PATH")

其中模型文件yolov8n.pt的n对应的是网络参数特征(深度,宽度,最大输出特征图数量),可以见cfg/models/v8/yolov8.yaml,详细代码如下

scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'  # [depth, width, max_channels]  n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers,  3157200 parameters,  3157184 gradients,   8.9 GFLOPs  s: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients,  28.8 GFLOPs  m: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients,  79.3 GFLOPs  l: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs  x: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs

2.官方COCO数据集coco.yaml介绍

yolov8模型共有80个检测目标,在coco.yaml(可以在这里coco数据集下载)中names里面就是检测到类别总数,不过在后面qt中使用它替换成txt文件也可以,只是用来读取类别并显示。

3.yolov8的onnx格式介绍

yolov8的onnx模型输出结构为1x84x8400,其中1为batch size即一次处理图片数量,84前4个分别是锚框中心的坐标位置(x,y),宽度、高度,后面80个为类别,8400为锚框总数,后面的80x8400个数为类别得分。

注:使用C++库为17,onnxruntime、opencv等各版本要大于等于官方推荐

三、QT中的配置(UI不介绍了)

1.opencv库和onnxruntime库安装

官网下载Releases - OpenCV,本人使用的是4.8.0,试过4.0.0也可以

官网下载https://github.com/microsoft/onnxruntime/releases/tag/v1.16.0,1.14.1也行,下载win-x64不带GPU的zip

2.QMake配置

在.pro文件中使用LIBS配置lib的位置,onnxruntime.lib,opencv_world480.lib以及opencv_world480d.lib,具体取决于你的路径,不过我直接装在E和F盘下所以看后面大部分即可

CONFIG(release,debug|release):LIBS +=-LE:/ONNX/onnxruntime-win-x64-1.16.0/onnxruntime-win-x64-1.16.0/lib/ -lonnxruntime\                                      -LF:/opencv/build/x64/vc16/lib/ -lopencv_world480else:CONFIG(debug,debug|release):LIBS += -LE:/ONNX/onnxruntime-win-x64-1.16.0/onnxruntime-win-x64-1.16.0/lib/ -lonnxruntime\                                      -LF:/opencv/build/x64/vc16/lib/ -lopencv_world480d

配置头文件路径

INCLUDEPATH += F:/opencv/build/include\               F:/opencv/build/include/opencv2\               E:/ONNX/onnxruntime-win-x64-1.16.0/onnxruntime-win-x64-1.16.0/includeDEPENDPATH  +=F:/opencv/build/include\              F:/opencv/build/include/opencv2\              E:/ONNX/onnxruntime-win-x64-1.16.0/onnxruntime-win-x64-1.16.0/include

注: +=后面不要有注释以及不要空一行;除最后一个路径的之后都要加\

3.动态链接库导入(注意)

在debug和release目录中要导入onnxruntime.dll和带shared的dll(在下载的),因为win系统中也有同名的dll库而会冲突,而如果环境变量里没有配置opencv的路径时也需要导入

四、QT+opencv视频流获取代码

1.视频捕获

CameraDetect.h

#ifndef CAMERADETECT_H#define CAMERADETECT_H#include "mainwindow.h"#include "QObject"#include "QTimer"#include "QPixmap"#include "myYoloDetect.h"using namespace cv;using namespace std;class MainWindow;class CameraDetect:public QObject{    Q_OBJECTpublic:    explicit CameraDetect(QObject *parent=nullptr);    void openCamera();    void closeCamera();private:    VideoCapture *cap;//捕获视频流    QTimer *capTimer;    bool iSOpen=false; //是否打开相机标志signals:    void cameraShowImage(QPixmap);    void cameraIsOpen(bool);};#endif // CAMERADETECT_H

CameraDetect.cpp

#include "cameraDetect.h"#include "QDebug"#include "QThread"CameraDetect::CameraDetect(QObject *parent):QObject(parent){}void CameraDetect::openCamera(){    //开启相机    int cameraDevId=0;    cap=new VideoCapture;    //使用默认相机,CAP_DSHOW表示win系统下的DirrectShow    cap->open(cameraDevId,CAP_DSHOW);    if(!cap->isOpened())    {        cap->release();        return;    }    else        iSOpen=true;    //将相机打开的信号发送出去    emit cameraIsOpen(true);    //qDebug()<<QString("相机参数:(%1,%2),%3 FPS").arg(cap->get(3)).arg(cap->get(4)).arg(cap->get(5));    //使用定时器定时捕获视频并显示,节省负载    capTimer=new QTimer;    connect(capTimer,&QTimer::timeout,[=]{        cv::Mat frame;        cap->read(frame);        if(frame.empty())        {            closeCamera();            return;        }        //导入YOLOv8时需要的检测        frame=myDetectTest(frame);        //相机RGB的转换,opencv默认是BGR的        cvtColor(frame,frame,COLOR_BGR2RGB);        QPixmap pixmap=QPixmap::fromImage(QImage((const uchar*)(frame.data),frame.cols,frame.rows,frame.step,QImage::Format_RGB888));        //将显示图像的信号发送出去        emit cameraShowImage(pixmap);    });    //采样时间    capTimer->start(1);}//关闭摄像头void CameraDetect::closeCamera(){    if(!iSOpen)        return;    if(capTimer->isActive())    {        capTimer->stop();        capTimer->deleteLater();        iSOpen=false;        emit cameraIsOpen(false);        cap->release();        cv::destroyAllWindows();    }}

2.视频显示(缺乏的类直接include)

/*********实时检测************/CameraDetect* camera;QThread* cameraThread;void MainWindow::time_Detect(){    camera=new CameraDetect;    cameraThread=new QThread;    //将相机捕获放进新的线程中    camera->moveToThread(cameraThread);    //绑定线程与对象,如当线程发送finished信号时自动结束线程和删除相机对象,释放内存    connect(cameraThread,&QThread::finished,cameraThread,&QThread::deleteLater);    connect(cameraThread,&QThread::finished,camera,&CameraDetect::deleteLater);    //将camera对象的cameraShowImage函数与主界面的展示视频绑定在一起    connect(camera,&CameraDetect::cameraShowImage,this,&MainWindow::showVideoImage);    cameraThread->start();    //将自己设定的捕获开启按钮和结束按钮绑定在一起    connect(ui->startDetButton_2,&QPushButton::clicked,camera,&CameraDetect::openCamera);    connect(ui->clearButton_2,&QPushButton::clicked,camera,&CameraDetect::closeCamera);}void MainWindow::showVideoImage(QPixmap img){    //这是使用QGraphicsPixmapItem,QGraphicsScene和QGraphicView这样显示能够缩放    item1=new QGraphicsPixmapItem(img);    scene1->clear();    scene1->addItem(item1);    ui->input_Image->setScene(scene1);    //适应窗口缩放    ui->input_Image->fitInView(item1,Qt::KeepAspectRatio);}MainWindow::~MainWindow(){    //结束时要先退出线程释放内存    if(cameraThread->isRunning())    {        cameraThread->quit();        cameraThread->wait();    }    delete ui;}

五、YOLOv8官方cpp源码介绍和修改

1.(推理)inference.cpp/h(写在注释里)

typedef struct _DL_INIT_PARAM{    std::string modelPath;    MODEL_TYPE modelType = YOLO_DETECT_V8;    std::vector<int> imgSize = { 640, 640 };    float rectConfidenceThreshold = 0.1; //选择锚框的置信度阈值    float iouThreshold = 0.5;//锚框如果重叠,其交并比如果大于0.5,则置信度低的会被抑制,不显示    intkeyPointsNum = 2;//Note:kpt number for pose    bool cudaEnable = false;//选择你是否使用CUDA,需要在开头宏定义USE_CUDA    int logSeverityLevel = 3;    int intraOpNumThreads = 1;} DL_INIT_PARAM;

2.YOLO_V8::TensorProcess需要修改(坑)

在onnx模型提取时要注意它的结构,使用yolov8时要对rawData进行一次翻转rawData.t(),如下。

 cv::Mat rawData;        if (modelType == YOLO_DETECT_V8)        {            // FP32            rawData = cv::Mat(strideNum, signalResultNum, CV_32F, output);            rawData=rawData.t();//需要翻转        }

而在遍历得分时,源码的strideNum(84)和signalResultNum(8400)要调换,因为如上所述84是锚框中的位置大小和类别个数,8400这是锚框的数量(感觉它名字好像起错了?),每个点则是得分,得分最高的类别则存储它的框的坐标位置和大小,遍历8400次。修改如下

for (int i = 0; i < signalResultNum; ++i)        {            float* classesScores = data+4;                                                          //获取得分,前4个是坐标宽长            cv::Mat scores(1, this->classes.size(), CV_32FC1, classesScores);                                   //80个类别的得分            cv::Point class_id;            double maxClassScore=0;            cv::minMaxLoc(scores, 0, &maxClassScore, 0, &class_id);                                               //根据得分找出id索引            /***测试max***/            // QString test=QString::fromStdString(std::to_string(maxClassScore));            // qDebug()<<"测试max:"<<test;            if (maxClassScore > rectConfidenceThreshold)            {                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];                // qDebug()<<x<<" "<<y<<" "<<w<<" "<<h<<" "<<data[4];                int left = int((x - 0.5 * w) * resizeScales);                int top = int((y - 0.5 * h) * resizeScales);                int width = int(w * resizeScales);                int height = int(h * resizeScales);                boxes.push_back(cv::Rect(left, top, width, height));            }            data += strideNum; //地址跳转到下一个框        }

3.创建myYoloDetect(目前先试用检测功能,暂不分类)

myYoloDetect.h

#ifndef MYYOLODETECT_H#define MYYOLODETECT_H#include <iostream>#include <iomanip>#include "inference.h"#include <filesystem>#include <fstream>#include <random>#include "QImage"cv::Mat videoDetector(YOLO_V8*& p,cv::Mat &img);int ReadCocoYaml(YOLO_V8*& p);cv::Mat myDetectTest(cv::Mat &img);//目标检测使用#endif // MYYOLODETECT_H

myYoloDetect.cpp

#include "myYoloDetect.h"#include "QString"#include "QDebug"#include "QDir"#include "QFile"#include "QTemporaryFile"#include "QCoreApplication"//实时视频检测cv::Mat videoDetector(YOLO_V8*& p,cv::Mat &img) {    std::vector<DL_RESULT> res;    p->RunSession(img, res);    for (auto& re : res)    {        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::cout << std::fixed << std::setprecision(2);        std::string label = p->classes[re.classId] + " " +                            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            );        // QString labelstr=QString::fromStdString(std::to_string(re.classId)+p->classes[re.classId]);        // qDebug()<<"类别为:"<<labelstr;    }    return img;}int ReadCocoYaml(YOLO_V8*& p) {    // Open the YAML file,用来创建临时文件    QString relativePath=":/Yolov8/build/Desktop_Qt_6_2_4_MSVC2019_64bit-Release/YOLOv8/coco.yaml";    QString tempPathDir=QCoreApplication::applicationDirPath();    QString tempFileName=tempPathDir+"/temp_coco_xx.yaml";    QString copyFileName=tempPathDir+"/temp_coco.txt";    QFile copyFile(copyFileName);    QTemporaryFile tempfile(tempFileName);    if(copyFile.size()==0)    //打开临时文件进行写入    {        if(tempfile.open())        {            copyFile.setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser);            tempfile.setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser);            QFile resourceFile(relativePath);            resourceFile.open(QIODevice::ReadOnly);            tempfile.write(resourceFile.readAll());            tempfile.flush();            tempFileName=tempfile.fileName();            tempfile.close();            QFile::copy(tempFileName,copyFileName);        }    }    std::ifstream file(copyFileName.toStdString());    if (!file.is_open())    {        std::cerr << "Failed to open file" << std::endl;        return 1;    }    // Read the file line by line    std::string line;    std::vector<std::string> lines;    while (std::getline(file, line))    {        lines.push_back(line);    }    // Find the start and end of the names section    std::size_t start = 0;    std::size_t end = 0;    for (std::size_t i = 0; i < lines.size(); i++)    {        if (lines[i].find("names:") != std::string::npos)        {            start = i + 1;        }        else if (start > 0 && lines[i].find(':') == std::string::npos)        {            end = i;            break;        }    }    // Extract the names    std::vector<std::string> names;    for (std::size_t i = start; i < end; i++)    {        std::stringstream ss(lines[i]);        std::string name;        std::getline(ss, name, ':'); // Extract the number before the delimiter        std::getline(ss, name); // Extract the string after the delimiter        names.push_back(name);    }    p->classes = names;    return 0;}cv::Mat myDetectTest(cv::Mat &img){    YOLO_V8* yoloDetector = new YOLO_V8;    QString relativePath=":/Yolov8/build/Desktop_Qt_6_2_4_MSVC2019_64bit-Release/YOLOv8/yolov8n.onnx";    // 获取应用程序目录    QString appDirPath = QCoreApplication::applicationDirPath();    // 指定临时文件的路径和文件名模板    QString tempFileTemplate = appDirPath + "/temp_yolov8n_xxx.onnx";    QTemporaryFile tempfile(tempFileTemplate);    QString tempFileName,onnxName= appDirPath + "/temp_yolov8n.onnx";    QFile file(onnxName);    //打开临时文件进行写入    if(file.size()==0)    {        if(tempfile.open())        {            tempfile.setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser);            QFile resourceFile(relativePath);            resourceFile.open(QIODevice::ReadOnly);            tempfile.write(resourceFile.readAll());            tempfile.flush();            tempFileName=tempfile.fileName();            tempfile.close();             //复制文件至onnx文件格式,否则临时文件随机后缀加载不出来            QFile::copy(tempFileName,onnxName);            tempfile.destroyed();        }    }    //QString relativePath=modelPathQstr+"/YOLOv8/yolov8n.onnx";    ReadCocoYaml(yoloDetector);    DL_INIT_PARAM params;    params.rectConfidenceThreshold = 0.1;    params.iouThreshold = 0.5;    params.modelPath = onnxName.toStdString();    params.imgSize = { 640, 640 };#ifdef USE_CUDA    params.cudaEnable = true;    // GPU FP32 inference    params.modelType = YOLO_DETECT_V8;    // GPU FP16 inference    //Note: change fp16 onnx model    //params.modelType = YOLO_DETECT_V8_HALF;#else    // CPU inference    params.modelType = YOLO_DETECT_V8;    params.cudaEnable = false;#endif    yoloDetector->CreateSession(params);    cv::Mat detect_Img= videoDetector(yoloDetector,img);    return detect_Img;}

代码解释和补充

由于考虑需要打包处理,模型onnx文件和yaml文件都最好以资源形式导入,即添加到资源管理器中,但是资源文件的路径不能被yolov8使用的标准文件IO流读取,故创建临时文件读取并复制,复制后就不会临时再创建了,不过其实可以直接复制资源文件出来,只是当时笔者想偏了又改了。

    YOLO_V8* yoloDetector = new YOLO_V8;    QString relativePath=":/Yolov8/build/Desktop_Qt_6_2_4_MSVC2019_64bit-Release/YOLOv8/yolov8n.onnx";    // 获取应用程序目录    QString appDirPath = QCoreApplication::applicationDirPath();    // 指定临时文件的路径和文件名模板    // QString tempFileTemplate = appDirPath + "/temp_yolov8n_xxx.onnx";    // QTemporaryFile tempfile(tempFileTemplate);    QString tempFileName,onnxName= appDirPath + "/temp_yolov8n.onnx";    QFile file(onnxName);    //打开临时文件进行写入    if(file.size()==0)    {        // tempfile.setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser);        // QFile resourceFile(relativePath);        // resourceFile.open(QIODevice::ReadOnly);        // tempfile.write(resourceFile.readAll());        // tempfile.flush();        // tempFileName=tempfile.fileName();        // tempfile.close();        //复制文件至onnx文件格式,否则临时文件随机后缀加载不出来        QFile::copy(relativePath,onnxName);    }

4.运行调试(意料之内有点卡,我的CPU推理速度很慢)

六、QT打包

1.使用MSVC终端打包

将build中release里的exe复制到新的文件夹里,然后打开MSVC终端(如果你用的是MSVC构建的)并到达当前exe的目录,输入命令windeployqt xxx.exe,就会初步打包完成

2.使用工具打包工具再次封装

Software Protection, Software Licensing, Software Virtualization在官网下载Enigma Virtual Box,同时,要把opencv_world480.dll,onnxruntime.dll,onnxruntime_xxx_shared.dll复制到刚刚到exe打包后的目录,然后在这个打包工具中添加这个目录,点击Browse添加exe等等之类的操作,具体可以看别的朋友的链接http://t.csdnimg.cn/HsZet,注意动态链接库导入就行,不然没有该环境变量的电脑打不开的。


点击全文阅读


本文链接:http://zhangshiyu.com/post/163681.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1