使用opencv的dnn模块来进行yolov5的目标检测

标签: YOLO dnn opencv yolov5 | 发表时间:2022-06-21 23:00 | 作者:迷途小书童
出处:https://xugaoxiang.com

环境

前言

yolov5C++ 部署方案中, opencv 应该是最能被想到的一种,从 3.3 版本后, opencv 就加入了 dnn 这个模块,有了这个模块,很多的机器学习项目就可以通过它来实现部署了,下面我们就来看看具体的实现步骤。

yolov5

由于 opencv 无法直接读取 yolov5 中的 pt 模型文件,因此,需要将原来的 pt 文件转换成 opencv 能直接读取的 onnx 模型文件

使用目前最新的版本 v6.1https://github.com/ultralytics/yolov5/releases/tag/v6.1,解压后,进入源码目录,下载对应的模型文件,这里使用 yolov5s.pt 为例,地址: https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5s.pt

接下来使用源码中的转换脚本 export.py,将 pt 文件转成 onnx 格式

  python export.py --weights yolov5s.pt --include onnx

完成后就可以在当前目录下生成了 yolov5s.onnx 文件

cmake

来到官网, https://cmake.org/download/,下载后,傻瓜式安装,然后,将其也加入到系统环境变量中

opencv

接下来,我们配置一下 opencv 的环境,来到官网 https://opencv.org/releases/,下载 windows 对应的版本,现在最新的版本是 4.5.5

下载完成后解压

然后新增一个环境变量 OpenCV_DIR,对应的值就是解压后的文件夹下的 build 目录,如下

修改系统环境变量 Path,将 opencv 包中的 build\x64\vc\15\bin 添加进去

开始部署

我们创建一个 C++ 项目,包含了源码文件 yolov5.cppCMakeLists.txt、目标名称文本文件 class.txtyolov5s.onnx 和若干测试图片

CMakeLists.txt 定义了编译的规则

  cmake_minimum_required(VERSION 3.20)
set(CMAKE_CXX_STANDARD 11)

project(yolov5   dnn)

find_package(OpenCV REQUIRED)

add_executable(yolov5   dnn yolov5.cpp)

include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(yolov5dnn ${OpenCV_LIBS} ${CMAKE_THREAD_LIBS_INIT})

class.txt 是待检测的目标,默认模型是 coco 的80个目标

最后来看源码文件 yolov5.cpp

  #include <opencv2/opencv.hpp>
#include <fstream>

using namespace cv;
using namespace std;
using namespace cv::dnn;

// 常量
const float INPUT_WIDTH = 640.0;
const float INPUT_HEIGHT = 640.0;
const float SCORE_THRESHOLD = 0.5;
const float NMS_THRESHOLD = 0.45;
const float CONFIDENCE_THRESHOLD = 0.45;

// 显示相关
const float FONT_SCALE = 0.7;
const int FONT_FACE = FONT_HERSHEY_SIMPLEX;
const int THICKNESS = 1;
Scalar BLACK = Scalar(0,0,0);
Scalar BLUE = Scalar(255, 178, 50);
Scalar YELLOW = Scalar(0, 255, 255);
Scalar RED = Scalar(0,0,255);

// 画框函数
void draw_label(Mat& input_image, string label, int left, int top)
{
    int baseLine;
    Size label_size = getTextSize(label, FONT_FACE, FONT_SCALE, THICKNESS, &baseLine);
    top = max(top, label_size.height);
    Point tlc = Point(left, top);
    Point brc = Point(left + label_size.width, top + label_size.height + baseLine);
    rectangle(input_image, tlc, brc, BLACK, FILLED);
    putText(input_image, label, Point(left, top + label_size.height), FONT_FACE, FONT_SCALE, YELLOW, THICKNESS);
}

// 预处理
vector<Mat> pre_process(Mat &input_image, Net &net)
{
    Mat blob;
    blobFromImage(input_image, blob, 1./255., Size(INPUT_WIDTH, INPUT_HEIGHT), Scalar(), true, false);

    net.setInput(blob);

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

    return outputs;
}

