QT----YOLOv5检测平台

news2025/1/13 14:18:07

目录

  • 1 opencv环境安装
    • 1.1 报错Could NOT find CUDNN (missing: CUDNN_LIBRARY CUDNN_INCLUDE_DIR) (Required is at least version "7.5")
    • 1.2 使用camke+vs编译opencv4.8.0
    • 1.3 报错'operator !=":重载函数具有类似的转换(编译源文件 H:\opencv-4.8.0\opencv-4.8.0kmodules\dnn\src\layers\normalize bbox layer.cpp)
  • 2 搭建界面
  • 3 按钮初始逻辑编写
    • 3.1 打开文件
      • 3.1.1 打开显示图片
      • 3.1.2 打开视频
    • 3.2 打开摄像头
    • 3.3 加载模型按钮
  • 4 加载YOLOv5
    • 4.1 初始化yolov5
    • 4.2 加载onnx模型
    • 4.3 图片模型推理
    • 4.4 视频推理和摄像头推理
    • 4.5 停止检测按钮
    • 4.6 必须先加载模型防止崩溃
    • 4.7 信息框显示最新数据
    • 4.8 使用线程优化

github连接,提交记录与章节对应方便代码查看
视频效果
Qt5.15.2 opencv4.5.2

1 opencv环境安装

直接查看QT人脸考勤那一章编译opencv,将头文件和lib文件导入

win32
{
    INCLUDEPATH += E:\Environment\opencv452\include\
                   E:\Environment\opencv452\include\opencv2
    LIBS += E:\Environment\opencv452\x64\mingw\lib\libopencv*
}

有gpu(深度学习环境自己配置过cuda的)需要修改其中的几个操作,开启cuda选项,点击执行cmake
file

发现cmake报错,查询发现是cuda11.7版本和opencv4.5.2版本不匹配,4.5.2支持cuda11.2,尝试换成opencv4.8.0还是报错不知道怎么解决,放弃了就用cpu吧
file

执行完后会出现新的选项,查看显卡算力查看算力,修改CUDA_ARCH_BIN我是6.1
file

1.1 报错Could NOT find CUDNN (missing: CUDNN_LIBRARY CUDNN_INCLUDE_DIR) (Required is at least version “7.5”)

(做了这么一大堆操作发现都没啥用,qt把构建版本改为MSVC就行,MinGW死活找不到,但是又出现了别的报错)发现cmake报错, Could NOT find CUDNN (missing: CUDNN_LIBRARY CUDNN_INCLUDE_DIR) (Required is at least version “7.5”),
缺少cudnn的环境变量,还发现自己的cuda版本和tookit不匹配,cuda12.2,tookit11.7,索性重装了(在cmake里可以找到,但是在qtcreater里找不到,绝了)
file
把原来的11.7 卸载,下载12.2链接安装,查看系统变量里是否有,没有手动添加
file

去下载cudnn官网下载我下载了8.8.1,cuda12.2
解压完成后将三个文件夹复制到你的cuda路径
file
打开系统变量path,添加上这些路径,删除之前版本的路径,重启电脑,重构项目
file

进入终端运行以下指令,路径自己更改,最后显示pass,则说明安装上了
cd C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.2\extras\demo_suite
deviceQuery.exe
bandwidthTest.exe
file

1.2 使用camke+vs编译opencv4.8.0

选择路径,点击config
file
依旧是选择这些点击configigure,
file
file
取消勾选这三个test,face,xfeatures2d,wechat_qrcode,取消java,pyhton的所有勾选
file

configure完成后在点击generate,点击openProject会打开VS
file
修改为release,将install修改为启动项,右击生成
file

1.3 报错’operator !=":重载函数具有类似的转换(编译源文件 H:\opencv-4.8.0\opencv-4.8.0kmodules\dnn\src\layers\normalize bbox layer.cpp)

双击报错,修改源码,添加上static_cast<T>
file
file
历经两个小时编译成功
file
将有.dll文件的路径添加到系统变量path里
file

2 搭建界面

按照下图搭建界面
file

3 按钮初始逻辑编写

3.1 打开文件

点击按钮打开文件对话框,选择文件后通过filename保存文件路径,并且把视频和图片显示到lb_show上

void MainWindow::on_btn_openfile_clicked()
{
    QString filename = QFileDialog::getOpenFileName(this,QStringLiteral("打开文件"),".","*.mp4 *.avi *.png *.jpe *.jpeg *.bmp");
    if(!QFile::exists(filename))
    {
        return;
    }
    ui->te_message->setText(filename);
}

