CV

图像检测-下

YOLO, YOLO-V2, YOLO-V3, YOLO-V4, SSD

Posted by 新宇 on July 29, 2020

一、YOLO算法

Yolo算法采用一个单独的CNN模型实现end-to-end的目标检测,核心思想就是利用整张图作为网络的输入,直接在输出层回归 bounding box(边界框) 的位置及其所属的类别,整个系统如下图所示:

1. 算法思想

Yolo意思是You Only Look Once,它并没有真正的去掉候选区域,而是创造性的将候选区和目标分类合二为一,看一眼图片就能知道有哪些对象以及它们的位置。

Yolo模型采用预定义预测区域的方法来完成目标检测,具体而言是将原始图像划分为 7x7=49 个网格(grid),每个网格允许预测出2个边框(bounding box,包含某个对象的矩形框),总共 49x2=98 个bounding box。我们将其理解为98个预测区,很粗略的覆盖了图片的整个区域,就在这98个预测区中进行目标检测。

2. 网络架构

YOLO的结构非常简单,就是单纯的卷积、池化最后加了两层全连接,从网络结构上看,与前面介绍的CNN分类网络没有本质的区别,最大的差异是输出层用线性函数做激活函数,因为需要预测bounding box的位置(数值型),而不仅仅是对象的概率。所以粗略来说,YOLO的整个结构就是输入图片经过神经网络的变换得到一个输出的张量,如下图所示:

2.1 网格输入

网络的输入是原始图像,唯一的要求是缩放到448x448的大小。主要是因为Yolo的网络中,卷积层最后接了两个全连接层,全连接层是要求固定大小的向量作为输入,所以Yolo的输入图像的大小固定为448x448。

2.2 网格输出

网络的输出就是一个7x7x30 的张量(tensor)

3. 模型训练

在进行模型训练时,我们需要构造训练样本和设计损失函数,才能利用梯度下降对网络进行训练。

3.1 训练样本的构建

3.2 损失函数

3.3 模型训练

Yolo先使用ImageNet数据集对前20层卷积网络进行预训练,然后使用完整的网络,在PASCAL VOC数据集上进行对象识别和定位的训练。 Yolo的最后一层采用线性激活函数,其它层都是Leaky ReLU。训练中采用了drop out和数据增强(data augmentation)来防止过拟合.

4. 模型预测

将图片resize成448x448的大小,送入到yolo网络中,输出一个 7x7x30 的张量(tensor)来表示图片中所有网格包含的对象(概率)以及该对象可能的2个位置(bounding box)和可信程度(置信度)。在采用NMS(Non-maximal suppression,非极大值抑制)算法选出最有可能是目标的结果。

5. YOLO总结

  • 优点:
    • 速度非常快,处理速度可以达到45fps,其快速版本(网络较小)甚至可以达到155fps。
    • 训练和预测可以端到端的进行,非常简便。
  • 缺点:
    • 准确率会打折扣
    • 对于小目标和靠的很近的目标检测效果并不好

二、YOLO-V2

1. better(从预测更准确)

  • batch normalization
    • 批标准化有助于解决反向传播过程中的梯度消失和梯度爆炸问题,降低对一些超参数的敏感性,并且每个batch分别进行归一化的时候,起到了一定的正则化效果,从而能够获得更好的收敛速度和收敛效果。
  • 使用高分辨率图像微调分类模型
    • 采用 224x224 图像进行分类模型预训练后,再采用 448x448 的高分辨率样本对分类模型进行微调(10个epoch),使网络特征逐渐适应 448x448 的分辨率。然后再使用 448x448 的检测样本进行训练,缓解了分辨率突然切换造成的影响。
  • 采用Anchor Boxes
    • YOLO2每个grid采用5个先验框
  • 聚类提取anchor尺度
    • YOLO2尝试统计出更符合样本中对象尺寸的先验框,这样就可以减少网络微调先验框到实际位置的难度;
    • YoloV2选择了聚类的五种尺寸最常使用的anchor box。
  • 边框位置的预测
  • 细粒度特征融合
  • 多尺度训练
    • YOLO2中没有全连接层,可以输入任何尺寸的图像。

