python目标检测SSD算法预测部分源码详解

目录
  • 学习前言
  • 什么是SSD算法
  • ssd_vgg_300主体的源码

学习前言

……学习了很多有关目标检测的概念呀,咕噜咕噜,可是要怎么才能进行预测呢,我看了好久的SSD源码,将其中的预测部分提取了出来,训练部分我还没看懂

什么是SSD算法

SSD是一种非常优秀的one-stage方法,one-stage算法就是目标检测和分类是同时完成的,其主要思路是均匀地在图片的不同位置进行密集抽样,抽样时可以采用不同尺度和长宽比,然后利用CNN提取特征后直接进行分类与回归,整个过程只需要一步,所以其优势是速度快

但是均匀的密集采样的一个重要缺点是训练比较困难,这主要是因为正样本与负样本(背景)极其不均衡(参见Focal Loss),导致模型准确度稍低。

SSD的英文全名是Single Shot MultiBox Detector,Single shot说明SSD算法属于one-stage方法,MultiBox说明SSD算法基于多框预测。
(它真的不是固态硬盘啊~~~~~~)

讲解构架

本次教程的讲解分为俩个部分,第一部分是ssd_vgg_300主体的源码的讲解,第二部分是如何调用ssd_vgg_300主体的源码,即利用源码进行预测。
ssd_vgg_300主体的源码的讲解包括如下三个部分:
1、网络部分,用于建立ssd网络,用于预测种类和框的位置。
2、先验框部分,根据每个特征层的shape,构建出合适比例的框,同时可以减少运算量。
3、解码部分,根据网络部分和先验框部分的输出,对框的位置进行解码。

利用源码进行预测的讲解包括以下三个部分:
1、如何对图片进行处理。
2、载入模型
3、预测过程中处理的流程。

在看本次算法前,建议先下载我简化过的源码,配合观看,在其中运行demo即可执行程序:

ssd_vgg_300主体的源码

本文使用的ssd_vgg_300的源码源于点击下载,本文对其进行了简化,只保留了预测部分,便于理顺整个SSD的框架。

1、 大体框架

在只需要预测的情况下,需要保留ssd_vgg_300源码的网络部分、先验框部分和解码部分
(这里只能使用图片哈,因为VScode收缩后也不能只复制各个部分的函数名)

其中:
1、net函数用于构建网络,其输入值为shape为(None,300,300,3)的图像,在其中会经过许多层网络结构,在这许多的网络结构中存在6个特征层,用于读取框框,最终输出predictions和locations,predictions和locations中包含6个层的预测结果和框的位置。
2、arg_scope用于初始化网络每一个层的默认参数,该项目会用到slim框架,slim框架是一个轻量级的tensorflow框架,其参数初始化与slim中的函数相关。
3、anchors用于获得先验框,先验框也是针对6个特征层的。
4、bboxes_decode用于结合先验框和locations获得在img中框的位置,locations相当于编码过后的框的位置,这样做可以方便SSD网络学习,bboxes_decode用于解码,解码后可以获得img中框的位置。

2、net网络构建

# =============================网络部分============================= #def net(self, inputs,        is_training=True,        update_feat_shapes=True,        dropout_keep_prob=0.5,        prediction_fn=slim.softmax,        reuse=None,        scope='ssd_300_vgg'):    """    SSD 网络定义,调用外部函数,建立网络层    """    r = ssd_net(inputs,                num_classes=self.params.num_classes,                feat_layers=self.params.feat_layers,                anchor_sizes=self.params.anchor_sizes,                anchor_ratios=self.params.anchor_ratios,                normalizations=self.params.normalizations,                is_training=is_training,                dropout_keep_prob=dropout_keep_prob,                prediction_fn=prediction_fn,                reuse=reuse,                scope=scope)    return r

在net函数中,其调用了一个外部的函数ssd_net,我估计作者是为了让代码主体更简洁。
实际的构建代码在ssd_net函数中,网络构建代码中使用了许多的slim.repeat,该函数用于重复构建卷积层,具体构建的层共11层,在进行目标检测框的选择时,我们选择其中的[‘block4’, ‘block7’, ‘block8’, ‘block9’, ‘block10’, ‘block11’]。
这里我们放出论文中的网络结构层。

通过该图我们可以发现,其网络结构如下:
1、首先通过了多个3X3卷积层、5次步长为2的最大池化取出特征,形成了5个Block,其中第四个Block的shape为(?,38,38,512),该层用于提取小目标(多次卷积后大目标的特征保存的更好,小目标特征会消失,需要在比较靠前的层提取小目标特征)。
2、进行一次卷积核膨胀dilate(关于卷积核膨胀的概念可以去网上搜索以下哈)。
3、读取第七个Block7的特征,shape为(?,19,19,1024)
4、分别利用1x1和3x3卷积提取特征,在3x3卷积的时候使用步长2,缩小特征数。获取第八个Block8的特征,shape为(?,10,10,512)
5、重复步骤4,获得9、10、11卷积层的特征,shape分别为(?,5,5,256)、(?,3,3,256)、(?,1,1,256)
此时网络便构建完了。

