当前位置: 代码网 > 科技>电脑产品>内存 > 经典目标检测YOLO系列(三)YOLOv3算法详解

经典目标检测YOLO系列(三)YOLOv3算法详解

2024年08月01日 内存 我要评论
经典目标检测YOLO系列(三)YOLOv3算法详解

经典目标检测yolo系列(三)yolov3算法详解

  • 不论是yolov1,还是yolov2,都有一个共同的致命缺陷:小目标检测的性能差。尽管yolov2使用了passthrough技术将16倍降采样的特征图(即c4特征图)融合到了c5特征图中,但最终的检测仍是在c5尺度的特征图上进行的。

  • 为了解决这一问题,yolo作者做了第3次改进,主要改进如下:

    • 使用了更好的主干网络darknet-53
    • 使用了多级检测与特征金字塔fpn方法
    • 修改损失函数

1 yolov3的改进之处

1.1 更好的主干网络darknet-53

  • 下图是darknet-53的网络架构图。

  • 相较于yolov2中所使用的darknet19,新的网络使用了53层卷积。

  • 同时,添加了残差网络中的残差连结结构,以提升网络的性能。

  • darknet53网络中的降采样操作没有使用maxpooling层,而是由stride=2的卷积来实现。

  • 卷积层仍旧是线性卷积、bn层以及leakyrelu激活函数的串联组合。

  • 虚线框是核心模块,由一层1×1卷积和一层3×3卷积层串联构成的残差模块。

在这里插入图片描述

在imagenet数据集上,darknet53的top1准确率和top5准确率几乎与resnet101和resnet152持平,但速度却显著高于后两者。因此,相较于所对比的两个残差网络,darknet53在速度和精度上具有更高的性价比。

不过darknet53没有成为学术界的主流模型,其受欢迎程度仍不及resnet系列。

在这里插入图片描述

1.2 多级检测与特征金字塔

  • yolo-v1模型精度不足的一个重要表现就是召回率低,即该检的检不出,这一缺点在针对小目标检测方面表现的尤为明显。
  • 为了提高目标检测的召回率,yolo-v2通过使用passthrough操作将浅层特征与深层特征进行融合,使得最终用于目标预测的特征中,既包含细节信息也包含语义信息。
    • 这种操作在一定程度上提高了目标检测的召回率,针对小型目标的检测能力也有明显提高。
    • 然而,仅使用一层细节特征进行细节特征融合往往是不够的(ssd的目标预测在6个尺度的特征图上进行)。
    • 因此,yolo-v3借助了特征金字塔网络(feature pyramid network, fpn)机制,从3个不同尺度的融合特征上进行目标预测。fpn是2017年(早于yolo-v3提出一年)提出的一种特征融合网络结构,旨在为目标检测模型提供一种有效的多尺度特征融合机制。

1.2.1 特征金字塔fpn

  • fpn工作认为网络浅层的特征图包含更多的细节信息,但语义信息较少,而深层的特征图则恰恰相反

  • 随着网络深度的加深,降采样操作的增多,细节信息不断被破坏,致使小物体的检测效果逐渐变差,而大目标由于像素较多,仅靠网络的前几层还不足以使得网络能够认识到大物体(感受野不充分),但随着层数变多,网络的感受野逐渐增大,网络对大目标的认识越来越充分,检测效果自然会更好。

  • 因此,用浅层网络负责检测较小的目标,深层网络负责检测较大的目标。实现这一技术路线的就是ssd网络,但ssd只关注了信息数量问题,没有关注语义深浅问题。浅层特征虽然保留足够多的位置信息,但是语义信息的层次较浅,对目标的理解和认识不够充分。

    在这里插入图片描述

  • 考虑识别物体的类别依赖于语义信息,因此fpn利用自顶向下(top-down)的特征融合结构,利用空间上采样将深层网络的语义信息融合到浅层网络中(下图中的d)。

    • 处于性能和算力之间的平衡考虑,我们只会使用到主干网络输出的3个尺度的特征图,即c3、c4和c5,其降采样倍数分别为8、16和32。fpn会通过1×1卷积、3×3卷积以及上采样操作得到p3、p4和p5特征图。
    • 如果我们输入图像比较大,如800×1333,c5特征图感受野就不够大,无法覆盖到一些大目标,而且自身的语义信息相对较浅,因此就会在c5或者p5进一步降采样得到特征图p6,甚至p7。例如,retinanet以及fcos等。

    在这里插入图片描述

