当前位置: 首页 > article >正文

YOLO目标检测3层

 一. 参考资料

《YOLO目标检测》 by 杨建华博士

本篇文章的主要内容来自于这本书,只是作为学习记录进行分享。

二. 搭建YOLOv1的网络

2.1 YOLOv1的网络结构

        作者带我们构建的YOLOv1网络是一个全卷积结构,其中不包含任何全连接层,这一点可以避免YOLOv1中存在的因全连接层而导致的参数过多的问题。尽管YOLO网络是在YOLOv2工作才开始转变为全卷积结构,但我们已经了解了全连接层的弊端,因此没有必要再循规蹈矩地照搬YOLOv1的原始网络结构,这也符合我们设计YOLOv1的初衷。

2.1.1 主干网络

        使用当下流行的ResNet网络代替YOLOv1的GoogLeNet风格的主干网络。相较于原本的主干网络,ResNet使用了诸如批归一化(batch normalization,BN)、残差连接(residual connection)等操作,有助于稳定训练更大更深的网络。

        前面已经讲过,将图像分类网络用作目标检测网络的主干网络时,通常是不需要最后的平均池化层和分类层的,因此,这里去除ResNet-18网络中的最后的平均池化层和全连接层,

        这里使用的ResNet-18网络的最大降采样倍数为32,在这个网络中,默认输入图像尺寸为416 \times 416,最后的输出图像为14 \times 14,要比传统的YOLOv1更精细些。

        根据书中提供的代码,实现ResNet主干网络的关键部分的代码为:

# YOLO_Tutorial/models/yolov1/yolov1_backbone.py
# --------------------------------------------------------
...
class ResNet(nn.Module):
    def __init__(self, block, layers, zero_init_residual=False):
        super(ResNet, self).__init__()
        self.inplanes=64
        self.conv1=nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1=nn.BatchNorm2d(64)
        self.relu=nn.ReLU(inplace=True)
        self.maxpool=nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1=self._make_layer(block, 64, layers[0])
        self.layer2=self._make_layer(block, 128, layers[1], stride=2)
        self.layer3=self._make_layer(block, 256, layers[2], stride=2)
        self.layer4=self._make_layer(block, 512, layers[3], stride=2)
 
    def forward(self, x):
        c1=self.conv1(x)     # [B, C, H/2, W/2]
        c1=self.bn1(c1)      # [B, C, H/2, W/2]
        c1=self.relu(c1)     # [B, C, H/2, W/2]
        c2=self.maxpool(c1)  # [B, C, H/4, W/4]
 
        c2=self.layer1(c2)   # [B, C, H/4, W/4]
        c3=self.layer2(c2)   # [B, C, H/8, W/8]
        c4=self.layer3(c3)   # [B, C, H/16, W/16]
        c5=self.layer4(c4)   # [B, C, H/32, W/32]
 
        return c5
2.1.2 颈部网络

        出于参数和性能的综合考虑,作者使用性价比较高的空间金字塔池化(SPP)模块,遵循主流的YOLO框架的做法,对SPP模块进行适当的改进。

改进的SPP模块的网络结构设计参考了YOLOv5开源项目中的实现方法,让一层5×5的最大池化层等效于先前讲过的5×5、9×9和13×13这三条并行的最大池化层分支,从而降低计算开销,这也和之前所讲的空间金字塔的特性相同,通过逐层卷积能够从小到大找到不同尺寸的目标,再将不同的卷积结果叠起来进行最终的输出。