# =============================网络部分============================= ##############################################################   该部分供SSDNet的net函数调用,用于建立网络                 ##   返回predictions, localisations, logits, end_points     #############################################################def ssd_net(inputs,            num_classes=SSDNet.default_params.num_classes,            feat_layers=SSDNet.default_params.feat_layers,            anchor_sizes=SSDNet.default_params.anchor_sizes,            anchor_ratios=SSDNet.default_params.anchor_ratios,            normalizations=SSDNet.default_params.normalizations,            is_training=True,            dropout_keep_prob=0.5,            prediction_fn=slim.softmax,            reuse=None,            scope='ssd_300_vgg'):    """SSD net definition.    """    # 建立网络    end_points = {}    with tf.variable_scope(scope, 'ssd_300_vgg', [inputs], reuse=reuse):        # Block1        '''        相当于执行:        net = self.conv2d(x,64,[3,3],scope = 'conv1_1')        net = self.conv2d(net,64,[3,3],scope = 'conv1_2')        '''        # (300,300,3) -> (300,300,64) -> (150,150,64)         net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1')        end_points['block1'] = net        net = slim.max_pool2d(net, [2, 2], scope='pool1')        # Block 2.        '''        相当于执行:        net = self.conv2d(net,128,[3,3],scope = 'conv2_1')        net = self.conv2d(net,128,[3,3],scope = 'conv2_2')        '''        # (150,150,64) -> (150,150,128) -> (75,75,128)        net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')        end_points['block2'] = net        net = slim.max_pool2d(net, [2, 2], scope='pool2')        # Block 3.        '''        相当于执行:        net = self.conv2d(net,256,[3,3],scope = 'conv3_1')        net = self.conv2d(net,256,[3,3],scope = 'conv3_2')        net = self.conv2d(net,256,[3,3],scope = 'conv3_3')        '''        # (75,75,128) -> (75,75,256) -> (38,38,256)        net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')        end_points['block3'] = net        net = slim.max_pool2d(net, [2, 2],stride = 2,padding = "SAME", scope='pool3')        # Block 4.        # 三次卷积        # (38,38,256) -> (38,38,512) -> block4_net -> (19,19,512)        net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')        end_points['block4'] = net        net = slim.max_pool2d(net, [2, 2],padding = "SAME", scope='pool4')        # Block 5.        # 三次卷积        # (19,19,512)->(19,19,512)        net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')        end_points['block5'] = net        net = slim.max_pool2d(net, [3, 3], stride=1,padding = "SAME", scope='pool5')        # Block 6: dilate        # 卷积核膨胀        # (19,19,512)->(19,19,1024)        net = slim.conv2d(net, 1024, [3, 3], rate=6, scope='conv6')        end_points['block6'] = net        net = tf.layers.dropout(net, rate=dropout_keep_prob, training=is_training)        # Block 7: 1x1 conv        # (19,19,1024)->(19,19,1024)        net = slim.conv2d(net, 1024, [1, 1], scope='conv7')        end_points['block7'] = net        net = tf.layers.dropout(net, rate=dropout_keep_prob, training=is_training)        # Block 8/9/10/11: 1x1 and 3x3 convolutions stride 2 (except lasts).        # (19,19,1024)->(19,19,256)->(10,10,512)        end_point = 'block8'        with tf.variable_scope(end_point):            net = slim.conv2d(net, 256, [1, 1], scope='conv1x1')            net = custom_layers.pad2d(net, pad=(1, 1))            net = slim.conv2d(net, 512, [3, 3], stride=2, scope='conv3x3', padding='VALID')        end_points[end_point] = net                end_point = 'block9'        # (10,10,512)->(10,10,128)->(5,5,256)        with tf.variable_scope(end_point):            net = slim.conv2d(net, 128, [1, 1], scope='conv1x1')            net = custom_layers.pad2d(net, pad=(1, 1))            net = slim.conv2d(net, 256, [3, 3], stride=2, scope='conv3x3', padding='VALID')        end_points[end_point] = net                end_point = 'block10'        # (5,5,256)->(5,5,128)->(3,3,256)        with tf.variable_scope(end_point):            net = slim.conv2d(net, 128, [1, 1], scope='conv1x1')            net = slim.conv2d(net, 256, [3, 3], scope='conv3x3', padding='VALID')        end_points[end_point] = net                end_point = 'block11'        # (3,3,256)->(1,1,256)        with tf.variable_scope(end_point):            net = slim.conv2d(net, 128, [1, 1], scope='conv1x1')            net = slim.conv2d(net, 256, [3, 3], scope='conv3x3', padding='VALID')        end_points[end_point] = net        # 预测和定位层        predictions = []        logits = []        localisations = []        for i, layer in enumerate(feat_layers):            with tf.variable_scope(layer + '_box'):                p, l = ssd_multibox_layer(end_points[layer],                                          num_classes,                                          anchor_sizes[i],                                          anchor_ratios[i],                                          normalizations[i])            predictions.append(prediction_fn(p))            logits.append(p)            localisations.append(l)        return predictions, localisations, logits, end_pointsssd_net.default_image_size = 300

