YoloV5 原文理解&工程实践(一)

图一引用酒酿小圆子
图2、4引用tt丫
图3引用ksaisaisai

YOLOv5 是一种快速、准确的目标检测算法,由Ultralytics团队开发。

YOLOv5的主要特点包括:
轻量高效:YOLOv5采用了一种简洁而高效的网络结构,既保持了较高的检测准确性,又使得网络更加轻量化和易于部署。
多尺度检测:YOLOv5引入了多尺度训练和推理策略,能够有效地检测不同大小的目标。
方便的部署:YOLOv5支持多种框架,并提供了方便的部署方式,使得在不同平台上部署成为可能。
总的来说,YOLOv5是一种快速、准确、轻量化的目标检测算法,适用于各种实时检测场景,并在业界取得了较好的反响。

原理

参考:lqj

下面的例子是一个448 * 448的图片,有3 * 3的grid。

在这里插入图片描述
在这里插入图片描述
计算x,y,w,h的真实值(ground truth)的过程:
在这里插入图片描述
在这里插入图片描述

网络结构

在这里插入图片描述

在这里插入图片描述

模块介绍

Focus模块

在 YOLOv5 中,“Focus” 是一种特殊的卷积层,用于替代传统的下采样操作。它的作用是在减少特征图的尺寸的同时,更好地保留物体的细节和小尺寸目标。

Focus 层可以采用切片操作把特征图拆分成多个块,采用隔列采样+通道拼接,从而实现特征图的下采样。这种方式有助于增强网络对小目标的感知能力,并提高目标检测的准确性。(如下图)

在这里插入图片描述
代码实现如下:

def autopad(k, p=None):  # kernel, padding
    # Pad to 'same'
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
    return p
class Conv(nn.Module):
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        return self.act(self.conv(x))
class Focus(nn.Module):
    # Focus wh information into c-space
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
        # self.contract = Contract(gain=2)
# 采用隔项切片操作 特征图由(b,c,w,h) ->(b,4c,w/2,h/2)
    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)
        return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)) 
        # return self.conv(self.contract(x))

Bottleneck

Bottleneck是一个标准的瓶颈层,由 1x1conv + 3x3conv + shortcut残差块 组成。在BottleneckCSP和yolo.py的parse_model中调用。

模型结构:
在这里插入图片描述

class Bottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        # 1*1卷积层
        self.cv1 = Conv(c1, c_, 1, 1)
        # 3*3卷积层
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        # shortcut=True 并且 c1==c2 才能做shortcut, 将输入和输出相加之后再输出
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))


BottleneckCSP

BottleneckCSP也是瓶颈层,由几个Bottleenck模块的堆叠 + CSP结构 组成。

模块结构:
在这里插入图片描述

class BottleneckCSP(nn.Module):
    # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        
        # 4个1*1卷积层的堆叠
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
        self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
        self.cv4 = Conv(2 * c_, c2, 1, 1)
        
        # bn层
        self.bn = nn.BatchNorm2d(2 * c_)  # applied to cat(cv2, cv3)
        
        # 激活函数
        self.act = nn.SiLU()
        
        # m:叠加n次Bottleneck的操作
        # 操作符*可以把一个list拆开成一个个独立的元素
        self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))

    def forward(self, x):
        y1 = self.cv3(self.m(self.cv1(x)))
        y2 = self.cv2(x)
        return self.cv4(self.act(self.bn(torch.cat((y1, y2), 1))))

c3 模块

C3是一种简化版的BottleneckCSP,模块和BottleneckCSP模块类似
Bottleneck模块
在这里插入图片描述
c3模块 = 3*conv + CSPBottleneck 。Conv部分在上述代码中已做介绍,所以这部分主要介绍CSPBottleneck。

在新版yolov5中,作者将BottleneckCSP模块转变为了C3模块,其结构作用基本相同均为CSP架构,只是在修正单元的选择上有所不同,其包含了3个标准卷积层以及多个Bottleneck模块(数量由配置文件.yaml的n和depth_multiple参数乘积决定)

该模块是对残差特征进行学习的主要模块,其结构分为两支,一支使用了上述指定多个Bottleneck堆叠和3个标准卷积层,另一支仅经过一个基本卷积模块,最后将两支进行concat操作。
代码如下:

class Bottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
class C3(nn.Module):
    # CSP Bottleneck with 3 convolutions
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c1, c_, 1, 1)
        self.cv3 = Conv(2 * c_, c2, 1)  # act=FReLU(c2)
        self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