使用QMimeDatabase和QMimeType来确定文件的MIME类型,以判断是图片文件还是视频文件,方便我们使用opencv进行操作

QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(filename);
qDebug()<<mime.name();

file

3.1.1 打开显示图片

对于不同通道的图片需要使用opencv进行转换

// 如果文件是图片类型
    if(mime.name().startsWith("image/"))
    {
        // 使用OpenCV读取图片
        cv::Mat src = cv::imread(filename.toStdString());
        // 检查图片是否正确读取,即图片数据是否非空
        if(src.empty())
        {
            ui->lb_show->setText("图像不存在"); // 如果图片不存在,显示错误消息
            return;
        }
        cv::Mat temp; // 创建一个临时Mat对象用于存放转换后的图片数据

        // 根据图片的通道数进行相应的颜色空间转换
        if(src.channels() == 4)
        {
            // 如果图片是4通道的(如带透明度的PNG),则转换为RGB
            cv::cvtColor(src, temp, cv::COLOR_BGRA2RGB);
        }
        else if(src.channels() == 3)
        {
            // 如果图片是3通道的(如JPG),则转换为RGB
            cv::cvtColor(src, temp, cv::COLOR_BGR2RGB);
        }
        else
        {
            // 如果图片是2通道的或其他情况,这里的注释应该为处理单通道灰度图,将其转换为RGB
            cv::cvtColor(src, temp, cv::COLOR_GRAY2RGB);
        }
        // 将OpenCV的Mat数据转换为QImage对象
        QImage img = QImage(temp.data, temp.cols, temp.rows, temp.step, QImage::Format_RGB888);
        // 将QImage对象转换为QPixmap对象,并根据标签的高度调整图片大小
        QPixmap mmp = QPixmap::fromImage(img);
        mmp = mmp.scaledToHeight(ui->lb_show->height());
        // 将调整后的图片显示在标签上
        ui->lb_show->setPixmap(mmp);
    }

3.1.2 打开视频

使用open打开视频,但是一打开时评程序就崩溃了,发现是指针为分配对象,没有初始化只有cv::VideoCapture *capture;,但是没有capture = new cv::VideoCapture;,这个指针一开始指向任意地址。这样做可以确保指针指向一个有效的内存地址,且该地址上存放的是一个cv::VideoCapture对象。

// 设置文件类型为视频
        filetype = "video";
        // 使用OpenCV打开视频文件
        capture->open(filename.toStdString());
        // 如果无法打开视频文件,则在文本编辑区显示错误消息并返回
        if(!capture->isOpened())
        {
            ui->te_message->append("mp4文件打开失败!");
            return;
        }

        // 获取视频的总帧数,宽度,高度,显示信息
        long totalFrame = capture->get(cv::CAP_PROP_FRAME_COUNT);
        int width = capture->get(cv::CAP_PROP_FRAME_WIDTH);
        int height = capture->get(cv::CAP_PROP_FRAME_HEIGHT);
        ui->te_message->append(QString("整个视频共 %1 帧, 宽=%2 高=%3 ").arg(totalFrame).arg(width).arg(height));

        // 设置要从视频的开始帧读取
        long frameToStart = 0;
        capture->set(cv::CAP_PROP_POS_FRAMES, frameToStart);
        ui->te_message->append(QString("从第 %1 帧开始读").arg(frameToStart));

        // 获取视频的帧率
        double frameRate = capture->get(cv::CAP_PROP_FPS);
        ui->te_message->append(QString("帧率为: %1 ").arg(frameRate));

        // 读取视频的第一帧
        cv::Mat frame;
        capture->read(frame);
        // 将帧的颜色空间从BGR转换为RGB
        cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
        // 将OpenCV的Mat对象转换为QImage对象
        QImage videoimg = QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
        // 将QImage对象转换为QPixmap对象
        QPixmap mmp = QPixmap::fromImage(videoimg);
        // 将QPixmap对象按照标签的高度进行缩放
        mmp = mmp.scaledToHeight(ui->lb_show->height());
        // 在标签上显示这个QPixmap对象
        ui->lb_show->setPixmap(mmp);

对于播放视频我们需要定时器和槽函数,将更新帧的槽函数和定时器建立连接,定时器开始时,播放视频

