Single Shot Multibox Detection (SSD)实战(上)

介绍了边界框、锚框、多尺度对象检测和数据集。现在,我们将利用这些背景知识构建一个目标检测模型:单次多盒检测(SSD)。这种快速简便的模式已经被广泛应用。该模型的一些设计思想和实现细节也适用于其他对象检测模型。

1. Model

图1显示了一个SSD模型的设计。该模型的主要组成部分是一个基本网络块和若干个串联的多尺度特征块。在这里,基网络块用于提取原始图像的特征,一般采用深度卷积神经网络的形式。关于SSDs的论文选择在分类层之前放置一个截断的VGG,但现在这通常被ResNet取代。我们可以设计基础网络,使其输出更大的高度和宽度。这样,基于这个特征图生成更多的锚框,允许我们检测更小的对象。接下来,每个多尺度要素块都会减少上一层提供的要素映射的高度和宽度(例如,它可以将尺寸缩小一半)。然后使用特征映射中的每个元素来扩展输入图像上的感受野。这样,多尺度特征块越接近图1顶部,其输出特征图越小,基于特征图生成的锚框越少。此外,特征块越接近顶部,特征图中每个元素的感受野越大,越适合检测较大的物体。由于SSD根据基本网络块和每个多尺度特征块生成不同数量的不同大小的锚盒,然后预测锚盒的类别和偏移量(即预测的边界框),以检测不同大小的对象,因此SSD是一种多尺度目标检测模型。

Fig. 1  The SSD is composed of a base network block and several multiscale feature blocks connected in a series.

接下来,我们将描述图1中模块的实现。首先,我们需要讨论类别预测和包围盒预测的实现。

1.1. Category Prediction Layer

将对象类别的数目设置为问问. 在这种情况下,锚盒类别的数量是q+1,0表示只包含背景的定位框。对于特定比例,将要素映射的高度和宽度分别设置为h和w。如果我们以每个元素为中心来生成锚箱,我们需要对hwa锚箱。如果我们使用全连接层(FCN)作为输出,这可能会导致模型参数过多。如何使用卷积层通道输出类别预测。SSD采用相同的方法来降低模型复杂度。具体地说,类别预测层使用保持输入高度和宽度的卷积层。因此,输出和输入与特征映射的宽度和高度的空间坐标一一对应。假设输出和输入具有相同的空间坐标(x,y),坐标的通道(x,y)在输出特征图上,包含使用输入特性图坐标生成的所有定位框的类别预测(x,y)作为中心。因此,有a(q+1)输出通道,输出通道索引为i(q+1)+ji(q+1)+j(0≤j≤q0≤j≤q)表示类别索引的预测j对于锚箱索引。我们将定义这种类型的类别预测层。在我们指定参数a和q之后,它使用3×3卷积层,填充为1。这个卷积层的输入和输出的高度和宽度保持不变。

%matplotlib inline

from d2l import mxnet as d2l

from mxnet import autograd, gluon, image, init, np, npx

from mxnet.gluon import nn

npx.set_np()

def cls_predictor(num_anchors, num_classes):

return nn.Conv2D(num_anchors * (num_classes + 1), kernel_size=3,

padding=1)

1.2. Bounding Box Prediction Layer

包围盒预测层的设计与类别预测层的设计相似。唯一的区别是,在这里,我们需要为每个锚框预测4个偏移量,而不是q+1类别。

def bbox_predictor(num_anchors):

return nn.Conv2D(num_anchors * 4, kernel_size=3, padding=1)

1.3. Concatenating Predictions for Multiple Scales

如前所述,SSD使用基于多尺度的特征映射来生成锚盒并预测其类别和偏移量。由于不同尺度的特征映射,同一元素中心锚盒的形状和数量不同,不同尺度下的预测输出可能具有不同的形状。