仔细看代码的同学会发现,除去层的构建外,最后还多了一段循环,那这个循环是做什么的呢?
而且同学们可以感受到,虽然我们提取了特征层,但是这个特征层和预测值、框的位置又有什么关系呢?
这个循环就是用来将特征层转化成预测值和框的位置的。
在循环中我们调用了ssd_multibox_layer函数,该函数的作用如下:
1、读取网络的特征层
2、对网络的特征层再次进行卷积,该卷积分为两部分,互不相干,分别用于预测种类和框的位置。
3、预测框的位置,以Block4为例,Block4的shape为(?,38,38,512),再次卷积后,使其shape变为(?,38,38,num_anchors x 4),其中num_anchors是每个特征点中先验框的数量,4代表框的特点,一个框需要4个特征才可以确定位置,最后再reshape为(?,38,38,num_anchors,4),代表38x38个特点中,第num_anchors个框下的4个特点。
4、预测种类,以Block4为例,Block4的shape为(?,38,38,512),再次卷积后,使其shape变为(?,38,38,num_anchors x 21),其中num_anchors是每个特征点中先验框的数量,21代表预测的种类,包含背景,SSD算法共预测21个种类,最后再reshape为(?,38,38,num_anchors,21),代表38x38个特点中,第num_anchors个框下的21个预测结果。
该函数的输出结果中:
location_pred的shape为(?,feat_block.shape[0],feat_block.shape[1], num_anchors,4)
class_pred的shape为(?,feat_block.shape[0],feat_block.shape[1],num_anchors,21)
具体执行代码如下:

#############################################################   该部分供ssd_net函数调用,返回种类预测和位置预测            ##   将特征层的内容输入,根据特征层返回预测结果                 #############################################################def ssd_multibox_layer(inputs,                       num_classes,                       sizes,                       ratios=[1],                       normalization=-1,                       bn_normalization=False):    reshape = [-1] + inputs.get_shape().as_list()[1:-1]  # 去除第一个和最后一个得到shape    net = inputs    # 对第一个特征层进行l2标准化。    if normalization > 0:        net = custom_layers.l2_normalization(net, scaling=True)    # Number of anchors.    num_anchors = len(sizes) + len(ratios)    # Location.    num_loc_pred = num_anchors * 4    loc_pred = slim.conv2d(net, num_loc_pred, [3, 3], activation_fn=None,                           scope='conv_loc')    loc_pred = custom_layers.channel_to_last(loc_pred)    loc_pred = tf.reshape(loc_pred,                          reshape + [num_anchors, 4])    # Class prediction.    num_cls_pred = num_anchors * num_classes    cls_pred = slim.conv2d(net, num_cls_pred, [3, 3], activation_fn=None,                           scope='conv_cls')    cls_pred = custom_layers.channel_to_last(cls_pred)    cls_pred = tf.reshape(cls_pred,                          reshape + [num_anchors, num_classes])    return cls_pred, loc_pred

3、anchor先验框生成

# ==========================生成先验框部分========================== #def anchors(self, img_shape, dtype=np.float32):    """    计算给定图像形状的默认定位框,调用外部函数,获得先验框。    """    return ssd_anchors_all_layers(img_shape,                                    self.params.feat_shapes,                                    self.params.anchor_sizes,                                    self.params.anchor_ratios,                                    self.params.anchor_steps,                                    self.params.anchor_offset,                                    dtype)

在anchor函数中,其调用了一个外部的函数ssd_anchors_all_layers,用于构建先验框。
先验框的构建和上述网络的构建关系不大,但是需要用到上述网络net的特征层size,先验框的构建目的是为了让图片构建出合适比例的框,同时可以减少运算量。
在进入ssd_anchors_all_layers函数后,根据名字可以知道,该函数用于生成所有层的先验框,其会进入一个循环,该循环用于根据每个特征层的size进行先验框的构建,代码如下:

#############################################################   该部分供SSDNet的anchors函数调用,用于获取先验框           ##   返回y,x,h,w的组和                                       #############################################################def ssd_anchors_all_layers(img_shape,                           layers_shape,                           anchor_sizes,                           anchor_ratios,                           anchor_steps,                           offset=0.5,                           dtype=np.float32):    """    对所有特征层进行计算    """    layers_anchors = []    for i, s in enumerate(layers_shape):        anchor_bboxes = ssd_anchor_one_layer(img_shape, s,                                             anchor_sizes[i],                                             anchor_ratios[i],                                             anchor_steps[i],                                             offset=offset, dtype=dtype)        layers_anchors.append(anchor_bboxes)    return layers_anchors

此时再调用ssd_anchor_one_layer,根据名字可以知道,该函数用于生成单层的先验框,该部分是先验框生成的核心。

输入参数包括图像大小img_shape,特征层大小feat_shape,先验框大小sizes,先验框长宽比率sizes,先验框放大倍数step。

执行过程:
1、根据feat_shape生成x、y的网格。
2、将x和y归一化到0到1之间,这里的x和y对应每个特征层的每一个点,同时x,y对应每个框的中心
3、生成每个特征层的每个点对应的num_anchors大小相同的h和w,即4、6、6、6、4、4,这里的h和w对应着每一个点对应的num_anchors个框中的h和w
4、将h和w每个赋值,h[0]对应比较小的正方形,h[1]对应比较大的正方形,h[2]和h[3]对应√2下不同的长方形,h[4]和h[5]对应√3下不同的长方形。

输出的参数包括:
X和Y的shape为(block.shape[0],block.shape[1],1)
H和w的shape为(boxes_len)

具体的执行代码如下:

#############################################################   该部分供ssd_anchors_all_layers函数调用                  ##   用于获取单层的先验框返回y,x,h,w                          #############################################################def ssd_anchor_one_layer(img_shape,                         feat_shape,                         sizes,                         ratios,                         step,                         offset=0.5,                         dtype=np.float32):    """    输入:图像大小img_shape,特征层大小feat_shape,先验框大小sizes,        先验框长宽比率sizes,先验框放大倍数step。    执行过程:        生成x、y的网格。        将x和y归一化到0到1之间。        生成每个特征层的每个点对应的boxes_len大小相同的h和w,即4、6、6、6、4、4。        将h和w每个赋值,h[0]对应比较小的正方形,h[1]对应比较大的正方形,                    h[2]和h[3]对应√2下不同的长方形,h[4]和h[5]对应√3下不同的长方形。    输出:    X和Y的shape为(block.shape[0],block.shape[1],1)    H和w的shape为(boxes_len)    """    # 网格化    y, x = np.mgrid[0:feat_shape[0], 0:feat_shape[1]]    # 归一化    y = (y.astype(dtype) + offset) * step / img_shape[0]    x = (x.astype(dtype) + offset) * step / img_shape[1]    # 拓充维度,便于后面decode计算    y = np.expand_dims(y, axis=-1)    x = np.expand_dims(x, axis=-1)        # 每一个点框框的数量     num_anchors = len(sizes) + len(ratios)    h = np.zeros((num_anchors, ), dtype=dtype)    w = np.zeros((num_anchors, ), dtype=dtype)    # 第一个第二个框框是正方形    h[0] = sizes[0] / img_shape[0]    w[0] = sizes[0] / img_shape[1]    di = 1    if len(sizes) > 1:        h[1] = math.sqrt(sizes[0] * sizes[1]) / img_shape[0]        w[1] = math.sqrt(sizes[0] * sizes[1]) / img_shape[1]        di += 1    for i, r in enumerate(ratios):        h[i+di] = sizes[0] / img_shape[0] / math.sqrt(r)        w[i+di] = sizes[0] / img_shape[1] * math.sqrt(r)    return y, x, h, w

在看该部分的时候,需要结合参数,所用参数如下:

img_shape=(300, 300)feat_shapes=[(38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1)],# 先验框的sizeanchor_sizes=[(21., 45.),                (45., 99.),                (99., 153.),                (153., 207.),                (207., 261.),                (261., 315.)],# 框的数量为4,6,6,6,4,4# 框的数量为2+len(anchor_ratios[i])anchor_ratios=[[2, .5],                [2, .5, 3, 1./3],                [2, .5, 3, 1./3],                [2, .5, 3, 1./3],                [2, .5],                [2, .5]],# 放大倍数anchor_steps=[8, 16, 32, 64, 100, 300],

仔细研读这段代码会发现其设计非常巧妙哈。
x和y会执行归一化,到0,1之间,如果除去xy的最后一维进行plot,其会呈现一个0到1的网格,以38x38的特征层的先验框为例,其绘制出的网格如下,其实每一个点对应的就是每个框的中心点。

h和w对应着每个框的高与宽,宽高成一定比例。

4、bboxes_decode框的解码

# =============================解码部分============================= #def bboxes_decode(self, feat_localizations, anchors,                    scope='ssd_bboxes_decode'):    """    进行解码操作    """    return ssd_common.tf_ssd_bboxes_decode(        feat_localizations, anchors,        prior_scaling=self.params.prior_scaling,        scope=scope)

在bboxes_decode函数中,其调用了一个外部的函数ssd_common.tf_ssd_bboxes_decode,用于构建框的解码,其位于其它的文件中。
执行框的解码的原因是,利用net网络预测得到的locations并不是实际的框的位置,其需要与先验框结合处理后才能得到最后的框的位置。
这里需要注意的是,decode的过程需要两个参数的结合,分别是net网络构建得到的locationsanchor先验框生成得到的先验框
在进入ssd_common.tf_ssd_bboxes_decode函数后,其执行过程与anchor先验框生成类似,内部也有一个循环,意味着要对每一个特征层进行单独的处理。

def tf_ssd_bboxes_decode(feat_localizations,                         anchors,                         prior_scaling=[0.1, 0.1, 0.2, 0.2],                         scope='ssd_bboxes_decode'):    """      从ssd网络特性和先验框框计算相对边界框。    """    with tf.name_scope(scope):        bboxes = []        for i, anchors_layer in enumerate(anchors):            bboxes.append(                tf_ssd_bboxes_decode_layer(feat_localizations[i],                                           anchors_layer,                                           prior_scaling))        return bboxes

在如上的执行过程中,内部存在一个tf_ssd_bboxes_decode_layer函数,该部分是先验框生成的核心,在tf_ssd_bboxes_decode_layer中,程序会对每一个特征层的框进行解码
其输入包括,一个特征层的框的预测定位feat_localizations,每一层的先验框anchors_layer,先验框比率prior_scaling
执行过程:
1、 分解anchors_layer,因为anchors_layer由多个y,x,h,w构成
2、 计算cx和cy,这里存在一个计算公式,公式论文中给出了。
3、 计算cw和ch,这里存在一个计算公式,公式论文中给出了。
4、 将[cy - ch / 2.0, cx - cw / 2.0, cy + ch / 2.0, cx + cw / 2.0]输出,其对应左上角角点和右下角角点。
其输出包括:左上角角点和右下角角点的集合bboxes。
bboxes的shape为(?,block.shape[0],block.shape[1], boxes_len,4)
具体执行代码如下:

# =========================================================================== ## 编码解码部分# =========================================================================== #def tf_ssd_bboxes_decode_layer(feat_localizations,                               anchors_layer,                               prior_scaling=[0.1, 0.1, 0.2, 0.2]):    """    其输入包括,一个特征层的框的预测定位feat_localizations,每一层的先验框anchors_layer,先验框比率prior_scaling    执行过程:    1、分解anchors_layer,因为anchors_layer由多个y,x,h,w构成    2、计算cx和cy,这里存在一个计算公式    3、计算cw和ch,这里存在一个计算公式    4、将[cy - ch / 2.0, cx - cw / 2.0, cy + ch / 2.0, cx + cw / 2.0]输出,其对应左上角角点和右下角角点。    其输出包括:左上角角点和右下角角点的集合bboxes。    bboxes的shape为(?,block.shape[0],block.shape[1], boxes_len,4)    """    yref, xref, href, wref = anchors_layer    # 计算中心点和它的宽长    cx = feat_localizations[:, :, :, :, 0] * wref * prior_scaling[0] + xref    cy = feat_localizations[:, :, :, :, 1] * href * prior_scaling[1] + yref    w = wref * tf.exp(feat_localizations[:, :, :, :, 2] * prior_scaling[2])    h = href * tf.exp(feat_localizations[:, :, :, :, 3] * prior_scaling[3])    # 计算左上角点和右下角点    ymin = cy - h / 2.    xmin = cx - w / 2.    ymax = cy + h / 2.    xmax = cx + w / 2.    bboxes = tf.stack([ymin, xmin, ymax, xmax], axis=-1)    return bboxes

解码完后的bboxes表示某一个特征层中的框在真实图像中的位置。

利用ssd_vgg_300进行预测 预测步骤

进行预测需要进行以下步骤:
1、建立ssd对象
2、利用ssd_net = ssd_vgg_300.SSDNet()获得网络,得到两个tensorflow格式的预测结果。
3、载入ssd模型。
4、读入图片image_names。
5、将图片预处理后,传入网络结构,获得预测结果,预测结果包括 框的位置、每个框的预测结果。
6、利用ssd_bboxes_select函数选择得分高于门限的框。
7、对所有的得分进行排序,取出得分top400的框
8、非极大值抑制,该部分用于去除重复率过高的框。
9、在原图中绘制框框。

具体预测过程 1、图片的预处理

图片预处理时,需要调用如下代码:

# 输入图片大小net_shape = (300, 300)# data_format 设置为 "NHWC" 时,排列顺序为 [batch, height, width, channels]# 具体使用方法可以查看该网址:https://www.jianshu.com/p/d8a699745529data_format = 'NHWC'# img_input的placeholderimg_input = tf.placeholder(tf.uint8, shape = (None, None, 3))# 对图片进行预处理,得到bbox_img和image_4dimage_pre, labels_pre, bboxes_pre, bbox_img = ssd_vgg_preprocessing.preprocess_for_eval(    img_input, None, None, net_shape, data_format, resize = ssd_vgg_preprocessing.Resize.WARP_RESIZE)# 由于只检测一张图片,所以需要在第一维添加一维度image_4d = tf.expand_dims(image_pre, 0)

看起来代码很长,特别是倒数第二段代码,但是其实里面什么也没有。
ssd_vgg_preprocessing.preprocess_for_eval的主要执行过程就是:
1、将image减去voc2012得到的所有图片的RGB平均值。
2、增加比例预处理框(这个的作用我不太懂,我觉得它的意思应该就是这个图片可能是一个大图片里面截出的一小个图片,需要对这个比例进行缩放,但是实际运用的时候应该就是一个大图片)。
3、将图片resize到300x300。
4、判断使用CPU还是GPU。

def preprocess_for_eval(image, labels, bboxes,                        out_shape=EVAL_SIZE, data_format='NHWC',                        difficults=None, resize=Resize.WARP_RESIZE,                        scope='ssd_preprocessing_train'):    """    预处理    """    with tf.name_scope(scope):        if image.get_shape().ndims != 3:            raise ValueError('Input must be of size [height, width, C>0]')                # 将image减去voc2012得到的所有图片的RGB平均值        image = tf.to_float(image)        image = tf_image_whitened(image, [_R_MEAN, _G_MEAN, _B_MEAN])        # 增加比例预处理框        bbox_img = tf.constant([[0., 0., 1., 1.]])        if bboxes is None:            bboxes = bbox_img        else:            bboxes = tf.concat([bbox_img, bboxes], axis=0)        # 这一大段其实只调用了最后一个elif        # 将图片resize到300x300        if resize == Resize.NONE:            # No resizing...            pass        elif resize == Resize.CENTRAL_CROP:            # Central cropping of the image.            image, bboxes = tf_image.resize_image_bboxes_with_crop_or_pad(                image, bboxes, out_shape[0], out_shape[1])        elif resize == Resize.PAD_AND_RESIZE:            # Resize image first: find the correct factor...            shape = tf.shape(image)            factor = tf.minimum(tf.to_double(1.0),                                tf.minimum(tf.to_double(out_shape[0] / shape[0]),                                           tf.to_double(out_shape[1] / shape[1])))            resize_shape = factor * tf.to_double(shape[0:2])            resize_shape = tf.cast(tf.floor(resize_shape), tf.int32)            image = tf_image.resize_image(image, resize_shape,                                          method=tf.image.ResizeMethod.BILINEAR,                                          align_corners=False)            # Pad to expected size.            image, bboxes = tf_image.resize_image_bboxes_with_crop_or_pad(                image, bboxes, out_shape[0], out_shape[1])        elif resize == Resize.WARP_RESIZE:            # Warp resize of the image.            image = tf_image.resize_image(image, out_shape,                                          method=tf.image.ResizeMethod.BILINEAR,                                          align_corners=False)        # 分割比例box        bbox_img = bboxes[0]        bboxes = bboxes[1:]        # ……不知道干嘛        if difficults is not None:            mask = tf.logical_not(tf.cast(difficults, tf.bool))            labels = tf.boolean_mask(labels, mask)            bboxes = tf.boolean_mask(bboxes, mask)        # 看使用cpu还是GPU        if data_format == 'NCHW':            image = tf.transpose(image, perm=(2, 0, 1))        return image, labels, bboxes, bbox_img