void MainWindow::updateFrame()
{
    cv::Mat frame;
    if(capture->read(frame))
    {
        //读取帧成功
        cv::cvtColor(frame,frame,cv::COLOR_BGR2RGB);
        QImage videoimg = QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
        QPixmap mmp = QPixmap::fromImage(videoimg);
        mmp = mmp.scaledToHeight(ui->lb_show->height());
        ui->lb_show->setPixmap(mmp);
    }
}


void MainWindow::on_btn_startdetect_clicked()
{
    if(filetype == "pic")
    {
        //对图像进行识别
    }

    else if(filetype == "video")
    {
        //对视频进行识别
        double frameRate = capture->get(cv::CAP_PROP_FPS);
        timer->start(1000/frameRate); // 根据帧率开始播放
    }
    else
    {
        //对摄像头进行识别
    }

}

3.2 打开摄像头

点击打开摄像头,按钮变为关闭摄像头,同时启动定时器,启动更新帧的槽函数(使用ifelse来判断是视频还是相机)

void MainWindow::on_btn_camera_clicked()
{
    filetype = "camera";
    if(ui->btn_camera->text() == "打开摄像头")
    {
        ui->btn_camera->setText("关闭摄像头");
        capture->open(0);
        timer->start(100);
    }
    else
    {
        ui->btn_camera->setText("打开摄像头");
        capture->release();
        timer->stop();
        ui->lb_show->clear();
    }
}

使用同一个updateFrame函数来处理更新视频帧和更新摄像头的数据,使用filetype来判断,将摄像头读取的每帧数据进行处理展示

else if(filetype == "camera")
    {
        cv::Mat src;
        if(capture->isOpened())
        {
            //将摄像头数据放入src
            *capture >> src;
            if(src.data == nullptr) return; // 如果图像数据为空,则返回
        }

        //将图像转换为qt能够处理的格式
        cv::Mat frame;
        cv::cvtColor(src,frame,cv::COLOR_BGR2RGB);
        cv::flip(frame,frame,1);

        QImage videoimg = QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
        QPixmap mmp = QPixmap::fromImage(videoimg);
        mmp = mmp.scaledToHeight(ui->lb_show->height());  //设置图像的缩放比例
        ui->lb_show->setPixmap(mmp);
    }

3.3 加载模型按钮

就弹出一个文件对话框选择模型就行

void MainWindow::on_btn_loadmodel_clicked()
{
    // 使用文件系统弹出对话框获得用户选择的文件路径
    QString filename = QFileDialog::getOpenFileName(this, QStringLiteral("打开文件"), ".", "*.onnx");
    // 检查文件是否存在
    if(!QFile::exists(filename))
    {
        return; // 如果文件不存在,则直接返回
    }
    // 在文本编辑框中显示选中的文件路径
    ui->te_message->setText(filename);
}

4 加载YOLOv5

没有找到好的教程只能抄了,边抄便理解。

4.1 初始化yolov5

confThreshold(类别置信度阈值):用于过滤那些分类置信度低于此阈值的检测结果。
nmsThreshold(非最大抑制阈值):在执行非最大抑制(一种去除重叠检测框的方法)时使用的阈值。
objThreshold(目标置信度阈值):用于过滤那些目标置信度(即,对象存在的置信度)低于此阈值的检测结果。
netname(网络名称):指定使用的网络模型的名称。

struct NetConfig
{
    float confThreshold; // 类别置信度阈值
    float nmsThreshold;  // 非最大抑制(NMS)阈值
    float objThreshold;  // 目标置信度阈值
    std::string netname; // 网络名称
};