2. faster(速度更快)

yoloV2提出了Darknet-19(有19个卷积层和5个MaxPooling层)网络结构作为特征提取网络。DarkNet-19比VGG-16小一些,精度不弱于VGG-16,但浮点运算量减少到约⅕,以保证更快的运算速度。

3. stronger(识别对象更多)

VOC数据集可以检测20种对象,但实际上对象的种类非常多,只是缺少相应的用于对象检测的训练样本。YOLO2尝试利用ImageNet非常大量的分类样本,联合COCO的对象检测数据集一起训练,使得YOLO2即使没有学过很多对象的检测样本,也能检测出这些对象。

三、YOLO-V3

yoloV3以V1,V2为基础进行的改进,主要有:利用多尺度特征进行目标检测;先验框更丰富;调整了网络结构;对象分类使用logistic代替了softmax,更适用于多标签分类任务。

1. 算法简介

YOLOv3是YOLO (You Only Look Once)系列目标检测算法中的第三版,相比之前的算法,尤其是针对小目标,精度有显著提升。

2. 多尺度检测

通常一幅图像包含各种不同的物体,并且有大有小。比较理想的是一次就可以将所有大小的物体同时检测出来。因此,网络必须具备能够“看到”不同大小的物体的能力。因为网络越深,特征图就会越小,所以网络越深小的物体也就越难检测出来。

在实际的feature map中,随着网络深度的加深,浅层的feature map中主要包含低级的信息(物体边缘,颜色,初级位置信息等),深层的feature map中包含高等信息(例如物体的语义信息:狗,猫,汽车等等)。因此在不同级别的feature map对应不同的scale,所以我们可以在不同级别的特征图中进行目标检测。如下图展示了多种scale变换的经典方法。

(a) 这种方法首先建立图像金字塔,不同尺度的金字塔图像被输入到对应的网络当中,用于不同scale物体的检测。但这样做的结果就是每个级别的金字塔都需要进行一次处理,速度很慢。

(b) 检测只在最后一层feature map阶段进行,这个结构无法检测不同大小的物体

(c) 对不同深度的feature map分别进行目标检测。SSD中采用的便是这样的结构。这样小的物体会在浅层的feature map中被检测出来,而大的物体会在深层的feature map被检测出来,从而达到对应不同scale的物体的目的,缺点是每一个feature map获得的信息仅来源于之前的层,之后的层的特征信息无法获取并加以利用。

(d) 与©很接近,但不同的是,当前层的feature map会对未来层的feature map进行上采样,并加以利用。因为有了这样一个结构,当前的feature map就可以获得“未来”层的信息,这样的话低阶特征与高阶特征就有机融合起来了,提升检测精度。在YOLOv3中,就是采用这种方式来实现目标多尺度的变换的。

3. 网络模型结构

在基本的图像特征提取方面,YOLO3采用了Darknet-53的网络结构(含有53个卷积层),它借鉴了残差网络ResNet的做法,在层之间设置了shortcut,来解决深层网络梯度的问题,shortcut如下图所示:包含两个卷积层和一个shortcut connections。 下面我们看下网络结构:

基本组件:蓝色方框内部分 1、CBL:Yolov3网络结构中的最小组件,由Conv+Bn+Leaky_relu激活函数三者组成。 2、Res unit:借鉴Resnet网络中的残差结构,让网络可以构建的更深。 3、ResX:由一个CBL和X个残差组件构成,是Yolov3中的大组件。每个Res模块前面的CBL都起到下采样的作用,因此经过5次Res模块后,得到的特征图是416->208->104->52->26->13大小。

