OpenCV实战——使用YOLO进行目标检测

news2024/12/24 3:09:13

OpenCV实战——使用YOLO进行目标检测

    • 0. 前言
    • 1. YOLO 模型简介
    • 2. 基于 YOLO 实现目标检测
    • 3. 完整代码
    • 相关链接

0. 前言

在本节中,我们将使用 YOLO 算法执行目标检测。目标检测是计算机视觉中的一项常见任务,借助深度学习技术,我们可以实现高准确度的检测。YOLOCOCO 数据集(数据集中包含 80 个类别和超过 300000 张图像)中可以达到 60.6mAP (20 fps) 或 33mAP (220 fps)。

1. YOLO 模型简介

YOLO 是深度学习网络目标检测的一类重要分枝,其将输入图像划分为 SxS 网格。对于每个网格,YOLO 检查 B 个边界框,然后深度学习模型提取每个网格的边界框、包含可能对象的置信度以及每个边界框中(训练数据集中)每个类别的置信度:

YOLO 网格
YOLO 使用 19x19 个网格,每个网格包含 5 个边界框,训练数据集中包含 80 个类别。网络的输出结果为 19x19x425,其中 425 来自边界框 (x,y,width,height)、边界框中是否包含对象的置信度、对象属于每个类别(共 80 个类别)的置信度:

5_bounding box*(x,y,w,h,object_confidence,classify_confidence[80])=5*(4 + 1 + 80)

YOLO 架构基于 DarkNet (包含 53 层网络),YOLODarkNet 的基础上增加了 53 层网络,共 106 层网络。如果我们需要预测速度更快的架构,可以使用包含较少网络层 TinyYOLO 架构。

2. 基于 YOLO 实现目标检测

在本节中,我们使用与深度学习简介一节相同的函数和类来加载模型、预处理图像和预测结果,同时介绍非极大值抑制 (non-maximum suppression, NMS),以及绘制带有标签的预测结果:

(1) 创建 object_detection_yolo.cpp 文件,导入所需的头文件,初始化所需的全局变量:

#include <fstream>
#include <sstream>
#include <iostream>

#include <opencv2/core.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

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

// Initialize the parameters
float confThreshold = 0.5; // Confidence threshold
float nmsThreshold = 0.4;  // Non-maximum suppression threshold
int inpWidth = 416;  // Width of network's input image
int inpHeight = 416; // Height of network's input image
vector<string> classes;

(2) 我们从 main 函数开始,首先读取存储模型可以预测的所有类别的文件:

int main(int argc, char** argv) {
    // 加载类别名
    string classesFile = "data/coco.names";
    ifstream ifs(classesFile.c_str());
    string line;
    while (getline(ifs, line)) classes.push_back(line);

(3) 使用模型定义和权重文件加载模型:

    // 提供模型的配置和权重文件
    String modelConfiguration = "data/yolov3.cfg";
    String modelWeights = "data/yolov3.weights";
    // 加载网络
    Net net = readNetFromDarknet(modelConfiguration, modelWeights);

(4) 加载图像并将其转换为 blob

    Mat input, blob;
    input= imread(argv[1]);
    if (input.empty()) {
        cout << "No input image" << endl;
        return 0;
    }
    // 创建输入
    blobFromImage(input, blob, 1/255.0, Size(inpWidth, inpHeight), Scalar(0,0,0), true, false);

(5) 使用 setInputforward 函数检测所有对象及其类别:

    // 设定网络输入
    net.setInput(blob);
    // 执行前向传播
    vector<Mat> outs;
    net.forward(outs, getOutputsNames(net));

(6) 对输出结果进行后处理,绘制检测到的目标及预测置信度:

    // 移除低置信度边界框
    postprocess(input, outs);

(7)postprocess 函数中,存储所有预测置信度高于 confThreshold 的边界框框:

    vector<int> classIds;
    vector<float> confidences;
    vector<Rect> boxes;
    for (size_t i = 0; i < outs.size(); ++i) {
        // 扫描网络输出的所有边界框,仅保留具有高置信度分数的边界框
        // 将边界框的类标签指定为边界框得分最高的类别
        float* data = (float*)outs[i].data;
        for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols) {
            Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
            Point classIdPoint;
            double confidence;
            // 获取最大分数的值和位置
            minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
            if (confidence > confThreshold) {
                int centerX = (int)(data[0] * frame.cols);
                int centerY = (int)(data[1] * frame.rows);
                int width = (int)(data[2] * frame.cols);
                int height = (int)(data[3] * frame.rows);
                int left = centerX - width / 2;
                int top = centerY - height / 2;
                
                classIds.push_back(classIdPoint.x);
                confidences.push_back((float)confidence);
                boxes.push_back(Rect(left, top, width, height));
            }
        }
    }