新建一些私有变量来初始化模型

   float confThreshold; // 类别置信度阈值
    float nmsThreshold;  // 非最大抑制(NMS)阈值
    float objThreshold;  // 目标置信度阈值
    // 定义锚点尺寸,每个尺寸组对应不同尺度的特征图
    const float anchors[3][6] = {
        {10.0, 13.0, 16.0, 30.0, 33.0, 23.0},
        {30.0, 61.0, 62.0, 45.0, 59.0, 119.0},
        {116.0, 90.0, 156.0, 198.0, 373.0, 326.0}
    };
    // 定义特征图的步长
    const float stride[3] = {8.0, 16.0, 32.0};
    // 定义可能检测到的类别
    std::string classes[80] = {
        "person", "bicycle", "car", "motorbike", "aeroplane", "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", "sofa", "pottedplant",
        "bed", "diningtable", "toilet", "tvmonitor", "laptop", "mouse",
        "remote", "keyboard", "cell phone", "microwave", "oven", "toaster",
        "sink", "refrigerator", "book", "clock", "vase", "scissors",
        "teddy bear", "hair drier", "toothbrush"
    };
    // 输入图像的宽和高
    const int inpWidth = 640;
    const int inpHeight = 640;

    // 用于存储网络输出的向量
    std::vector<cv::Mat> outs;
    // 检测到的类别ID
    std::vector<int> classIds;
    // 检测到的类别的置信度
    std::vector<float> confidences;
    // 检测到的对象的边界框
    std::vector<cv::Rect> boxes;
    // 用于非最大抑制后保留的检测框的索引
    std::vector<int> indices;
    // 神经网络
    cv::dnn::Net net;


// YOLOv5对象的初始化函数
void YOLOv5::Init(NetConfig config)
{
    // 设置类别置信度阈值
    this->confThreshold = config.confThreshold;
    // 设置非最大抑制阈值
    this->nmsThreshold = config.nmsThreshold;
    // 设置目标置信度阈值
    this->objThreshold = config.objThreshold;

    // 预分配内存以优化性能,以下是基于经验的估计值
    classIds.reserve(20);     // 预留空间用于存储检测到的类别ID
    confidences.reserve(20);  // 预留空间用于存储检测到的类别的置信度
    boxes.reserve(20);        // 预留空间用于存储检测到的边界框
    outs.reserve(3);          // 预留空间用于存储网络的输出
    indices.reserve(20);      // 预留空间用于存储非最大抑制后保留的检测框的索引
}


4.2 加载onnx模型

新建函数loadmodel,加载onnx模型,如果有gpu就是用gpu没有就使用cpu

// 加载ONNX模型文件
bool YOLOv5::loadModel(QString onnxfile)
{
    try
    {
        // 使用OpenCV从ONNX文件读取网络模型
        this->net = cv::dnn::readNetFromONNX(onnxfile.toStdString());

        // 检查是否有可用的CUDA设备(即检查是否可以使用GPU进行加速)
        int deviceID = cv::cuda::getCudaEnabledDeviceCount();
        if(deviceID == 1)
        {
            // 如果有可用的CUDA设备,将网络的推理后端设置为CUDA以使用GPU
            this->net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
            this->net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
        }
        else
        {
            // 如果没有检测到CUDA设备,则弹出消息框提示用户当前使用CPU进行推理
            QMessageBox::information(NULL, "warning", QStringLiteral("正在使用CPU推理!\n"), QMessageBox::Yes, QMessageBox::Yes);
        }
        return true; // 模型加载成功,返回true
    }
    catch(std::exception& e)
    {
        // 如果在加载模型过程中发生异常,弹出消息框提示错误信息,并返回false
        QMessageBox::critical(NULL, "Error", QStringLiteral("模型加载出错,请检查重试!\n %1").arg(e.what()), QMessageBox::Yes, QMessageBox::Yes);
        return false;
    }
}

测试一下加载模型,成功加载cpu模型

if(!yolov5->loadModel(onnxfile))
    {
        ui->te_message->append("加载模型失败!");
        return;
    }
    else
    {
         ui->te_message->append("加载模型成功!");
    }

4.3 图片模型推理

推理代码直接抄了