其他基础操作: 1、Concat:张量拼接,会扩充两个张量的维度,例如26×26×256和26×26×512两个张量拼接,结果是26×26×768。 2、Add:张量相加,张量直接相加,不会扩充维度,例如104×104×128和104×104×128相加,结果还是104×104×128。

Backbone中卷积层的数量: 每个ResX中包含1+2×X个卷积层,因此整个主干网络Backbone中一共包含1+(1+2×1)+(1+2×2)+(1+2×8)+(1+2×8)+(1+2×4)=52,再加上一个FC全连接层,即可以组成一个Darknet53分类网络。不过在目标检测Yolov3中,去掉FC层,仍然把Yolov3的主干网络叫做Darknet53结构。

4. 先验框

5. logistic回归

预测对象类别时不使用softmax,而是被替换为一个1x1的卷积层+logistic激活函数的结构。使用softmax层的时候其实已经假设每个输出仅对应某一个单个的class,但是在某些class存在重叠情况(例如woman和person)的数据集中,使用softmax就不能使网络对数据进行很好的预测。

6. 模型的输入与输出

四、YOLO-V4

  • Yolov4的结构图和Yolov3是相似的,不过使用各种新的算法思想对各个子结构都进行了改进。 先整理下Yolov4的结构组件
    • 基本组件:
      • CBM:Yolov4网络结构中的最小组件,由Conv+Bn+Mish激活函数三者组成。
      • CBL:由Conv+Bn+Leaky_relu激活函数三者组成。
      • Res unit:借鉴Resnet网络中的残差结构,让网络可以构建的更深。
      • CSPX:由三个卷积层和X个Res unint模块Concate组成。
      • SPP:采用1×1,5×5,9×9,13×13的最大池化的方式,进行多尺度融合。
    • 其他基础操作:
      • Concat:张量拼接,维度会扩充,和Yolov3中的解释一样,对应于cfg文件中的route操作。
      • Add:张量相加,不会扩充维度,对应于cfg文件中的shortcut操作。
      • Backbone中卷积层的数量: 每个CSPX中包含3+2×X个卷积层,因此整个主干网络Backbone中一共包含2+(3+2×1)+2+(3+2×2)+2+(3+2×8)+2+(3+2×8)+2+(3+2×4)+1=72。

注意: 网络的输入大小不是固定的,在yoloV3中输入默认是416×416,在yoloV4中默认是608×608,在实际项目中也可以根据需要修改,比如320×320,一般是32的倍数。 输入图像的大小和最后的三个特征图的大小也是对应的,比如416×416的输入,最后的三个特征图大小是13×13,26×26,52×52, 如果是608×608,最后的三个特征图大小则是19×19,38×38,76×76。

五、 YOLO-V3案例

1. 数据获取

根据要实现的业务场景,需要收集大量的图像数据,一般来说包含两大来源,一部分是网络数据,可以是开源数据,也可以通过百度、Google图片爬虫得到,另一部分是用户场景的视频录像,这一部分的数据量会更大。对于开源数据我们不需要进行标注,而爬取的数据和视频录像需要进行标注,这时我们可以使用开源工具labelImg进行标注,该软件截图如下:

mac下配置labelImg的方法:

# macos安装方法
# 1.创建虚拟环境,指定python版本,必须是python3
conda create -n labelImage python=3.6
# 2. 开启虚拟环境
conda activate labelImage
# 3.安装pyqt5
pip install pyqt5 -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
# 4.安装lxml
pip install lxml -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
# 5.安装labelImg
pip install labelImg -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
 
# 使用方法:
# 1.终端中启动,开启虚拟环境
conda activate labelImage
# 2.启动labelImg
labelImg
# 3.接下来就可以进行图像标注了

2. TFRecord文件

该案例中我们依然使用VOC数据集来进行目标检测,不同的是我们要利用tfrecord文件来存储和读取数据,首先来看一下tfrecord文件的相关内容。

  • 为什么要使用tfrecord文件?
    • TFRecord是Google官方推荐使用的数据格式化存储工具,为TensorFlow量身打造的。
    • TFRecord规范了数据的读写方式,数据读取和处理的效率都会得到显著的提高