2、载入ssd模型

载入ssd模型分为以下几步:
1、建立Session会话
2、建立ssd网络
3、载入模型
执行代码如下:

# 载入ssd的模型# 建立Session()isess = tf.Session()reuse = True if 'ssd_net' in locals() else None# 建立网络ssd_net = ssd_vgg_300.SSDNet()with slim.arg_scope(ssd_net.arg_scope(data_format = data_format)):    predictions, localisations, _, _ = ssd_net.net(image_4d, is_training = False, reuse = reuse)# 载入模型ckpt_filename = 'D:/Collection/SSD-Tensorflow-master/logs/model.ckpt-18602'isess.run(tf.global_variables_initializer())saver = tf.train.Saver()saver.restore(isess, ckpt_filename)

3、读取图片进行预测

该部分需要进行如下操作:
1、获取先验框。
2、读取图片。
3、将图片放入已经完成载入的模型,得到predictions和locations。
4、将每个特征层的预测结果都进行筛选,得分小于threshold的都剔除,并使得所有特征层的预测结果都并排存入一个list。
5、对所有的预测结果进行得分的排序,取出top400的框框。
6、进行非极大抑制,取出重复率过高的框。
7、在原图中绘制框。

具体执行代码如下:

# 获得所有先验框,六个特征层的ssd_anchors = ssd_net.anchors(net_shape) def process_image(img, select_threshold = 0.5, nms_threshold = .45, net_shape = (300, 300)):    # 运行SSD模型    rimg, rpredictions, rlocalisations, rbbox_img = isess.run([image_4d, predictions, localisations, bbox_img],                                                              feed_dict = {img_input: img})    # 得到20个类的得分,框框的位置    rclasses, rscores, rbboxes = np_methods.ssd_bboxes_select(        rpredictions, rlocalisations, ssd_anchors,        select_threshold = select_threshold, img_shape = net_shape, num_classes = 21, decode = True)        # 防止超出边界    rbboxes = np_methods.bboxes_clip(rbbox_img, rbboxes)    # 取出top400,并通过极大值抑制除去类似框    rclasses, rscores, rbboxes = np_methods.bboxes_sort(rclasses, rscores, rbboxes, top_k = 400)    rclasses, rscores, rbboxes = np_methods.bboxes_nms(rclasses, rscores, rbboxes, nms_threshold = nms_threshold)        # 在img里进行等比例缩放resize    rbboxes = np_methods.bboxes_resize(rbbox_img, rbboxes)    return rclasses, rscores, rbboxes  # 读取图片img = mpimg.imread('./street.jpg')# 进行图片的预测rclasses, rscores, rbboxes = process_image(img) visualization.plt_bboxes(img, rclasses, rscores, rbboxes)

其中,预测结果筛选的代码如下:
该部分首先解码;再将每个特征层进行reshape完成平铺;读出除去背景的得分;将得分多余threshold的类进行保存,小于的进行剔除;利用np.concatenate将结果同一排摆放。

def ssd_bboxes_select_layer(predictions_layer,                            localizations_layer,                            anchors_layer,                            select_threshold=0.5,                            img_shape=(300, 300),                            num_classes=21,                            decode=True):    """        选择大于门限的框    """    # 对框进行解码    if decode:        localizations_layer = ssd_bboxes_decode(localizations_layer, anchors_layer)    # 将所有预测结果变为3维,第一维度维batch,第二维度为size,第三维度为class_num | 4    p_shape = predictions_layer.shape    batch_size = p_shape[0] if len(p_shape) == 5 else 1    predictions_layer = np.reshape(predictions_layer,                                   (batch_size, -1, p_shape[-1]))    l_shape = localizations_layer.shape    localizations_layer = np.reshape(localizations_layer,                                     (batch_size, -1, l_shape[-1]))    if select_threshold is None or select_threshold == 0:        classes = np.argmax(predictions_layer, axis=2)        scores = np.amax(predictions_layer, axis=2)        mask = (classes > 0)        classes = classes[mask]        scores = scores[mask]        bboxes = localizations_layer[mask]    else:        # 取出所有的预测结果        sub_predictions = predictions_layer[:, :, 1:]        # 判断哪里的预测结果大于门限        idxes = np.where(sub_predictions > select_threshold)        # 如果大于门限则留下,并+1,除去背景        classes = idxes[-1]+1        # 取出所有分数        scores = sub_predictions[idxes]        # 和框的位置        bboxes = localizations_layer[idxes[:-1]]    return classes, scores, bboxes