// YOLOv5目标检测函数
void YOLOv5::detect(cv::Mat &frame)
{
    // 将输入图像转换为神经网络的blob格式,并进行归一化和大小调整
    cv::dnn::blobFromImage(frame, blob, 1 / 255.0, cv::Size(this->inpWidth, this->inpHeight), cv::Scalar(0, 0, 0), true);
    // 将blob设置为网络的输入
    this->net.setInput(blob);
    // 运行前向传播,得到网络输出
    this->net.forward(outs, this->net.getUnconnectedOutLayersNames());

    // 清除之前的检测结果
    classIds.clear();
    confidences.clear();
    boxes.clear();
    // 计算从模型输入尺寸到原始图像尺寸的缩放比例
    float ratioh = (float)frame.rows / this->inpHeight, ratiow = (float)frame.cols / this->inpWidth;
    int n = 0, q = 0, i = 0, j = 0, nout = 8 + 5, c = 0;
    for (n = 0; n < 3; n++)   ///尺度
    {
        // 计算特征图的网格数量
        int num_grid_x = (int)(this->inpWidth / this->stride[n]);
        int num_grid_y = (int)(this->inpHeight / this->stride[n]);
        int area = num_grid_x * num_grid_y; // 网格总数
        // 对网络输出进行sigmoid处理
        this->sigmoid(&outs[n], 3 * nout * area);
        for (q = 0; q < 3; q++)    ///anchor数
        {
            // 获取当前尺度下的锚框宽度和高度
            const float anchor_w = this->anchors[n][q * 2];
            const float anchor_h = this->anchors[n][q * 2 + 1];
            float* pdata = (float*)outs[n].data + q * nout * area; // 当前锚框的网络输出
            for (i = 0; i < num_grid_y; i++) // 遍历网格y方向
            {
                for (j = 0; j < num_grid_x; j++) // 遍历网格x方向
                {
                    // 获取当前格子的置信度
                    float box_score = pdata[4 * area + i * num_grid_x + j];
                    if (box_score > this->objThreshold) // 如果置信度大于阈值
                    {
                        float max_class_socre = 0, class_socre = 0;
                        int max_class_id = 0;
                        for (c = 0; c < 80; c++)  get max socre
                        {
                            // 获取类别置信度
                            class_socre = pdata[(c + 5) * area + i * num_grid_x + j];
                            if (class_socre > max_class_socre)
                            {
                                max_class_socre = class_socre;
                                max_class_id = c; // 记录最大类别置信度及其索引
                            }
                        }

                        if (max_class_socre > this->confThreshold) // 如果类别置信度大于阈值
                        {
                            // 计算检测框的中心坐标、宽度和高度
                            float cx = (pdata[i * num_grid_x + j] * 2.f - 0.5f + j) * this->stride[n];  ///cx
                            float cy = (pdata[area + i * num_grid_x + j] * 2.f - 0.5f + i) * this->stride[n];   ///cy
                            float w = powf(pdata[2 * area + i * num_grid_x + j] * 2.f, 2.f) * anchor_w;   ///w
                            float h = powf(pdata[3 * area + i * num_grid_x + j] * 2.f, 2.f) * anchor_h;  ///h

                            // 将检测框的坐标还原到原图上
                            int left = (cx - 0.5*w)*ratiow;
                            int top = (cy - 0.5*h)*ratioh;   ///坐标还原到原图上

                            // 将检测结果保存到相应的容器中
                            classIds.push_back(max_class_id);
                            confidences.push_back(max_class_socre);
                            boxes.push_back(Rect(left, top, (int)(w*ratiow), (int)(h*ratioh)));
                        }
                    }
                }
            }
        }
    }

    indices.clear();
    cv::dnn::NMSBoxes(boxes, confidences, this->confThreshold, this->nmsThreshold, indices);
    for (size_t i = 0; i < indices.size(); ++i)
    {
        int idx = indices[i];
        Rect box = boxes[idx];
        // 绘制预测框及其类别和置信度
        this->drawPred(classIds[idx], confidences[idx], box.x, box.y,
                       box.x + box.width, box.y + box.height, frame);
    }
}
void YOLOv5::drawPred(int classId, float conf, int left, int top, int right, int bottom, cv::Mat &frame)
{
    // 绘制检测框
    rectangle(frame, Point(left, top), Point(right, bottom), Scalar(0, 0, 255), 3);

    // 构建标签,包含类别名称和置信度
    string label = format("%.2f", conf);
    label = this->classes[classId] + ":" + label;

    int baseLine;
    // 获取标签尺寸
    Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
    top = max(top, labelSize.height);
    // 绘制标签
    putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 255, 0), 1);
}

void YOLOv5::sigmoid(cv::Mat *out, int length)
{
    float* pdata = (float*)(out->data);
    int i = 0;
    // 对网络输出进行sigmoid处理
    for (i = 0; i < length; i++)
    {
        pdata[i] = 1.0 / (1 + expf(-pdata[i]));
    }
}

在读取图片后装换位RBG后进行检测yolov5->detect(temp),人太大了好像就不检测了。
file

file

4.4 视频推理和摄像头推理

视频推理需要用到启动推理的按钮,同样也是在读取的帧转换为RBG后启动推理,并且使用canDetect来判断是否开启检测,一直判断好像确实比较消耗资源
file
在点击开始检测时把canDetect设置为true,同时把其他按钮屏蔽。