2.1 什么是TFRecord文件

2.2 将什么数据转换为TFRecord文件

对于中大数据集来说,Google官方推荐先将数据集转化为TFRecord数据, 这样可加快在数据读取, 预处理中的速度。接下来我们就将VOC数据集转换为Records格式,将数据写入TFRecords文件中,直接使用write_to_tfrecord即可实现,首先导入工具包:

from dataset.vocdata_tfrecord import load_labels,write_to_tfrecord
import os
  • 将数据写入tfrecord中的流程是:
    • 指定要写入的数据集路径
    • 获取所有的XML标注文件
    • 指定tfrecord的存储位置
    • 获取图像的路径
    • 将数据写入到tfrecord文件中
# 指定要写入的数据集路径

data_path = '/Users/yaoxiaoying/Desktop/yoloV3-tf2/dataset/VOCdevkit/VOC2007'
# 获取所有的XML标注文件

all_xml = load_labels(data_path, 'train')
# 指定tfrecord的存储位置

tfrecord_path = 'voc_train.tfrecords'
# 获取图像的路径

voc_img_path = os.path.join(data_path, 'JPEGImages')
# 将数据写入到tfrecord文件中

write_to_tfrecord(all_xml, tfrecord_path, voc_img_path)

2.3 读取TFRecord文件

VOC数据集已经被写入到TFRecord文件中了,那我们就要从TFrecord文件中将数据读取出来。只使用 getdata就能够轻松的读取数据。

# 读取tfrecords文件所需的工具包

from dataset.get_tfdata import getdata
# 绘图

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

# 指定tfrecord文件的位置,获取tfrecord文件中的数据

datasets = getdata("dataset/voc_val.tfrecords")

# 将从TFRecord文件中读取的数据展示出来

from matplotlib.patches import Rectangle
# 数据类别

from utils.config_utils import read_class_names
classes = read_class_names("config/classname")
# 将tfrecord中的图像进行展示

plt.figure(figsize=(15, 10))
# 初始化:第几个图像

i = 0
# 从datasets中选取3个样本,获取图像,大小,框的标注信息和类别信息

for image, width, height, boxes, boxes_category in datasets.take(3):
    # 进行绘图

    plt.subplot(1, 3, i+1)
    # 绘制图像

    plt.imshow(image)
    # 获取坐标区域

    ax = plt.gca()
    # 遍历所有的框

    for j in range(boxes.shape[0]):
        # 绘制框

        rect = Rectangle((boxes[j, 0], boxes[j, 1]), boxes[j, 2] -boxes[j, 0], boxes[j, 3]-boxes[j, 1], color='r', fill=False)
        # 将框显示在图像上

        ax.add_patch(rect)
        # 显示标注信息
        # 获取标注信息的id

        label_id = boxes_category[j]
        # 获取标准信息

        label = classes.get(label_id.numpy())
        # 将标注信息添加在图像上

        ax.text(boxes[j, 0], boxes[j, 1] + 8, label,color='w', size=11, backgroundcolor="none")
    # 下一个结果

    i += 1
# 显示图像

plt.show()

2.4 数据处理

# 输入:原图像及图像上的标准框
# 输出:将尺度调整后的图像,及相应的目标框

image,bbox = preprocess(oriimage,oribbox,input_shape=(416,416))

# 对读取的数据进行处理并绘制结果:

# 1.导入工具包

from dataset.preprocess import preprocess as ppro
# 2.创建画布

plt.figure(figsize=(15,10))
# 3.获取数据遍历