对所有的预测结果进行得分的排序,取出top400的框框的过程非常简单,代码如下:
首先利用argsort对得分进行排序,并从大到小排序得分的序号;取出种类classes、得分scores、框bboxes的top400个。

def bboxes_sort(classes, scores, bboxes, top_k=400):    """    进行排序筛选    """    idxes = np.argsort(-scores)    classes = classes[idxes][:top_k]    scores = scores[idxes][:top_k]    bboxes = bboxes[idxes][:top_k]    return classes, scores, bboxes

进行非极大抑制的过程也比较简单,具体代码如下:
将bboxes中每一个框,从得分最高到得分最低依次与其之后所有的框比较;IOU较小或者属于不同类的框得到保留;

def bboxes_nms(classes, scores, bboxes, nms_threshold=0.45):    """    非极大抑制,去除重复率过大的框.    """    keep_bboxes = np.ones(scores.shape, dtype=np.bool)    for i in range(scores.size-1):        if keep_bboxes[i]:            # 计算重叠区域            overlap = bboxes_jaccard(bboxes[i], bboxes[(i+1):])            # 保留重叠区域不是很大或者种类不同的            keep_overlap = np.logical_or(overlap < nms_threshold, classes[(i+1):] != classes[i])            keep_bboxes[(i+1):] = np.logical_and(keep_bboxes[(i+1):], keep_overlap)    # 保留重叠部分小或者种类不同的    idxes = np.where(keep_bboxes)    return classes[idxes], scores[idxes], bboxes[idxes]

4、全部预测代码

import osimport mathimport randomimport numpy as npimport tensorflow as tfimport cv2import matplotlib.pyplot as pltimport matplotlib.image as mpimgimport sys sys.path.append('./') from nets import ssd_vgg_300, ssd_common, np_methodsfrom preprocessing import ssd_vgg_preprocessingfrom notebooks import visualization# 构建slim框架。slim = tf.contrib.slim# 输入图片大小net_shape = (300, 300)# data_format 设置为 "NHWC" 时,排列顺序为 [batch, height, width, channels]# 具体使用方法可以查看:https://www.jianshu.com/p/d8a699745529。data_format = 'NHWC'# img_input的placeholderimg_input = tf.placeholder(tf.uint8, shape = (None, None, 3))# 对图片进行预处理,得到bbox_img和image_4dimage_pre, labels_pre, bboxes_pre, bbox_img = ssd_vgg_preprocessing.preprocess_for_eval(    img_input, None, None, net_shape, data_format, resize = ssd_vgg_preprocessing.Resize.WARP_RESIZE)# 由于只检测一张图片,所以需要在第一维添加一维度image_4d = tf.expand_dims(image_pre, 0)# 载入ssd的模型# 建立Session()isess = tf.Session()reuse = True if 'ssd_net' in locals() else None# 建立网络ssd_net = ssd_vgg_300.SSDNet()with slim.arg_scope(ssd_net.arg_scope(data_format = data_format)):    predictions, localisations, _, _ = ssd_net.net(image_4d, is_training = False, reuse = reuse)# 载入模型ckpt_filename = './logs/model.ckpt-1498'isess.run(tf.global_variables_initializer())saver = tf.train.Saver()saver.restore(isess, ckpt_filename) # 获得所有先验框,六个特征层的ssd_anchors = ssd_net.anchors(net_shape) def process_image(img, select_threshold = 0.5, nms_threshold = .45, net_shape = (300, 300)):    # 运行SSD模型    rimg, rpredictions, rlocalisations, rbbox_img = isess.run([image_4d, predictions, localisations, bbox_img],                                                              feed_dict = {img_input: img})    # 得到20个类的得分,框框的位置    rclasses, rscores, rbboxes = np_methods.ssd_bboxes_select(        rpredictions, rlocalisations, ssd_anchors,        select_threshold = select_threshold, img_shape = net_shape, num_classes = 21, decode = True)        # 防止超出边界    rbboxes = np_methods.bboxes_clip(rbbox_img, rbboxes)    # 取出top400,并通过极大值抑制除去类似框    rclasses, rscores, rbboxes = np_methods.bboxes_sort(rclasses, rscores, rbboxes, top_k = 400)    rclasses, rscores, rbboxes = np_methods.bboxes_nms(rclasses, rscores, rbboxes, nms_threshold = nms_threshold)        # 在img里进行等比例缩放resize    rbboxes = np_methods.bboxes_resize(rbbox_img, rbboxes)    return rclasses, rscores, rbboxes  # 读取图片img = mpimg.imread('./street.jpg')# 进行图片的预测rclasses, rscores, rbboxes = process_image(img) visualization.plt_bboxes(img, rclasses, rscores, rbboxes)

以上就是python目标检测SSD算法预测部分源码详解的详细内容,更多关于python目标检测SSD算法预测的资料请关注我们其它相关文章!

(0)