1.3 yolov3中的fpn

  • yolov3的关键改进便是使用了fpn结构与多级检测方法。yolov3在3个尺度上去进行预测,分别是经过8倍降采样的特征图c3、经过16倍降采样的特征图c4和经过32倍降采样的特征图c5。yolov3网络结构如下图所示。

    • yolov3中的fpn,特征融合采用通道拼接,而非求和。

    • yolov3中fpn的卷积层较多。

    • yolov3最终会输出52×52×3(1+c+4)、26×26×3(1+c+4)和13×13×3(1+c+4)三个预测张量,然后将这些预测结果汇总到一起,进行后处理,得到最终的检测结果。

    • 在这里插入图片描述

  • 从网格角度来看,假如输入图像是416×416,那么darknet-53输出的3个特征图:c3(52×52×256)、c4(26×26×512)和c5(13×13×1024)。相当于针对输入图像做了不同疏密的网格,显然越密的网格越适合检测小物体,而越疏的网格越适合检测大物体。

  • 在每个特征图上,yolov3在每个网格处放置3个先验框。

    • 由于yolov3一共使用3个尺度,因此,yolov3一共设定了9个先验框,这9个先验框仍旧是使用kmeans聚类的方法获得的。

    • 在coco上,这9个先验框的宽高分别是(10, 13)、(16, 30)、(33, 23)、(30, 61)、(62, 45)、(59, 119)、(116, 90)、(156, 198)、(373, 326)。

      • c3特征图,每个网格处放置(10, 13)、(16, 30)、(33, 23)三个先验框,用来检测较小的物体。
      • c4特征图,每个网格处放置(30, 61)、(62, 45)、(59, 119)三个先验框,用来检测中等大小的物体。
      • c5特征图,每个网格处放置(116, 90)、(156, 198)、(373, 326)三个先验框,用来检测较大的物体。
    • 可以使用下面代码可视化,这三组先验框。

      #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      import os
      import cv2
      
      
      def show_anchor_box(picture_path, feature_map_size=13):
          # 输入图片尺寸
          input_size = 416
      
          # 在coco数据集上,利用kmeans聚类出来的9组不同宽高的anchor box
          mask52 = [0, 1, 2]
          mask26 = [3, 4, 5]
          mask13 = [6, 7, 8]
          anchors = [
              10, 13, 16, 30, 33, 23,  # 小物体
              30, 61, 62, 45, 59, 119,  # 中等物体
              116, 90, 156, 198, 373, 326  # 大物体
          ]
          grid_show_flag = true
      
      
          img = cv2.imread(picture_path)
          print("原始图片的shape: ", img.shape)
          img = cv2.resize(img, (input_size, input_size))
      
          # 显示网格,颜色为黑色
          if grid_show_flag:
              height, width, channels = img.shape
              grid_sizex = int(input_size / feature_map_size)
              for x in range(0, width - 1, grid_sizex):
                  cv2.line(img, pt1 = (x, 0), pt2 = (x, height), color = (0, 0, 0), thickness = 1, linetype = 1)  # x grid
      
              grid_sizey = int(input_size / feature_map_size)
              for y in range(0, height - 1, grid_sizey):
                  cv2.line(img, pt1 = (0, y), pt2 = (width, y),  color = (0, 0, 0), thickness = 1, linetype = 1)  # y grid
      
          if feature_map_size == 13:
              for ele in mask13:
                  # 画出图像中心点聚类出来不同宽高的3组anchor box,颜色为红色
                  # 需要告诉函数的左上角顶点pt1和右下角顶点的坐标pt2
                  cv2.rectangle(img,
                                    pt1 = ((int(input_size * 0.5 - 0.5 * anchors[ele * 2]), int(input_size * 0.5 - 0.5 * anchors[ele * 2 + 1]))),
                                    pt2 = ((int(input_size * 0.5 + 0.5 * anchors[ele * 2]), int(input_size * 0.5 + 0.5 * anchors[ele * 2 + 1]))),
                                    color = (0, 0, 255),
                                    thickness = 2
                            )
      
          if feature_map_size == 26:
              for ele in mask26:
                  # 画出图像中心点聚类出来不同宽高的3组anchor box,颜色为红色
                  # 需要告诉函数的左上角顶点pt1和右下角顶点的坐标pt2
                  cv2.rectangle(img,
                                    pt1 = ((int(input_size * 0.5 - 0.5 * anchors[ele * 2]), int(input_size * 0.5 - 0.5 * anchors[ele * 2 + 1]))),
                                    pt2 = ((int(input_size * 0.5 + 0.5 * anchors[ele * 2]), int(input_size * 0.5 + 0.5 * anchors[ele * 2 + 1]))),
                                    color = (0, 0, 255),
                                    thickness = 2
                            )
      
          if feature_map_size == 52:
              for ele in mask52:
                  # 画出图像中心点聚类出来不同宽高的3组anchor box,颜色为红色
                  # 需要告诉函数的左上角顶点pt1和右下角顶点的坐标pt2
                  cv2.rectangle(img,
                                    pt1 = ((int(input_size * 0.5 - 0.5 * anchors[ele * 2]), int(input_size * 0.5 - 0.5 * anchors[ele * 2 + 1]))),
                                    pt2 = ((int(input_size * 0.5 + 0.5 * anchors[ele * 2]), int(input_size * 0.5 + 0.5 * anchors[ele * 2 + 1]))),
                                    color = (0, 0, 255),
                                    thickness = 2
                            )
      
      
          cv2.imshow('img', img)
          while cv2.waitkey(1000) != 27:  # loop if not get esc.
              if cv2.getwindowproperty('img', cv2.wnd_prop_visible) <= 0:
                  break
          cv2.destroyallwindows()
      
      
      if __name__ == '__main__':
          directory = './imgs'
          for filename in os.listdir(directory):
              picture_path = os.path.join(directory, filename)
              show_anchor_box(picture_path, feature_map_size=13)
              show_anchor_box(picture_path, feature_map_size=26)
              show_anchor_box(picture_path, feature_map_size=52)
      

      在这里插入图片描述