i = 0
for image,width,height,boxes,boxes_category in datasets.take(3):
    # 4.进行数据处理

    image,boxes = preprocess(image,boxes)
    # 5.划分不同的坐标轴subplot()

    plt.subplot(1,3,i+1)
    # 6.显示图像:plt.imshow()

    plt.imshow(image[0])
    # 7.显示box,遍历所有的bbox,rectange进行绘制

    ax = plt.gca()
    for j in range(boxes.shape[0]):
        rect = Rectangle((boxes[j, 0], boxes[j, 1]), boxes[j, 2] -boxes[j, 0], boxes[j, 3]-boxes[j, 1], color='r', fill=False)
        ax.add_patch(rect)
        # 8.显示类别

        label_id = boxes_category[j]
        label = classes.get(label_id.numpy())
        ax.text(boxes[j, 0], boxes[j, 1] + 8, label,color='w', size=11, backgroundcolor="none")
    i+=1
plt.show()

3. 模型构建

# 导入工具包

from model.yoloV3 import YOLOv3
# 模型实例化:指定输入图像的大小,和类别数

yolov3 = YOLOv3((416,416,3),80)
# 获取模型架构

yolov3.summary()

4. 模型预测

4.1 损失函数计算

# 导入所需的工具包

from core.loss import Loss
# 实例化

yolov3_loss = Loss((416,416,3),80)

4.2 正负样本的设定

  • 正样本:
    • 首先计算目标中心点落在哪个grid上,然后计算这个grid对应的3个先验框(anchor)和目标真实位置的IOU值,取IOU值最大的先验框和目标匹配。那么该anchor 就负责预测这个目标,那这个anchor就作为正样本,将其置信度设为1,其他的目标值根据标注信息设置。
  • 负样本:
    • 所有不是正样本的anchor都是负样本,将其置信度设为0,参与损失计算,其它的值不参与损失计算,默认为0。
# 导入目标值设置所需方法

from core.bbox_target import bbox_to_target
# 获取图像及其标注信息

for image, width, height, boxes, labels in datasets.take(1):
    # 获取anchor的目标值,label1是13*13的目标值,label2是26*26的目标值,label3是52*52的目标值,

    label1,label2,label3 = bbox_to_target(bbox=boxes,label=labels,num_classes=20)

# 导入工具包

import tensorflow as tf
# label1[...,0:4]坐标值,label1[...,4]置信度,label1[...,5:]类别分数

index = tf.where(tf.equal(label1[...,4],1))
# index.numpy(),说明索引为12 12 0 个像素中Anchor是正样本

array([[12, 12,  0]])

# label1[12, 12,0,0:4].numpy()

array([209., 318.,  88., 108.], dtype=float32)

# label1[12,12,0,5:].numpy()

array([0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0.], dtype=float32)

# 将目标值绘制在图像上

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
# 1.获取类别信息

from utils.config_utils import read_class_names
classes = read_class_names('config/classname')
# 2.创建画布

plt.figure(figsize=(15,10))
# 3.获取数据遍历

for image,width,height,boxes,boxes_category in datasets.take(1):
    # 4.显示图像:plt.imshow()

    plt.imshow(image)
    # 5.显示box,遍历所有的bbox,rectange进行绘制

    ax = plt.gca()
    for j in range(boxes.shape[0]):
        rect = Rectangle((boxes[j, 0], boxes[j, 1]), boxes[j, 2] -boxes[j, 0], boxes[j, 3]-boxes[j, 1], color='r', fill=False)
        ax.add_patch(rect)
        # 6.显示类别

        label_id = boxes_category[j]
        label = classes.get(label_id.numpy())
        ax.text(boxes[j, 0], boxes[j, 1] + 8, label,color='w', size=11, backgroundcolor="none")
    # 7.绘制正样本的anchor的目标值

    anchor = label1[12, 12,0,0:4].numpy()
    rect2 = Rectangle((anchor[0]-anchor[2]/2, anchor[1]-anchor[3]/2), anchor[2], anchor[3],color='g', fill=False)
    ax.add_patch(rect2)
plt.show()