(8) 使用 NMSBoxes 函数应用非极大值抑制,只得到具有高置信度的非重叠边界框并进行绘制:

    // 执行非极大值抑制
    // 消除具有较低置信度的冗余重叠边界框
    vector<int> indices;
    NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
    for (size_t i = 0; i < indices.size(); ++i) {
        int idx = indices[i];
        Rect box = boxes[idx];
        drawPred(classIds[idx], confidences[idx], box.x, box.y,
                 box.x + box.width, box.y + box.height, frame);
    }

使用 YOLO 执行目标检测的结果如下所示:

检测结果

3. 完整代码

完整代码 object_detection_yolo.cpp 如下所示:

#include <fstream>
#include <sstream>
#include <iostream>

#include <opencv2/core/core.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

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

// 初始化参数
float confThreshold = 0.5;  // 置信度阈值
float nmsThreshold = 0.4;   // 非极大值抑制阈值
int inpWidth = 416;         // 网络输入图像宽度
int inpHeight = 416;        // 网络输入图像高度
vector<string> classes;

// 绘制预测边界框
void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame) {
    // 绘制显示边界框矩形
    rectangle(frame, Point(left, top), Point(right, bottom), Scalar(255, 255, 255), 1);
    // 获取类别名的标签及其置信度
    string conf_label = format("%.2f", conf);
    string label="";
    if (!classes.empty()) {
        label = classes[classId] + ":" + conf_label;
    }
    // 在边界框顶部显示标签
    int baseLine;
    Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
    top = max(top, labelSize.height);
    rectangle(frame, Point(left, top - labelSize.height), Point(left + labelSize.width, top + baseLine), Scalar(255, 255, 255), FILLED);
    putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,0,0),1,LINE_AA);
}

// 使用非最大值抑制移除置信度低的边界框
void postprocess(Mat& frame, const vector<Mat>& outs) {
    vector<int> classIds;
    vector<float> confidences;
    vector<Rect> boxes;
    for (size_t i = 0; i < outs.size(); ++i) {
        // 扫描网络输出的所有边界框,仅保留具有高置信度分数的边界框
        // 将边界框的类标签指定为边界框得分最高的类别
        float* data = (float*)outs[i].data;
        for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols) {
            Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
            Point classIdPoint;
            double confidence;
            // 获取最大分数的值和位置
            minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
            if (confidence > confThreshold) {
                int centerX = (int)(data[0] * frame.cols);
                int centerY = (int)(data[1] * frame.rows);
                int width = (int)(data[2] * frame.cols);
                int height = (int)(data[3] * frame.rows);
                int left = centerX - width / 2;
                int top = centerY - height / 2;
                
                classIds.push_back(classIdPoint.x);
                confidences.push_back((float)confidence);
                boxes.push_back(Rect(left, top, width, height));
            }
        }
    }
    
    // 执行非极大值抑制
    // 消除具有较低置信度的冗余重叠边界框
    vector<int> indices;
    NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
    for (size_t i = 0; i < indices.size(); ++i) {
        int idx = indices[i];
        Rect box = boxes[idx];
        drawPred(classIds[idx], confidences[idx], box.x, box.y,
                 box.x + box.width, box.y + box.height, frame);
    }
}

// 获取输出层的名称
vector<String> getOutputsNames(const Net& net) {
    static vector<String> names;
    if (names.empty()) {
        // 获取输出层的索引
        vector<int> outLayers = net.getUnconnectedOutLayers();
        // 获取网络中所有层的名称
        vector<String> layersNames = net.getLayerNames();
        // 获取names变量中输出层的名称
        names.resize(outLayers.size());
        for (size_t i = 0; i < outLayers.size(); ++i) {
            names[i] = layersNames[outLayers[i] - 1];
        }
    }
    return names;
}