在下面的例子中,我们使用同一批数据构建两个不同比例的特征映射,Y1和Y2。在这里,Y2的高度和宽度是Y1的一半。以类别预测为例,我们假设Y1和Y2特征映射中的每个元素生成五个(Y1)或三个(Y2)锚定框。当有10个对象类别时,类别预测输出通道的数量为 5×(10+1)=55或3×(10+1)=33。预测输出的格式是(批次大小、通道数、高度、宽度)。如您所见,除了批量大小,其他维度的大小是不同的。因此,我们必须将它们转换成一致的格式,并将多个尺度的预测串联起来,以便于后续的计算。

def forward(x, block):

block.initialize()

return block(x)

Y1 = forward(np.zeros((2, 8, 20, 20)), cls_predictor(5, 10))

Y2 = forward(np.zeros((2, 16, 10, 10)), cls_predictor(3, 10))

(Y1.shape, Y2.shape)

((2, 55, 20, 20), (2, 33, 10, 10))

通道尺寸包含具有相同中心的所有锚定盒的预测。我们首先将通道维度移动到最后一个维度。我们可以将批量大小转换为相同的二进制大小(因为批量大小的预测是相同的)(batch size, height ×× width ×× number of channels),以便于后续在第一尺寸。

def flatten_pred(pred):

return npx.batch_flatten(pred.transpose(0, 2, 3, 1))

def concat_preds(preds):

return np.concatenate([flatten_pred(p) for p in preds], axis=1)

因此,尽管Y1和Y2的形态不同,我们仍然可以将同一批次的两个不同尺度的预测结果串联起来。

concat_preds([Y1, Y2]).shape

(2, 25300)

1.4. Height and Width Downsample Block

对于多尺度目标检测,我们定义了下面的下采样块,它将高度和宽度减少了50%。这个街区有两个3×3,填充为1和a的卷积层2×2,以串联方式连接的跨距为2的最大池化层。我们知道,3×3填充为1的卷积层不会改变特征映射的形状。然而,随后的池化层直接将特征图的大小缩小了一半。因为1×2+(3−1)+(3−1)=6,输出特征映射中的每个元素在形状的输入特征映射上都有一个接受域6×6。height和width downsample块放大了输出特征映射中每个元素的感受野。

def down_sample_blk(num_channels):

blk = nn.Sequential()

for _ in range(2):

blk.add(nn.Conv2D(num_channels, kernel_size=3, padding=1),

nn.BatchNorm(in_channels=num_channels),

nn.Activation('relu'))

blk.add(nn.MaxPool2D(2))

return blk

通过在height和width下采样块中测试正向计算,我们可以看到它改变了输入通道的数目,并使高度和宽度减半。

forward(np.zeros((2, 3, 20, 20)), down_sample_blk(10)).shape

(2, 10, 10, 10)

1.5. Base Network Block

基本网络块用于从原始图像中提取特征。为了简化计算,我们将构造一个基础小网络。该网络由三个高度和宽度的下采样块串联而成,因此在每一步中它的通道数加倍。当我们用输入原始256×256图像时,基础网络块输出具有该形状的特征映射32×32。

def base_net():

blk = nn.Sequential()

for num_filters in [16, 32, 64]:

blk.add(down_sample_blk(num_filters))

return blk

forward(np.zeros((2, 3, 256, 256)), base_net()).shape

(2, 64, 32, 32)

1.6. The Complete Model

SSD型号共包含五个模块。每个模块输出一个特征映射,用于生成锚框并预测这些锚框的类别和偏移量。第一个模块是基本网络块,模块2到4个是高度和宽度下采样块,第五个模块是一个全局最大池化层,它将高度和宽度减小到1。因此,模块2到5都是图1所示的多尺度特征块。

def get_blk(i):

if i == 0:

blk = base_net()

elif i == 4:

blk = nn.GlobalMaxPool2D()

else:

blk = down_sample_blk(128)

return blk

定义每个模块的正向计算过程。与前面描述的卷积神经网络相比,该模块不仅返回卷积计算输出的特征映射Y,而且返回由Y生成的当前尺度的锚盒及其预测的类别和偏移量。