1.3 修改损失函数

  • 边界框的置信度损失。
    • 由yolov1及yolov2的mse损失函数,改为bce损失,即我们之前自己实现的yolov1及yolov2中的损失函数。
    • 不设置正负样本的权重,尽管负样本数量远远大于正样本。
    • 不使用预测框和目标框的iou值作为置信度的学习标签,而采用0/1离散值,即我们之前自己实现的yolov1做法。
  • 类别损失。
    • 不同于之前的mse损失函数,yolov3先使用sigmoid函数将每个类别的置信度映射到0到1之间,再使用bce去计算每个类别的损失,即我们之前自己实现的yolov1做法。
    • 不使用softmax的解释。
      • softmax面对的类别必须是平行互斥的,预测得到最终的类别取概率分布中的最大者。
      • 当面对类别标签为非平行互斥的数据集,softmax预测将无能为力。
      • 与之不同的是,sigmoid预测得到的结果仅表示属于对应类别可能性,与其他类别无关,预测类别之间不互斥在某种意义上意味着对象可以拥有多个标签。
  • 边界框损失
    • 使用bce函数来计算中心点偏移量的损失
    • 使用mse计算宽高偏移量的损失

1.4 yolov3效果

  • 相较于yolov2的aps指标5.0,yolov3达到了18.3,小目标检测能力大大提高。

  • 尽管yolov3的性能不及retinanet,但在ap50指标上,yolov3几乎和retinanet达到一个水准,但yolov3的速度是后者的3倍左右。

在这里插入图片描述

2 yolov3的复现

  • 事实上,yolov2最大的变化就在于使用了多级检测以及fpn。

  • 后面依然不会百分之百地复现官方的yolov3,先给出实现的网络结构图。

    在这里插入图片描述

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com