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

目录

一、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)

  1. github上下载项目ultralytics:https://github.com/ultralytics/ultralytics,可以选择git clone或者直接下载ZIP解压到本地。
  2. 本项目采用windows操作系统,使用CPU进行推理(默认操作),IDE为pycharm
  3. 由于目前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 model
model = 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 onnx
from onnxconverter_common import float16

model = 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_world480
else: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/include


DEPENDPATH  +=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_OBJECT
public:
    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,则置信度低的会被抑制,不显示
    int	keyPointsNum = 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,注意动态链接库导入就行,不然没有该环境变量的电脑打不开的。

  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值