int main(int argc, char** argv) {
    // 加载类别名
    string classesFile = "data/coco.names";
    ifstream ifs(classesFile.c_str());
    string line;
    while (getline(ifs, line)) classes.push_back(line);
    // 提供模型的配置和权重文件
    String modelConfiguration = "data/yolov3.cfg";
    String modelWeights = "data/yolov3.weights";
    // 加载网络
    Net net = readNetFromDarknet(modelConfiguration, modelWeights);
    net.setPreferableBackend(DNN_BACKEND_OPENCV);
    net.setPreferableTarget(DNN_TARGET_CPU);
    
    Mat input, blob;
    input= imread(argv[1]);
    if (input.empty()) {
        cout << "No input image" << endl;
        return 0;
    }
    // 创建输入
    blobFromImage(input, blob, 1/255.0, Size(inpWidth, inpHeight), Scalar(0,0,0), true, false);
    // 设定网络输入
    net.setInput(blob);
    // 执行前向传播
    vector<Mat> outs;
    net.forward(outs, getOutputsNames(net));
    // 移除低置信度边界框
    postprocess(input, outs);
    vector<double> layersTimes;
    double freq = getTickFrequency() / 1000;
    double t = net.getPerfProfile(layersTimes) / freq;
    string label = format("Inference time for compute the image : %.2f ms", t);
    cout << label << endl;
    
    imshow("YOLOv3", input);
    waitKey(0);
    return 0;
}

相关链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系
OpenCV实战(21)——基于随机样本一致匹配图像
OpenCV实战(22)——单应性及其应用
OpenCV实战(23)——相机标定
OpenCV实战(24)——相机姿态估计
OpenCV实战(25)——3D场景重建
OpenCV实战(26)——视频序列处理
OpenCV实战(27)——追踪视频中的特征点
OpenCV实战(28)——光流估计
OpenCV实战(29)——视频对象追踪
OpenCV实战(30)——OpenCV与机器学习的碰撞
OpenCV实战(31)——基于级联Haar特征的目标检测
OpenCV实战(32)——使用SVM和定向梯度直方图执行目标检测
OpenCV实战(33)——OpenCV与深度学习的碰撞

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

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

相关文章

Vue虚拟节点和渲染函数

1.虚拟节点 虚拟节点&#xff08;dom&#xff09;本质上就是一个普通的JS对象&#xff0c;用于描述视图的界面结构 2.渲染函数render()&#xff1a;接收一个 createElement()函数创建的VNode Vue.component("board", {render: function(createElement) {return cr…

025-第三代软件开发-实现需求长时间未操作返回登录界面

第三代软件开发-实现需求长时间未操作返回登录界面 文章目录 第三代软件开发-实现需求长时间未操作返回登录界面项目介绍实现需求长时间未操作返回登录界面实现思路用户操作监控QML 逻辑处理 关键字&#xff1a; Qt、 Qml、 QTimer、 timeout、 eventFilter 项目介绍 欢迎…

【Linux】文件权限、目录权限、掩码、粘滞位以及相关指令

文章目录 Linux权限两种用户Linux权限管理三个问题:什么是权限呢?三种角色是什么:那么为什么存在所属组呢? 文件类型和访问权限&#xff08;事物属性&#xff09;a) 文件类型b)基本权限 文件权限值的表示方法文件访问权限的相关设置方法a)chmodb)chownc)chgrp 权限掩码d)umas…

第87步 时间序列建模实战:LSTM回归建模

基于WIN10的64位系统演示 一、写在前面 这一期&#xff0c;我们介绍大名鼎鼎的LSTM回归。 同样&#xff0c;这里使用这个数据&#xff1a; 《PLoS One》2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal…

UVM-什么是UVM方法学

概念简介 百度对UVM的解释如下&#xff1a; 通用验证方法学&#xff08;Universal Verification Methodology, UVM&#xff09;是一个以SystemVerilog类库为主体的验证平台开发框架&#xff0c;验证工程师可以利用其可重用组件构建具有标准化层次结构和接口的功能验证环境 UVM…

C/C++文件操作————写文件与读文件以及通讯录的改进 (保姆级教学)

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.前言 2.写文件函数与读文件函数 …

打印新闻标题,使用封装get、set方法,打印前15个字符串