def blk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):

Y = blk(X)

anchors = npx.multibox_prior(Y, sizes=size, ratios=ratio)

cls_preds = cls_predictor(Y)

bbox_preds = bbox_predictor(Y)

return (Y, anchors, cls_preds, bbox_preds)

当我们在图7.1中提到的更大的目标块时,它必须生成一个更大的锚框。在这里,我们首先将0.2到1.05的间隔分成五个相等的部分,以确定不同比例的较小锚箱的尺寸:0.2、0.37、0.54等,然后根据SQRT(0.2×0.37)= 0.272,SQRT(0.37×0.54)= 0.447,

以及类似的公式,确定了不同尺度下较大锚箱的尺寸。

sizes = [[0.2, 0.272], [0.37, 0.447], [0.54, 0.619], [0.71, 0.79],

[0.88, 0.961]]

ratios = [[1, 2, 0.5]] * 5

num_anchors = len(sizes[0]) + len(ratios[0]) – 1

可以定义完整的模型,TinySSD。

class TinySSD(nn.Block):

def __init__(self, num_classes, **kwargs):

super(TinySSD, self).__init__(**kwargs)

self.num_classes = num_classes

for i in range(5):

# The assignment statement is self.blk_i = get_blk(i)

setattr(self, 'blk_%d' % i, get_blk(i))

setattr(self, 'cls_%d' % i, cls_predictor(num_anchors,

num_classes))

setattr(self, 'bbox_%d' % i, bbox_predictor(num_anchors))

def forward(self, X):

anchors, cls_preds, bbox_preds = [None] * 5, [None] * 5, [None] * 5

for i in range(5):

# getattr(self, 'blk_%d' % i) accesses self.blk_i

X, anchors[i], cls_preds[i], bbox_preds[i] = blk_forward(

X, getattr(self, 'blk_%d' % i), sizes[i], ratios[i],

getattr(self, 'cls_%d' % i), getattr(self, 'bbox_%d' % i))

# In the reshape function, 0 indicates that the batch size remains

# unchanged

anchors = np.concatenate(anchors, axis=1)

cls_preds = concat_preds(cls_preds)

cls_preds = cls_preds.reshape(

cls_preds.shape[0], -1, self.num_classes + 1)

bbox_preds = concat_preds(bbox_preds)

return anchors, cls_preds, bbox_preds

现在,我们创建一个SSD模型实例,并使用它对图像minibatchx执行正向计算,它的高度和宽度为256像素。如前所述,第一个模块输出带有形状的特征映射32×32。因为模块2到4是高度和宽度的下采样块,模块5是一个全局池化层,并且特征图中的每个元素都被用作4个锚框的中心,总共,在五个比例下为每个图像生成锚框。

net = TinySSD(num_classes=1)

net.initialize()

X = np.zeros((32, 3, 256, 256))

anchors, cls_preds, bbox_preds = net(X)

print('output anchors:', anchors.shape)

print('output class preds:', cls_preds.shape)

print('output bbox preds:', bbox_preds.shape)

output anchors: (1, 5444, 4)

output class preds: (32, 5444, 2)

output bbox preds: (32, 21776)