// 后处理
Mat post_process(Mat &input_image, vector<Mat> &outputs, const vector<string> &class_name) 
{
    vector<int> class_ids;
    vector<float> confidences;
    vector<Rect> boxes; 

    float x_factor = input_image.cols / INPUT_WIDTH;
    float y_factor = input_image.rows / INPUT_HEIGHT;

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

    const int dimensions = 85;
    const int rows = 25200;
    for (int i = 0; i < rows; ++i) 
    {
        float confidence = data[4];
        if (confidence >= CONFIDENCE_THRESHOLD) 
        {
            float * classes_scores = data + 5;
            Mat scores(1, class_name.size(), CV_32FC1, classes_scores);
            Point class_id;
            double max_class_score;
            minMaxLoc(scores, 0, &max_class_score, 0, &class_id);
            if (max_class_score > SCORE_THRESHOLD) 
            {
                confidences.push_back(confidence);
                class_ids.push_back(class_id.x);

                float cx = data[0];
                float cy = data[1];
                float w = data[2];
                float h = data[3];
                int left = int((cx - 0.5 * w) * x_factor);
                int top = int((cy - 0.5 * h) * y_factor);
                int width = int(w * x_factor);
                int height = int(h * y_factor);
                boxes.push_back(Rect(left, top, width, height));
            }

        }
        data += 85;
    }

    vector<int> indices;
    NMSBoxes(boxes, confidences, SCORE_THRESHOLD, NMS_THRESHOLD, indices);
    for (int i = 0; i < indices.size(); i++) 
    {
        int idx = indices[i];
        Rect box = boxes[idx];

        int left = box.x;
        int top = box.y;
        int width = box.width;
        int height = box.height;
        rectangle(input_image, Point(left, top), Point(left + width, top + height), BLUE, 3*THICKNESS);

        string label = format("%.2f", confidences[idx]);
        label = class_name[class_ids[idx]] + ":" + label;
        draw_label(input_image, label, left, top);
    }
    return input_image;
}

// 主函数
int main(int argc, char **argv)
{
    vector<string> class_list;
    ifstream ifs("class.txt");
    string line;

    while (getline(ifs, line))
    {
        class_list.push_back(line);
    }

    Mat frame;
    frame = imread(argv[1]);

    Net net;
    net = readNet("yolov5s.onnx");

    vector<Mat> detections;
    detections = pre_process(frame, net);

    Mat img = post_process(frame.clone(), detections, class_list);

    vector<double> layersTimes;
    double freq = getTickFrequency() / 1000;
    double t = net.getPerfProfile(layersTimes) / freq;
    string label = format("Inference time : %.2f ms", t);
    putText(img, label, Point(20, 40), FONT_FACE, FONT_SCALE, RED);

    imshow("Output", img);
    waitKey(0);

    return 0;
}

一切准备就绪,马上开始编译,进入到工程目录,打开 powershell,然后一次执行

  mkdir build
cmake -B build
cmake --build build --config Release

编译成功后,就可以找图片来测试了

  .\build\Release\yolov5dnn.exe bus.jpg

可以看到图片的检测时间花费了210毫秒,显然是没有用到 gpu,如果需要启用 gpu 加速的话,就需要重新编译 opencv,使能 cuda,这部分内容可以去参考 windows 编译 opencv,支持 cuda 加速ubuntu 下编译 opencv,支持 cuda 加速

相关 [opencv dnn 模块] 推荐:

使用opencv的dnn模块来进行yolov5的目标检测

- - YOLO – 迷途小书童的Note
yolov5 的 C++ 部署方案中,. opencv 应该是最能被想到的一种,从 3.3 版本后, opencv 就加入了. dnn 这个模块,有了这个模块,很多的机器学习项目就可以通过它来实现部署了,下面我们就来看看具体的实现步骤. 由于 opencv 无法直接读取. yolov5 中的 pt 模型文件,因此,需要将原来的 pt 文件转换成 opencv 能直接读取的 onnx 模型文件.

PyQt+OpenCV 录制保存、播放视频

- - 编程语言 - ITeye博客
准备这一两个月看看以前的书,另外学习下视频方面的东西(CV方面). 之前学过几天QT/PyQt,于是用PyQt结合OpenCV,做了一个小程序. 具有播放并保存摄像头捕获组成的视频,以及播放本地视频文件两个小功能. 界面很简单,没图没真相. 代码就100多行,很容易看懂. 最后感叹下OpenCV和python的强大,噢,还有QT/PyQt.