package day21; import java.util.ArrayList; import java.util.Collections;/*** author monian* Wo yi wu ta,wei shou shu er!*/ public class Homework01 {SuppressWarnings({"all"})public static void main(String[] args) {News news1 new News("新冠确…

Typora的相关配置(Typora主题、字体、快捷键、习惯)

Typora的相关配置(Typora主题、字体、快捷键、习惯) 文章目录 Typora的相关配置(Typora主题、字体、快捷键、习惯)[toc]一、主题配置二、字体配置查看字体名称是否可以被识别&#xff1a;如果未能正确识别&#xff1a; 三、习惯配置四、快捷键配置更改提供的功能的快捷键&#…

【学习笔记】win11 时间显示秒

【学习笔记】windows 11 时间显示秒 原本一直用着 windows 10 的系统&#xff0c;点击右下角的托盘时钟&#xff0c;可以看到当前的秒数&#xff0c;平时拿来粗略的计时&#xff0c;看时间非常的方便&#xff0c;现在换成了 windows 11 的系统&#xff0c;点击右下角的托盘时钟…

如何处理前端本地存储和缓存?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

【Qt控件之QTabBar】介绍及使用

概述 QTabBar类提供了一个选项卡栏&#xff0c;例如用于选项卡对话框。 QTabBar非常简单易用&#xff0c;它使用预定义的形状绘制选项卡&#xff0c;并在选择选项卡时发出信号。它可以被子类化以调整外观和感觉。Qt还提供了一个实现好的QTabWidget。 每个选项卡具有一个tabT…

图——邻接表

图的邻接表表示法&#xff08;有向图&#xff09; 实现绿色的有向图 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <corecrt_malloc.h>#define Max 100//顶点数量最大值typedef struct ArcNode {//边信息int VNode_index;//顶点下标ArcNode…

MySQL索引全解:从理论到实践,打造高效查询的指南

文章目录 索引的数据结构Hash表有序数组树 详细聊聊BTreeBTree的特点树的度&#xff08;宽度&#xff09;可以很大叶子节点存储数据叶子节点双向指针记录 聚簇索引聚簇索引的优点聚簇索引的缺点 覆盖索引如何利用覆盖索引 普通索引与唯一索引的选择查询更新change bufferchange…

从入门到精通,30天带你学会C++【第八天:函数及洛谷精选题目讲解】(学不会你找我)

目录 Everyday English 前言 函数 洛谷 P5736 【深基7.例2】质数筛 分析题意 思路点拨 AC代码 AC截图 结尾 Everyday English Winners never quit! 胜者永不言弃&#xff01; 前言 这节课我们来学习函数&#xff0c;虽然我断更了几周&#xff0c;但我还是要把最…

三十六、【进阶】show profiles分析

1、profiles &#xff08;1&#xff09;详情 可以帮助清楚的展现&#xff0c;每一条SQL语句的执行耗时&#xff0c;以及时间都耗费到哪里去了 &#xff08;2&#xff09;基础语句 2、查看是否支持profiles mysql> select have_profiling; ------------------ | have_prof…

【LeetCode力扣】234 快慢指针 | 反转链表 | 还原链表

目录 1、题目介绍 2、解题思路 2.1、暴力破解法 2.2、快慢指针反转链表 1、题目介绍 原题链接&#xff1a; 234. 回文链表 - 力扣&#xff08;LeetCode&#xff09; 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1]输出&#xff1a;true 示例 2&#xff1a; 输入&am…

自然语言处理---Transformer机制详解之ELMo模型介绍

1 ELMo简介 ELMo是2018年3月由华盛顿大学提出的一种预训练模型. ELMo的全称是Embeddings from Language Models.ELMo模型的提出源于论文<< Deep Contextualized Word Representations >>.ELMo模型提出的动机源于研究人员认为一个好的预训练语言模型应该能够包含丰…

42904-2023 金属和合金的腐蚀 海水管路动水腐蚀试验

1 范围 本文件规定了在天然海水或人工海水中控制流速、温度模拟管路动水腐蚀试验方法。 本文件适用于板状试样、管状试样及管件等在天然海水或人工海水中进行的管路动水腐蚀试验。 2 规范性引用文件 下列文件中的内容通过文中的规范性引用而构成本文件必不可少的条款。其中…

在pytorch中对于张量维度的理解

原文参考链接&#xff1a; https://blog.csdn.net/qq_36930921/article/details/121670945. https://zhuanlan.zhihu.com/p/356951418 张量的计算&#xff1a;https://zhuanlan.zhihu.com/p/140260245 学习过程中对知识的补充学习&#xff0c;谨防原文失效&#xff0c;请大家支…

MySQL——练习

MySQL 一、练习要求二、练习过程 一、练习要求 创建表并插入数据&#xff1a; 字段名数据类型主键外键非空唯一自增idINT是否是是否nameVARCHAR(50)否否是否否glassVARCHAR(50)否否是否否 sch 表内容 id name glass 1 xiaommg glass 1 2 xiaojun glass 21、创建一个可以统计…