# YOLO_Tutorial/models/yolov1/yolov1_neck.py
# --------------------------------------------------------
...
class SPPF(nn.Module):
    def __init__(self, in_dim, out_dim, expand_ratio=0.5, pooling_size=5,
                 act_type='lrelu', norm_type='BN'):
        super().__init__()
        inter_dim=int(in_dim * expand_ratio)
        self.out_dim=out_dim
        self.cv1=Conv(in_dim, inter_dim, k=1, act_type=act_type, norm_type=
          norm_type)
        self.cv2=Conv(inter_dim * 4, out_dim, k=1, act_type=act_type, norm_type=
          norm_type)
        self.m=nn.MaxPool2d(kernel_size=pooling_size, stride=1, padding=pooling_
          size // 2)
 
    def forward(self, x):
        x=self.cv1(x)
        y1=self.m(x)
        y2=self.m(y1)
        return self.cv2(torch.cat((x, y1, y2, self.m(y2)), 1))

在代码4-2中,输入的特征图会先被一层1 \times 1卷积处理,其通道数会被压缩一半,随后再由一层5 \times 5最大池化层连续处理三次,依据感受野的原理,该处理方式等价于分别使用5 \times 59 \times 913 \times 13最大池化层并行地处理特征图。最后,将所有处理后的特征图沿通道拼接,再由另一层1 \times 1卷积做一次输出的映射,将其通道映射至指定数目的输出通道。

2.1.3 检测头

在YOLOv1中,检测头部分用的是全连接层,全连接层具有参数过多,过于占用内存空间的缺点,这里,我们抛弃全连接层,改用卷积网络。由于当前主流的检测头是解耦检测头,因此,我们也采用解耦检测头作为YOLOv1的检测头,由类别分支和回归分支组成,类别分支进行类别和置信度预测,回归分支进行位置参数预测,如图4-4所示。

检测头的结构十分简单,共输出两种不同的特征:类别特征\mathbf{F}_{cls} \in \mathbb{R}^{13 \times 13 \times 512}和位置特征\mathbf{F}_{reg} \in \mathbb{R}^{13 \times 13 \times 512},没有复杂结构,代码编写简单,作者实现了相关代码,如以下代码所示:

# YOLO_Tutorial/models/yolov1/yolov1_head.py
# --------------------------------------------------------
...
class DecoupledHead(nn.Module):
    def __init__(self, cfg, in_dim, out_dim, num_classes=80):
        super().__init__()
        print('==============================')
        print('Head: Decoupled Head')
        self.in_dim=in_dim
        self.num_cls_head=cfg['num_cls_head']
        self.num_reg_head=cfg['num_reg_head']
        self.act_type=cfg['head_act']
        self.norm_type=cfg['head_norm']
 
        # cls head
        cls_feats=[]
        self.cls_out_dim=max(out_dim, num_classes)
        for i in range(cfg['num_cls_head']):
            if i==0:
                cls_feats.append(
                    Conv(in_dim, self.cls_out_dim, k=3, p=1, s=1,
                        act_type=self.act_type,
                        norm_type=self.norm_type,
                        depthwise=cfg['head_depthwise'])
                        )
            else:
                cls_feats.append(
                    Conv(self.cls_out_dim, self.cls_out_dim, k=3, p=1, s=1,
                        act_type=self.act_type,
                        norm_type=self.norm_type,
                        depthwise=cfg['head_depthwise'])
                        )
        # reg head
        reg_feats=[]
        self.reg_out_dim=max(out_dim, 64)
        for i in range(cfg['num_reg_head']):
            if i==0:
                reg_feats.append(
                    Conv(in_dim, self.reg_out_dim, k=3, p=1, s=1,
                        act_type=self.act_type,
                        norm_type=self.norm_type,
                        depthwise=cfg['head_depthwise'])
                        )
            else:
                reg_feats.append(
                    Conv(self.reg_out_dim, self.reg_out_dim, k=3, p=1, s=1,
                        act_type=self.act_type,
                        norm_type=self.norm_type,
                        depthwise=cfg['head_depthwise'])
                        )
 
        self.cls_feats=nn.Sequential(*cls_feats)
        self.reg_feats=nn.Sequential(*reg_feats)
 
    def forward(self, x):
        cls_feats=self.cls_feats(x)
        reg_feats=self.reg_feats(x)
 
        return cls_feats, reg_feats
2.1.4 预测层

        在官方的YOLOv1中,每个网格预测两个边界框,而这两个边界框的学习完全依赖自身预测的边界框位置的准确性,YOLOv1本身并没有对这两个边界框做任何约束。可以认为,这两个边界框是“平权”的,谁学得好谁学得差完全是随机的,二者之间没有显式的互斥关系,且每个网格处最终只会输出置信度最大的边界框,那么可以将这两个“平权”的边界框修改为一个边界框,即每个网格处只需要输出一个边界框。于是,我们的YOLOv1网络最终输出的张量为\mathbf{Y} \in \mathbb{R}^{13 \times 13 \times (1+N_c+4)},其中通道维度上的1表示边界框的置信度,N_c表示类别的总数,4表示边界框的4个位置参数。这里不再有表示每个网格的边界框数量的B

        预测层的结构如下图所示:

        预测层中的几个部分:

        (1) 边界框置信度:使用类别特征\mathbf{F}_{cls} \in \mathbb{R}^{13 \times 13 \times 512}来完成边界框置信度的预测。另外,不同于YOLOv1中使用预测框与目标框的IoU作为优化目标,我们暂时采用简单的二分类标签0/1作为置信度的学习标签。这样改进并不表示二分类标签比将IoU作为学习标签的方法更好,而仅仅是图方便,省去了在训练过程中计算IoU的麻烦,且便于读者上手。由于边界框的置信度的值域在0~1范围内,为了确保这一点,避免网络输出超出这一值域的“不合理”数值,作者使用Sigmoid函数将网络的置信度输出映射到0~1范围内。

        (2) 类别置信度:作者使用类别特征\mathbf{F}_{cls} \in \mathbb{R}^{13 \times 13 \times 512}来完成类别置信度的预测。因此,类别特征将分别被用于有无目标的检测和类别分类两个子任务中。类别置信度显然也在0~1范围内,因此我们使用Sigmoid函数来输出对每个类别置信度的预测。

        (3) 边界框位置参数:作者使用位置特征\mathbf{F}_{reg} \in \mathbb{R}^{13 \times 13 \times 512}来完成边界框位置参数的预测。我们已经知道,边界框的中心点偏差(t_x,t_y)的值域是0~1,因此,我们也对网络输出的中心点偏差t_xt_y使用Sigmoid函数。另外两个参数Wh是非负数,这也就意味着,我们必须保证网络输出的这两个量是非负数,否则没有意义。一种办法是用ReLU函数来保证这一点,然而ReLU的负半轴梯度为0,无法回传梯度,有“死元”的潜在风险。另一种办法则是仍使用线性输出,但添加一个不小于0的不等式约束。但不论是哪一种方法,都存在约束问题,这一点往往是不利于训练优化的。为了解决这一问题,我们采用指数函数来处理,该方法既能保证输出范围是实数域,又是全局可微的,不需要额外的不等式约束。两个参数Wh的计算如以下公式(4-1)所示,其中,指数函数外部乘了网络的输出步长S,这就意味着预测的t_wt_h都是相对于网格尺度来表示的。

W=s \times e^{t_w} \\ h =s \times e^{t_h} \\


http://www.kler.cn/a/519518.html

相关文章:

  • unity.NavMesh Agent
  • 基于单片机的智能小区门禁系统设计(论文+源码)
  • 2024 年度技术总结:从实践到成长
  • 可以称之为“yyds”的物联网开源框架有哪几个?
  • 笔灵ai写作技术浅析(二):自然语言处理
  • 【精选】基于数据挖掘的招聘信息分析与市场需求预测系统 职位分析、求职者趋势分析 职位匹配、人才趋势、市场需求分析数据挖掘技术 职位需求分析、人才市场趋势预测
  • 存储过程优化实践:统一返回结构、参数 JSON 化与事务原子化
  • 开发环境搭建-3:配置 nodejs 开发环境 (fnm+ node + pnpm)
  • VMware虚拟机迁移到阿里云
  • 科技快讯 | 理想官宣:正式收费!WeChat 港币钱包拓宽商户网络;百川智能发布深度思考模型Baichuan-M1-preview
  • C# 多线程同步(Mutex | Semaphore)
  • firefox屏蔽debugger()
  • 简笔画生成smplx sketch2pose
  • java读取在resources目录下的文件内容
  • 《 C++ 点滴漫谈: 十四 》为什么说 #define 是 C++ 的潘多拉盒子?
  • 房租管理系统的智能化应用助推租赁行业高效运营与决策优化
  • 蓝桥与力扣刷题(160 相交链表)
  • ubuntu调用图形化网络测试工具
  • Maui学习笔记- SQLite简单使用案例02添加详情页
  • Hive关于数据库的语法,warehouse,metastore
  • 算法12(力扣739)-每日温度
  • 小识Java死锁是否会造成CPU100%?
  • 16 分布式session和无状态的会话
  • 贪心算法(六)
  • 均值(信息学奥赛一本通-1060)
  • 【Linux系统】进程间通信一