4.3 模型训练

  • 前面我们已经详细介绍了网络模型架构,在网络预测前我们需要对网络进行训练,接下来使用端到端的方式进行模型训练,基本步骤是:
    • 1、加载数据集:我们在这里使用VOC数据集,所以需要从TFrecord文件中加载VOC数据集
    • 2、模型实例化:加载yoloV3模型和损失函数的实现
    • 3、模型训练:计算损失函数,使用反向传播算法对模型进行训练

4.3.1 获取数据集

# 导入

from dataset.preprocess import dataset
# 设置batch_size

batch_size=1
# 获取训练集数据,并指定batchsize,返回训练集数据

trainset = dataset("dataset/voc_train.tfrecords",batch_size)

4.3.2 加载模型

# V3模型的实例化,指定输入图像的大小,即目标检测的类别个数

yolov3 = YOLOv3((416, 416, 3,), 20)
yolov3_loss = Loss((416,416,3), 20)

4.3.3 模型训练

  • 模型训练也就是要使用损失函数,进行反向传播,利用优化器进行参数更新,训练的流程是:
    • 1、指定优化器:在这里我们使用加动量的SGD方法
    • 2、设置epoch,进行遍历获取batch数据送入网络中进行预测
    • 3、计算损失函数,使用反向传播更新参数,我们使用tf.GradientTape实现:
      • 定义上下文环境:tf.GradientTape
      • 计算损失函数loss
      • 使用 tape.gradient(loss,model.trainable_variables) 自动计算梯度,loss是损失结果,trainable_variables为所有需要训练的变量。
      • 使用 optimizer.apply_gradients(zip(grads,model.trainable_variables)) 自动更新模型参数,zip(grads, trainable_variables)- 将梯度和参数关联起来,然后apply_gradients会自动的利用梯度对参数进行更新。
# 1、定义优化方法

optimizer = tf.keras.optimizers.SGD(0.1,0.9)
# 2.设置epoch,获取batch数据送入网络中进行预测

for epoch in range(300):
    loss_history = []
    # 遍历每一个batch的图像和目标值,进行更新

    for (batch, inputs) in enumerate(trainset):
        images, labels = inputs
        # 3.计算损失函数,使用反向传播更新参数

        # 3.1 定义上下文环境

        with tf.GradientTape() as tape:
            # 3.2 将图像送入网络中

            outputs = yolov3(images)
            # 3.3 计算损失函数

            loss = yolov3_loss([*outputs, *labels])
            # 3.4 计算梯度

            grads = tape.gradient(loss, yolov3.trainable_variables)
            # 3.5 梯度更新

            optimizer.apply_gradients(zip(grads, yolov3.trainable_variables))
            # 3.6 打印信息

            info = 'epoch: %d, batch: %d ,loss: %f'%(epoch, batch, np.mean(loss_history))
            print(info)
            loss_history.append(loss.numpy())
yolov3.save('yolov3.h5')

5. 模型预测

我们使用训练好的模型进行预测,在这里我们通过yoloV3模型进行预测,并将预测结果绘制在图像上。首先导入工具包,预训练好的模型是使用coco数据集进行训练的,所以指定相应的类别信息:

# 读取图像,绘图的工具包

import cv2
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
# yoloV3的预测器

from core.predicter import Predictor

# coco数据集中的类别信息

classes = ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 
           'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 
           'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog',
           'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe',
           'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 
           'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 
           'skateboard', 'surfboard','tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 
           'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 
           'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 
           'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 
           'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 
           'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']
  • 整个流程是:
    • 1.读取要进行目标检测的图像
    • 2.实例化yoloV3的预测器,并加载预训练模型。
    • 3.利用预测器对图片进行目标检测
    • 4.将检测结果绘制在图像上
# 1. 图像读取

img = cv2.imread("image.jpg")
# 2.实例化,并加载预训练模型

predictor = Predictor(class_num=80, yolov3="weights/yolov3.h5")
# 3.获取检测结果

boundings = predictor.predict(img)
# 4.将检测结果绘制在图像上
# 4.1 显示图像

plt.imshow(img[:, :, ::-1])
# 获取坐标区域