Single Shot Multibox Detection (SSD)实战(上)的更多相关文章

  1. Single Shot Multibox Detection (SSD)实战(下)

    Single Shot Multibox Detection (SSD)实战(下) 2. Training 将逐步解释如何训练SSD模型进行目标检测. 2.1. Data Reading and In ...

  2. 论文笔记 SSD: Single Shot MultiBox Detector

    转载自:https://zhuanlan.zhihu.com/p/33544892 前言 目标检测近年来已经取得了很重要的进展,主流的算法主要分为两个类型(参考RefineDet):(1)two-st ...

  3. SSD: Single Shot MultiBox Detector

    By Wei Liu, Dragomir Anguelov, Dumitru Erhan, Christian Szegedy, Scott Reed, Cheng-Yang Fu, Alexande ...

  4. 目标检测--SSD: Single Shot MultiBox Detector(2015)

    SSD: Single Shot MultiBox Detector 作者: Wei Liu, Dragomir Anguelov, Dumitru Erhan, Christian Szegedy, ...

  5. SSD(single shot multibox detector)算法及Caffe代码详解[转]

    转自:AI之路 这篇博客主要介绍SSD算法,该算法是最近一年比较优秀的object detection算法,主要特点在于采用了特征融合. 论文:SSD single shot multibox det ...

  6. [论文理解]SSD:Single Shot MultiBox Detector

    SSD:Single Shot MultiBox Detector Intro SSD是一套one-stage算法实现目标检测的框架,速度很快,在当时速度超过了yolo,精度也可以达到two-stag ...

  7. 【计算机视觉】目标检测之ECCV2016 - SSD Single Shot MultiBox Detector

    本文转载自: http://www.cnblogs.com/lillylin/p/6207292.html SSD论文阅读(Wei Liu--[ECCV2016]SSD Single Shot Mul ...

  8. SSD(Single Shot MultiBox Detector)的安装配置和运行

    下文图文介绍转自watersink的博文SSD(Single Shot MultiBox Detector)不得不说的那些事. 该方法出自2016年的一篇ECCV的oral paper,SSD: Si ...

  9. SSD(single shot multibox detector)

    SSD,全称Single Shot MultiBox Detector,是Wei Liu在ECCV 2016上提出的一种目标检测算法,截至目前是主要的检测框架之一,相比Faster RCNN有明显的速 ...

随机推荐

  1. 【工具类】Stream流构建指定长度的时间集合

    package com.gabriel.stage.utils; import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.Dat ...

  2. android调用号和libc

    调用号(以arm平台为例)在/bionic/libc/kernel/uapi/asm-arm/asm/unistd.h: /* WARNING: DO NOT EDIT, AUTO-GENERATED ...

  3. C++基础:模板的声明实现分离

    模板的声明和实现为什么不能分离我不想废话了,我只是在想一种能够分离的方式. 文件 test.h 1 #pragma once 2 3 template<typename TC> 4 cla ...

  4. ConcurrentHashMap源码解读二

    接下来就讲解put里面的三个方法,分别是 1.数组初始化方法initTable() 2.线程协助扩容方法helpTransfer() 3.计数方法addCount() 首先是数组初始化,再将源码之前, ...

  5. 自动化测试面试官:登录或注册时有验证码怎么处理?OCR图像识别技术大揭秘!

    本节大纲 读取cookie实现免登陆 pytesseract+tesseract-ocr实现图像识别 Pillow库对验证码截图 API接口实现图像识别 今天的这个技术点,为什么要给大家分享一下呢? ...

  6. mac Idea快捷键小记

    重写父类方法:control + o 实现父类方法:control + i 最全的一个按键:control + 回车

  7. 面试侃集合 | ArrayBlockingQueue篇

    面试官:平常在工作中你都用过什么什么集合? Hydra:用过 ArrayList.HashMap,呃-没有了 面试官:好的,回家等通知吧- 不知道大家在面试中是否也有过这样的经历,工作中仅仅用过的那么 ...

  8. C++知识概要

    static的用法和作用 在全局变量前加上关键字 static,全局变量就定义成一个全局静态变量.存储在静态存储区,在整个程序运行期间一直存在.同时全局静态变量在声明他的文件之外是不可见的 在局部变量 ...

  9. JAVA 面试相关

    1. int和Integer有什么区别? 答:Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类 ...

  10. [DB] Flink 读 MySQL

    思路 在 Flink 中创建一张表有两种方法: 从一个文件中导入表结构(Structure)(常用于批计算)(静态) 从 DataStream 或者 DataSet 转换成 Table (动态) pa ...