相关推荐

  • python目标检测SSD算法预测部分源码详解

    目录 学习前言 什么是SSD算法 ssd_vgg_300主体的源码 学习前言 ……学习了很多有关目标检测的概念呀,咕噜咕噜,可是要怎么才能进行预测呢,我看了好久的SSD源码,将其中的预测部分提取了出来,训练部分我还没看懂 什么是SSD算法 SSD是一种非常优秀的one-stage方法,one-stage算法就是目标检测和分类是同时完成的,其主要思路是均匀地在图片的不同位置进行密集抽样,抽样时可以采用不同尺度和长宽比,然后利用CNN提取特征后直接进行分类与回归,整个过程只需要一步,所以其优势是速度

  • python目标检测SSD算法训练部分源码详解

    目录 学习前言 讲解构架 模型训练的流程 1.设置参数 2.读取数据集 3.建立ssd网络. 4.预处理数据集 5.框的编码 6.计算loss值 7.训练模型并保存 开始训练 学习前言 ……又看了很久的SSD算法,今天讲解一下训练部分的代码.预测部分的代码可以参照https://blog.csdn.net/weixin_44791964/article/details/102496765 讲解构架 本次教程的讲解主要是对训练部分的代码进行讲解,该部分讲解主要是对训练函数的执行过程与执行思路进行详

  • Android开发数据结构算法ArrayList源码详解

    目录 简介 ArrayList源码讲解 初始化 扩容 增加元素 一个元素 一堆元素 删除元素 一个元素 一堆元素 修改元素 查询元素 总结 ArrayList优点 ArrayList的缺点 简介 ArrayList是List接口的一个实现类,它是一个集合容器,我们通常会通过指定泛型来存储同一类数据,ArrayList默认容器大小为10,自身可以自动扩容,当容量不足时,扩大为原来的1.5倍,和上篇文章的Vector的最大区别应该就是线程安全了,ArrayList不能保证线程安全,但我们也可以通过其

  • Java并发编程之ConcurrentLinkedQueue源码详解

    一.ConcurrentLinkedQueue介绍 并编程中,一般需要用到安全的队列,如果要自己实现安全队列,可以使用2种方式: 方式1:加锁,这种实现方式就是我们常说的阻塞队列. 方式2:使用循环CAS算法实现,这种方式实现队列称之为非阻塞队列. 从点到面, 下面我们来看下非阻塞队列经典实现类:ConcurrentLinkedQueue (JDK1.8版) ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全的队列.当我们添加一个元素的时候,它会添加到队列的尾部,当我们

  • Android实现屏幕锁定源码详解

    最近有朋友问屏幕锁定的问题,自己也在学习,网上找了下也没太详细的例子,看的资料书上也没有有关屏幕锁定程序的介绍,下个小决心,自己照着官方文档学习下,现在做好了,废话不多说,先发下截图,看下效果,需要注意的地方会加注释,有问题的朋友可以直接留言,我们共同学习交流,共同提高进步!直接看效果图: 一:未设置密码时进入系统设置的效果图如下: 二:设置密码方式预览: 三:密码解密效果图 四:九宫格解密时的效果图 下面来简单的看下源码吧,此处讲下,这个小DEMO也是临时学习下的,有讲的不明白的地方请朋友直接

  • Spring AOP底层源码详解

    ProxyFactory的工作原理 ProxyFactory是一个代理对象生产工厂,在生成代理对象之前需要对代理工厂进行配置.ProxyFactory在生成代理对象之前需要决定到底是使用JDK动态代理还是CGLIB技术. // config就是ProxyFactory对象 // optimize为true,或proxyTargetClass为true,或用户没有给ProxyFactory对象添加interface if (config.isOptimize() || config.isProxy

  • Java8中AbstractExecutorService与FutureTask源码详解

    目录 前言 一.AbstractExecutorService 1.定义 2.submit 3.invokeAll 4.invokeAny 二.FutureTask 1.定义 2.构造方法 3.get 4.run/ runAndReset 5. cancel 三.ExecutorCompletionService 1.定义 2.submit 3.take/ poll 总结 前言 本篇博客重点讲解ThreadPoolExecutor的三个基础设施类AbstractExecutorService.F

  • Django Rest Framework实现身份认证源码详解

    目录 一.Django框架 二.身份认证的两种实现方式: 三.身份认证源码解析流程 一.Django框架 Django确实是一个很强大,用起来很爽的一个框架,在Rest Framework中已经将身份认证全都封装好了,用的时候直接导入authentication.py这个模块就好了.这个模块中5个认证类.但是我们在开发中很少用自带的认证类,而是根据项目实际需要去自己实现认证类.下面是内置的认证类 BaseAuthentication(object):所有的认证相关的类都继承自这个类,我们写的认证

  • Android线程间通信Handler源码详解

    目录 前言 01. 用法 02.源码 03.结语 前言 在[Android]线程间通信 - Handler之使用篇主要讲了 Handler 的创建,发送消息,处理消息 三个步骤.那么接下来,我们也按照这三个步骤,从源码中去探析一下它们具体是如何实现的.本篇是关于创建源码的分析. 01. 用法 先回顾一下,在主线程和非主线程是如何创建 Handler 的. //主线程 private val mHandler: Handler = object : Handler(Looper.getMainLo

  • Spring JPA联表查询之OneToOne源码详解

    目录 前言 源码 注解属性 单向联表 user 实体类 car 实体类 查询结果 双向联表 user 实体 car 实体 查询结果 延迟加载(懒加载) user 实体 查询结果: 查询完会发现,控制台又打印了一个 JPQL: 最后结论 前言 前面几篇我们学习的都是单表查询,就是对一张表中的数据进行查询.而实际项目中,基本都会有多张表联合查询的情况,今天我们就来了解下JPA的联表查询是如做的. 源码 @OneToOne 注解实现一对一关系映射.比如用户跟车辆的关系(这里假设一个人只能有一辆车),一

随机推荐