void MainWindow::on_btn_startdetect_clicked()
{
    //开始检测时封锁其他按钮
    ui->btn_startdetect->setEnabled(false);
    ui->btn_stopdetect->setEnabled(true);
    ui->btn_openfile->setEnabled(false);
    ui->btn_loadmodel->setEnabled(false);
    ui->btn_camera->setEnabled(false);
    ui->comboBox->setEnabled(false);
    ui->te_message->append(QStringLiteral("=======================\n"
                                           "        开始检测\n"
                                           "=======================\n"));
    if(filetype == "pic")
    {
        //对图像进行识别
    }
    else if(filetype == "video")
    {
        //对视频进行识别
        canDetect = true;
        double frameRate = capture->get(cv::CAP_PROP_FPS);
        timer->start(1000/frameRate); // 根据帧率开始播放

    }
    else
    {
        canDetect = true;
        //对摄像头进行识别
    }

}

效果很卡,还需要优化,1秒多才能处理一帧
file

4.5 停止检测按钮

停止按钮很简单,把其他按钮功能打开,停止计时器

void MainWindow::on_btn_stopdetect_clicked()
{
    //开始检测时封锁其他按钮
    ui->btn_startdetect->setEnabled(true);
    ui->btn_stopdetect->setEnabled(false);
    ui->btn_openfile->setEnabled(true);
    ui->btn_loadmodel->setEnabled(true);
    ui->btn_camera->setEnabled(true);
    ui->comboBox->setEnabled(true);
    timer->stop();
    ui->te_message->append(QString( "======================\n"
                                    "        停止检测\n"
                                    "======================\n"));
    canDetect = false;
}

4.6 必须先加载模型防止崩溃

如果不加载模型直接开始检测,程序会崩溃,所以需要在设置一个变量is_loadedmodel,加载模型时,在其他按钮的开始都添加判断是否加载模型的代码

 if(!is_loadedmodel)
    {
        QMessageBox::information(nullptr,"错误","请先加载模型!");
        return;
    }

4.7 信息框显示最新数据

要确保te_message控件始终显示最新的数据,你可以使用QTextEdit的moveCursor方法,使得每次向te_message添加新内容后,视图自动滚动到底部。在输出消息后,调用这个方法:
ui->te_message->moveCursor(QTextCursor::End);

4.8 使用线程优化

首先让yolov5成为Qobject的对象才能使用线程,修改头文件和构造函数

class YOLOv5 : public QObject {
    Q_OBJECT  // 使得类支持Qt信号和槽机制
public:
    explicit YOLOv5(QObject *parent = nullptr);


YOLOv5::YOLOv5(QObject *parent)
    : QObject{parent}
{}

现在捋一下逻辑,我们在构造函数里创建线程,把yolov5的对象放入线程,但是在线程中不能直接调用对象的函数,需要用信号来触发,所以在需要检测的时候我们把信号和检测的图片同时发送出去,在mainwindow的头文件里定义,同时将yolov5->detect都替换成这个信号发送

signals:
    void sendFrame(cv::Mat &frame);

//发送检测信号
        emit sendFrame(temp);

把yolov5的detect函数定义为槽函数

public slots:
    void detect(cv::Mat& frame);

在到mainwindow的构造函数里添加上线程和信号连接,这样在发送帧后就会自动调用检测函数

//使用线程优化
    QThread *thread = new QThread();
    //把yolov5放入线程
    yolov5->moveToThread(thread);
    thread->start();
    connect(this,&MainWindow::sendFrame,yolov5,&YOLOv5::detect);

运行后报错了,数据类型没有被识别,在main函数里添加上这一句qRegisterMetaType<cv::Mat>("cv::Mat&");
file
再次运行,发现没有有检测框,但是两个函数都调用了,且id也能检测出来,那就是两个函数都运行了,但是绘制的图像不是原图,应该是没有传地址过去,同一个数据处理应该在同一个线程上,可能还需要将绘制函数也作为信号和槽函数。
file
添加信号,槽函数,在mainwindow的构造函数里在添加一个连接

signals:
    void senddraw(int classId, float conf, int left, int top, int right, int bottom, cv::Mat& frame);
public slots:
    void detect(cv::Mat& frame);
    void drawPred(int classId, float conf, int left, int top, int right, int bottom, cv::Mat& frame);


//发送绘制信号
    connect(yolov5,&YOLOv5::senddraw,yolov5,&YOLOv5::drawPred);

发现还是不行,信号只管发送,不会等待返回,所以得当检测绘制执行完毕,发送信号,绘制到界面上。先设置绘制框完成的信号
file

把绘制图片的代码封装一下,绑定一下

connect(yolov5,&YOLOv5::drawEnd,this,&MainWindow::drawRectPic);

void MainWindow::drawRectPic(cv::Mat &frame)
{
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double, std::milli> elapsed = end - start;
    ui->te_message->append(QString("cost_time: %1 ms").arg(elapsed.count()));
    ui->te_message->moveCursor(QTextCursor::End); //确保显示最新信息
    //显示图片
    QImage img = QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
    QPixmap mmp = QPixmap::fromImage(img);
    mmp = mmp.scaledToHeight(ui->lb_show->height());  //设置图像的缩放比例
    ui->lb_show->setPixmap(mmp);
}

又发现了新的问题,只会显示识别的帧,这是还需要一个信号,indices里边代码检测框的数量,0的时候就是没有检测到,此时发出信号,绘制没有检测到框的图片。发现优化和没优化一个速度,逻辑还是有点问题,没有异步起来,只有上一个动作执行完才能进行下一个。