# c_ 表示输入通道数和输出通道数,shortcut 表示是否使用残差连接,
# g 表示组卷积的分组数目,e 表示扩展系数。self.m它将列表推导式生成的多个Bottleneck 模块解包为单独的参数,用于构建一个包含多个 Bottleneck 模块的列表
    def forward(self, x):
        return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))      

spp 模块

在这里插入图片描述
在这里插入图片描述
这里的图片有点问题 CBL 应该更改为CBS yolov5中使用的是SiLU而非ReLU.

代码如下

class SPP(nn.Module):
    # Spatial Pyramid Pooling (SPP) layer https://arxiv.org/abs/1406.4729
    def __init__(self, c1, c2, k=(5, 9, 13)):
        super().__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
        self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])

    def forward(self, x):
        x = self.cv1(x)
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')  # suppress torch 1.9.0 max_pool2d() warning
            return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
class SPPF(nn.Module):
    # Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher
    def __init__(self, c1, c2, k=5):  # equivalent to SPP(k=(5, 9, 13))
        super().__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * 4, c2, 1, 1)
        self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)

    def forward(self, x):
        x = self.cv1(x)
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')  # suppress torch 1.9.0 max_pool2d() warning
            y1 = self.m(x)
            y2 = self.m(y1)
            return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))

下班了,下次继续。。。
书接上文。

Neck

Neck部分用的是PANet结构
FPN 主要通过自顶向下的网络结构,在不同层级提取的特征图之间建立连接,实现多尺度信息融合。FPN 通过上采样和融合操作,将低分辨率的语义信息与高分辨率的细节信息相结合。
PANet 也通过自顶向下的路径聚合机制实现多尺度信息的融合,但相比 FPN 更加灵活和深入。PANet 通过横向连接自适应卷积操作,能够更好地捕获不同层级特征之间的关系,从而更有效地聚合多尺度信息。PANet 强调了更深层次的语义信息传播和整合,通过自适应卷积操作和路径聚合机制,能够更充分地利用不同层级特征图中的语义信息。

在这里插入图片描述

工程实践

  • 源码中不同文件的作用:

models/yolo.py:这个文件定义了 YOLOv5 的网络结构,包括网络的初始化、前向传播等关键部分。

utils/datasets.py:这个文件定义了数据集加载和预处理的方法,包括数据增强、标注解析等功能。

utils/general.py:这个文件包含了一些通用的工具函数,例如计算损失函数、处理坐标转换等。

train.py:这是用于训练 YOLOv5 模型的主文件,包括了模型训练的整个流程,如数据加载、训练循环、日志记录等。

detect.py:这个文件包含了用于目标检测的推理代码,可以使用训练好的模型进行目标检测。

models/common.py:包含了一些公共的网络模块和工具函数,比如卷积操作、激活函数等。

requirements.txt:这个文件列出了项目所需的 Python 包及其版本,方便用户安装相应的依赖。

experiment.py:这个文件通常用于定义实验配置和参数,包括训练过程中的超参数设置、数据集路径、模型配置等。通过修改 experiment.py 文件,可以方便地调整模型训练时的各项参数。

tf.py:文件通常包含了与 TensorFlow 框架相关的一些函数和工具,用于在项目中与 TensorFlow 进行交互。可能包括一些 TensorFlow 特定的操作或者工具函数。

hubconf.py:文件一般用于定义模型的结构和配置信息,以便于通过 Python 的 torch.hub.load() 接口加载预训练模型。在这个文件中,可以指定模型的网络结构、权重文件的链接等信息,使得用户可以方便地加载预训练模型进行推理或微调。

了解完各个文件的作用之后,此刻一名调包侠‘算法工程师’ 将顺利调用大佬的作品,只见他火速标注完数据,切分数据集之后,潇洒的输入了路径。。。接下来的步骤就是推理并部署到设备上。

我尝试了libtorch,onnxruntime,最后发现还是dnn方便,因为这种方式只需依赖opencv库,比较好在设备上部署。下面展示方法(nms,画框框等与官方代码一致)

int main() {
    cv::dnn::Net net = cv::dnn::readNetFromONNX("path/to/your/onnx");
    cv::Mat img = cv::imread("path/to/your/image");
    cv::resize(img, img, cv::Size(640, 640));
    cv::Mat blob = cv::dnn::blobFromImage(img, 1 / 255.0, cv::Size(640, 640), cv::Scalar(0, 0, 0), true, false);
    net.setInput(blob);
    cv::Mat detection = net.forward();
    cv::Mat filtered_boxes = filter_box(detection, 0.25, 0.45);
    draw(img, filtered_boxes);
    cv::imshow("Detection Result", img);
    cv::waitKey(0);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值