ax = plt.gca()
# 4.2 遍历检测框,将检测框绘制在图像上

for bounding in boundings:
    # 绘制框

    rect = Rectangle((bounding[0].numpy(), bounding[1].numpy()), bounding[2].numpy(
    ) - bounding[0].numpy(), bounding[3].numpy()-bounding[1].numpy(), color='r', fill=False)
    # 将框显示在图像上

    ax.add_patch(rect)
    # 显示类别信息
    # 获取类别信息的id

    label_id = bounding[5].numpy().astype('int32')
    # 获取类别

    label = classes[label_id]
    # 将标注信息添加在图像上

    ax.text(bounding[0].numpy(), bounding[1].numpy() + 8,
            label, color='w', size=11, backgroundcolor="none")
# 显示图像

plt.show()

六、SSD

  • 目标检测算法主要分为两类:
    • Two-stage方法:
      • 如R-CNN系列算法,主要思路就是通过Selective Search或者CNN网络产生一系列的稀疏矩阵的候选区域,然后对这些候选区域进行分类和回归,two stage的方法优势在于准确率度高;
    • One-stage方法:
      • 如YOLO系列方法,主要思路就是均匀地在图片上不同位置进行密集采样,采样时使用不同尺度和长宽比box,然后利用CNN提取特征后直接进行分类和回归,整个过程只需要一步,所以优势在于速度快。我们接下来介绍的SSD方法也是单阶段的算法。

1. SSD网络结构

1.1 backbone

网络采用VGG16作为基础模型,使用imagenet数据进行预训练后,将conv4-1前一层的maxpooling中池化模式padding改为same(图中对应pytorch中的ceil_mode),使得输出为38x38,Conv4-3就是多尺度特征中的第一个38x38的特征图,因为该层比较靠前,所以在其后面增加了一个L2 Normalization层,对每个像素点在channle维度做归一化。VGG16最后的两个全连接层转换成 3x3 卷积层 conv6和 卷积层conv7,同时将最后的池化层由原来的stride=2的 2x2 变成stride=1的 3x3的池化层。

其中conv6使用的Dilated Convolutions,可以翻译为扩张卷积或空洞卷积。与普通的卷积相比,增加了一个扩张率(dilation rate)参数,主要用来表示扩张的大小。扩张卷积与普通卷积的相同点在于,卷积核的大小是一样的,在神经网络中参数数量不变,区别在于扩张卷积具有更大的感受野。如下图所示:

1.2 extra

新增的Conv8_2,Conv9_2,Conv10_2,Conv11_2提取用于检测的特征图,特征图的大小如下表所示: 红框中的内容是进行多尺度分析的特征图,在加上backbone部分的Conv4_3和Conv7获取的特征图,共提取了6个特征图,其大小分别是 (38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1),我们将其送入到loc和cls中进行目标检测。

1.3 loc and cls

1.3.1 PriorBox层先验框的生成方法

1.3.2 loc的预测结果

2. 模型训练

2.1 正负样本标记

  • 在训练过程中,首先需要确定训练图片中的 ground truth 与哪一个先验框来进行匹配,与之匹配的先验框所对应的边界框将负责预测它。
  • SSD的先验框和ground truth匹配原则:
    • 正样本
      • 1、对于图片中的每个gt,找到与其IOU最大的先验框,该先验框与其匹配,这样可以保证每个gt一定与某个先验框匹配。
      • 2、对于剩余未匹配的先验框,若某个gt的IOU大于某个阈值(一般0.5),那么该先验框与这个gt匹配
    • 负样本
      • 其它的先验框标记为负样本
  • 注意:
    • 1、某个gt可以和多个先验框匹配,而每个先验框只能和一个gt进行匹配
    • 2、如果多个gt和某一个先验框的IOU均大于阈值,那么先验框只与IOU最大的那个进行匹配

2.2 损失函数

2.3 困难样本挖掘

3. 模型预测