 if(indices.size() == 0)
    {
        emit detectEnd(frame);
    }

点击访问博客查看更多内容

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

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

相关文章

PAC性能开销权衡及优化措施

PAC性能开销&#xff1f;如何进行优化&#xff1f;本博客探讨这些问题。

[StartingPoint][Tier0]Mongod

Task 1 How many TCP ports are open on the machine? (机器上打开了多少个 TCP 端口&#xff1f;) Example: $ sudo nmap -sS -T4 10.129.222.112 -p 27017,22 2 Task 2 Which service is running on port 27017 of the remote host? (哪个服务正在远程主机的端口 270…

NASA数据集——1980 年至 2020 年北美 3km分辨率气温(摄氏度)、相对湿度(%)、风速(米/秒)、风向(真北偏角)、总降水量(雨+雪)等数据集

Daily SnowModel Outputs Covering the ABoVE Core Domain, 3-km Resolution, 1980-2020 简介 文件修订日期&#xff1a;2023-01-27 数据集版本: 1 摘要 该数据集提供了 1980 年 9 月 1 日至 2020 年 8 月 31 日期间 3 千米网格上的 SnowModel 每日模拟输出&#xff0c;涵…

Java快速入门系列-3(Java基础)

第三章&#xff1a;Java基础 3.1 Java语法基础3.1.1 Java程序入口点&#xff1a;main方法3.1.2 注释3.1.3 变量声明与赋值3.1.4 数据类型3.1.5 标识符与关键字 3.2 数据类型与变量3.2.1 基本数据类型3.2.2 引用数据类型 3.3 控制流程3.3.1 条件语句3.3.2 循环结构 3.4 数组与集…

mbti,ESTP型人格的心理问题分析

什么是ESTP型人格 ESTP分别代表外向&#xff0c;实感&#xff0c;理智&#xff0c;依赖&#xff0c;而ESTP型人格则是一种性格上十分激进&#xff0c;喜欢冒险&#xff0c;并且总是因为情绪起伏过大&#xff0c;而一下子做出应激行为的相对冒险的人格。具有ESTP型人格的人一般…

页面刚加载的时候显示自己定义的{{***}}然后一闪而过

这时候别用插值表达式语法了&#xff0c;直接用v-text或者v-html就能解决这个问题 但是有个问题&#xff0c;如下图所示&#xff1a; 具体bind使用方式&#xff0c;如下图所示&#xff1a; 但是v-bind也可以进行简写&#xff0c;就是去掉v-bind&#xff0c;直接写&#xff1a…

提高空调压缩机能效的通用方法

压缩机的能效提高主要依靠技术改进而不是大幅度增加材料的消耗&#xff0c;这也是技术经济性最好的节能手段。 1、改进电机效率&#xff0c;电机效率的提高意味着压缩机电效率的提高和压缩机总体效率的提高&#xff1b; 1.1、降低定子铜耗 降低定子绕组中电流通过所产生的铜耗…

前端路径问题总结

1.相对路径 不以/开头 以当前资源的所在路径为出发点去找目标资源 语法: ./表示当前资源的路径 ../表示当前资源的上一层路径 缺点:不同位置,相对路径写法不同2.绝对路径 以固定的路径作为出发点作为目标资源,和当前资源所在路径没关系 语法:以/开头,不同的项目中,固定的路径…

Java零基础入门-java8新特性(完结篇)

一、概述 ​上几期&#xff0c;我们是完整的学完了java异常类的学习及实战演示、以及学习了线程进程等基础概念&#xff0c;而这一期&#xff0c;我们要来玩点好的东西&#xff0c;那就是java8&#xff0c;我们都知道java8是自2004年发布java5之后最重要且一次重大的版本更新&a…

4月4号总结

java学习 一.接口 1.介绍 定义接口需要使用到关键字interface去定义接口。 格式如下&#xff1a; 类与接口的关系不是继承&#xff0c;而是实现&#xff0c;用关键字 implements &#xff0c;格式如下&#xff1a; 这个类去实现接口&#xff0c;其中的关系就相当于&#xf…

Hadoop-Yarn

一、Yarn资源调度器 思考&#xff1a; 1&#xff09;如何管理集群资源&#xff1f; 2&#xff09;如何给任务合理分配资源&#xff1f; Yarn 是一个资源调度平台&#xff0c;负责为运算程序提供服务器运算资源&#xff0c;相当于一个分布式的操作系统平台。 而 MapReduce …

『python爬虫』巨量http代理使用 每天白嫖1000ip(保姆级图文)

目录 注册 实名得到API链接和账密 Python3requests调用Scpay总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 注册 实名 注册巨量http 用户概览中领取1000ip,在动态代理中使用.用来测试一下还是不错的 得到AP…

可视化大屏 - 项目1

文章目录 技术栈echarts 可视化需求分析代码实现 技术栈 flexible.js rem 实现不同终端下的响应式布局&#xff0c;根据不同屏幕宽度&#xff0c;自适配布局&#xff1b; html中引入index.js&#xff0c;可以改名为flexible.js&#xff1b;默认划分10份&#xff0c;可以自己修…

蓝桥杯 --- 日期问题模板

目录 1.如何判断闰年 2.如何遍历当前年份的每一天 3.如果想要输出某一年某一天到某一年某一天之间一共有多少天。 4.精确到具体周几到周几的问题分析 5.如何直接通过一层for循环枚举年月日 习题&#xff1a; 蓝桥杯竞赛特别喜欢考日期问题&#xff0c;今天给大家分享一下…

Linux云计算之网络基础8——IPV6和常用网络服务

目录 一、IPV6基础 IPV6详解 IPv6数据报的基本首部 IPv6数据报的扩展首部 IPv6地址的表示方法 IPv6地址分类 网际控制报文协议ICMPv6 二、cisco基于IPV6的配置 cisco基于IPV6的配置步骤 模拟配置 三、HTML基础介绍 文档的结构 动手操作一下 四、常用网络服务介绍…

基于单片机的测时仪系统设计

**单片机设计介绍&#xff0c;基于单片机的测时仪系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的测时仪系统设计是一个结合了单片机技术与测时技术的综合性项目。该设计的目标是创建一款精度高、稳定性强且…

软考109-上午题-【计算机网络】-网络设备

一、网络设备 1-1、物理层的互联设备 物理层的设备&#xff1a;中继器、集线器 1、中继器 中继器&#xff0c;可以使得两个链路在物理层上互联。 可以使得信号再生&#xff0c;信号增强。因此&#xff0c;中继器使得接受用户&#xff0c;收到衰减很小的原始信号 2、集线器&a…

55555555555555

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

【二】Django小白三板斧

今日内容 静态文件配置 request对象方法初识 pycharm链接数据库&#xff08;MySQL&#xff09; django链接数据库&#xff08;MySQL&#xff09; Django ORM简介 利用ORM实现数据的增删查改 【一】Django小白三板斧 HttpResponse 返回字符串类型的数据 render 返回HTML文…

2012年认证杯SPSSPRO杯数学建模D题(第一阶段)人机游戏中的数学模型全过程文档及程序

2012年认证杯SPSSPRO杯数学建模 减缓热岛效应 D题 人机游戏中的数学模型 原题再现&#xff1a; 计算机游戏在社会和生活中享有特殊地位。游戏设计者主要考虑易学性、趣味性和界面友好性。趣味性是本质吸引力&#xff0c;使玩游戏者百玩不厌。网络游戏一般考虑如何搭建安全可…