Windows平台上编译OpenCV的Android版本

- Pei - C++博客-首页原创精华区
Android NDK(r5b或更高版本). 在opencv\android\scripts\目录下,新建wincfg.cmd文件. 关于wincfg.cmd文件的配置,可以参考opencv\android\scripts\wincmd.cfg.tmpl文件. 编译完成即可在opencv\android\build目录下得到所有的静态库文件.

记录下用OpenCV做Demo时候用到的小代码

- Fenix - 丕子
Demo做完了,但是鲁棒性和泛化能力很差,是关于视频中人体检测跟踪以及计数的,对我这个没有接触过视频以及跟踪的人来说,10天搞定还真是困难很大. 主要就是先进行图像差将运动的前景减出来,然后只要在前景当中运行HOG的MultiScale方法检测行人,这样可以提高一部分效率,毕竟前景图都是原始图的子图.

在 Mac OS X 10.6 下编译安装高性能 OpenCV 库

- Wolf - Chun Tian (binghe)
最初由 Intel 开发的 OpenCV 库已经逐渐成为目前计算机视觉和图像识别领域的事实标准了,教科书里几乎所有的主流图形算法都在 OpenCV 里可以找到高效的实现. 为了完成公司安排给我的工作,这个库是我的必经之路,道理很简单:我不可能花费一个相关专业的硕士研究生的在校时间去把所有底层算法从头学习并实现一遍,我的时间顶多只有一两个月,因此重要的是如何用尽可能少的时间把整个工作中我最不擅长的部分用现成的开源软件来完成.

利用OpenCV的人脸检测给头像带上圣诞帽

- - CSDN博客综合推荐文章
采用一张圣诞帽的png图像作为素材,.     利用png图像背景是透明的,贴在背景图片上就是戴帽子的效果了. 人脸检测的目的主要是为了确定贴帽子的位置,类似ps中自由变换的功能,检测到人脸中间的位置,resize圣诞帽子和人脸大小匹配,确定位置,贴上去,ok. 代码:非常简洁,根据参考博客给出的代码,由OpenCV自带的人脸检测代码经过简单修改即可.

[译] OpenCV vs Dlib 人脸检测比较分析

- - IT瘾-dev
点击我爱计算机视觉标星,更快获取CVML新技术. 人脸检测是计算机视觉最典型的应用之一,早期OpenCV的logo就是Haar人脸检测的示意图. 很多人的第一个OpenCV学习目标就是跑通Haar级联人脸检测,Dlib库在业内开始流行很大程度上是因为其HOG-SVM人脸检测比OpenCV Haar的好,而近年来OpenCV和Dlib均已包含基于深度学习的人脸检测算法实现.

[OpenCV实战]5 基于深度学习的文本检测

- - IT瘾-geek
在这篇文章中,我们将逐字逐句地尝试找到图片中的单词. 基于最近的一篇论文进行文字检测. 应该注意,文本检测不同于文本识别. 在文本检测中,我们只检测文本周围的边界框. 但是,在文本识别中,我们实际上找到了框中所写的内容. 例如,在下面给出的图像中,文本检测将为您提供单词周围的边界框,文本识别将告诉您该框包含单词STOP.

Adrian小哥教程:如何使用Tesseract和OpenCV执行OCR和文本识别

- - 机器之心
近期,Adrian Rosebrock 发布一篇教程,介绍了如何使用 OpenCV、Python 和 Tesseract 执行文本检测和文本识别. 从安装软件和环境、项目流程、review 代码、实验结果,到展示局限、提出建议,这篇教程可以说十分详细了. 机器之心对该教程进行了摘要编译介绍. 本教程将介绍如何使用 OpenCV OCR.

【OpenCV入门教程之十二】OpenCV边缘检测:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑 - 【浅墨的游戏编程Blog】毛星云(浅墨)的专栏 - CSDN博客

- -
本篇文章中,我们将一起学习OpenCV中边缘检测的各种算子和滤波器——Canny算子,Sobel算子,Laplace算子以及Scharr滤波器. 文章中包含了五个浅墨为大家准备的详细注释的博文配套源代码. 在介绍四块知识点的时候分别一个,以及最后的综合示例中的一个. 依然是是放出一些程序运行截图吧:.