您现在的位置是:网站首页> 编程资料编程资料

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

2023-05-26 465人已围观

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

学习前言

……学习了很多有关目标检测的概念呀,咕噜咕噜,可是要怎么才能进行预测呢,我看了好久的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即可执行程序:

下载链接:https://pan.baidu.com/s/16UtXIfE-imrzjg_rx7xTKQ 

提取码:vpo2 

ssd_vgg_300主体的源码

本文使用的ssd_vgg_300的源码源于

链接:https://pan.baidu.com/s/1Wi1t9bYpTJEu5j3cq3pUnA 

提取码:6hye 

本文对其进行了简化,只保留了预测部分,便于理顺整个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_points ssd_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)

具体执行代码如下:

####################
                
                

-六神源码网