睿智的目標檢測22——Keras搭建RFBnet目標檢測平臺

學習前言

RFBnet是SSD的一種加強版,主要是利用了膨脹卷積這一方法增大了感受野,相比於普通的ssd,RFBnet也是一種加強吧。
在這裏插入圖片描述

什麼是RFBnet目標檢測算法

RFBnet是改進版的SSD,其整體的結構與SSD相差不大,其主要特點是在SSD的特徵提取網絡上用了RFB模塊。
RFB的全稱Receptive Field Block,是一種輕量級的、而且集成了各類檢測算法優點的模塊,結合了Inception、蟲洞卷積的思想,以提高感受野的方式提高網絡的特徵提取能力。
在這裏插入圖片描述

源碼下載

https://github.com/bubbliiiing/RFB-keras
喜歡的可以點個star噢。

RFBnet實現思路

一、預測部分

1、主幹網絡介紹

在這裏插入圖片描述
RFBnet採用的主幹網絡是VGG網絡,關於VGG的介紹大家可以看我的另外一篇博客https://blog.csdn.net/weixin_44791964/article/details/102779878,這裏的VGG網絡相比普通的VGG網絡有一定的修改,主要修改的地方就是:
1、將VGG16的FC6和FC7層轉化爲卷積層。
2、增加了RFB模塊。

主要使用到的RFB模塊有兩種,一種是BasicRFB,另一種是BasicRFB_a。
二者使用的思想相同,構造有些許不同。
BasicRFB的結構如下:
在這裏插入圖片描述
BasicRFB_a和BasicRFB類似,並聯結構增加,有8個並聯。

實現代碼:

import keras.backend as K
from keras.layers import Activation
from keras.layers import Conv2D
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import GlobalAveragePooling2D
from keras.layers import Input,Lambda
from keras.layers import MaxPooling2D,Concatenate,UpSampling2D
from keras.layers import merge, concatenate
from keras.layers import Reshape
from keras.layers import ZeroPadding2D,BatchNormalization
from keras.models import Model


def conv2d_bn(x,filters,num_row,num_col,padding='same',stride=1,dilation_rate=1,relu=True):
    x = Conv2D(
        filters, (num_row, num_col),
        strides=(stride,stride),
        padding=padding,
        dilation_rate=(dilation_rate, dilation_rate),
        use_bias=False)(x)
    x = BatchNormalization(scale=False)(x)
    if relu:    
        x = Activation("relu")(x)
    return x


def BasicRFB(x,input_filters,output_filters,stride=1,map_reduce=8):
    input_filters_div = input_filters//map_reduce

    branch_0 = conv2d_bn(x,input_filters_div*2,1,1,stride=stride)
    branch_0 = conv2d_bn(branch_0,input_filters_div*2,3,3,relu=False)

    branch_1 = conv2d_bn(x,input_filters_div,1,1)
    branch_1 = conv2d_bn(branch_1,input_filters_div*2,3,3,stride=stride)
    branch_1 = conv2d_bn(branch_1,input_filters_div*2,3,3,dilation_rate=3,relu=False)
    
    branch_2 = conv2d_bn(x,input_filters_div,1,1)
    branch_2 = conv2d_bn(branch_2,(input_filters_div//2)*3,3,3)
    branch_2 = conv2d_bn(branch_2,input_filters_div*2,3,3,stride=stride)
    branch_2 = conv2d_bn(branch_2,input_filters_div*2,3,3,dilation_rate=5,relu=False)

    branch_3 = conv2d_bn(x,input_filters_div,1,1)
    branch_3 = conv2d_bn(branch_3,(input_filters_div//2)*3,1,7)
    branch_3 = conv2d_bn(branch_3,input_filters_div*2,7,1,stride=stride)
    branch_3 = conv2d_bn(branch_3,input_filters_div*2,3,3,dilation_rate=7,relu=False)

    out = concatenate([branch_0,branch_1,branch_2,branch_3],axis=-1)
    out = conv2d_bn(out,output_filters,1,1,relu=False)

    short = conv2d_bn(x,output_filters,1,1,stride=stride,relu=False)
    out = Lambda(lambda x: x[0] + x[1])([out,short])
    out = Activation("relu")(out)
    return out

def BasicRFB_c(x,input_filters,output_filters,stride=1,map_reduce=8):
    input_filters_div = input_filters//map_reduce

    branch_0 = conv2d_bn(x,input_filters_div*2,1,1,stride=stride)
    branch_0 = conv2d_bn(branch_0,input_filters_div*2,3,3,relu=False)

    branch_1 = conv2d_bn(x,input_filters_div,1,1)
    branch_1 = conv2d_bn(branch_1,input_filters_div*2,3,3,stride=stride)
    branch_1 = conv2d_bn(branch_1,input_filters_div*2,3,3,dilation_rate=3,relu=False)
    
    branch_2 = conv2d_bn(x,input_filters_div,1,1)
    branch_2 = conv2d_bn(branch_2,(input_filters_div//2)*3,1,7)
    branch_2 = conv2d_bn(branch_2,input_filters_div*2,7,1,stride=stride)
    branch_2 = conv2d_bn(branch_2,input_filters_div*2,3,3,dilation_rate=7,relu=False)

    out = concatenate([branch_0,branch_1,branch_2],axis=-1)
    out = conv2d_bn(out,output_filters,1,1,relu=False)

    short = conv2d_bn(x,output_filters,1,1,stride=stride,relu=False)
    out = Lambda(lambda x: x[0] + x[1])([out,short])
    out = Activation("relu")(out)
    return out

def BasicRFB_a(x,input_filters,output_filters,stride=1,map_reduce=8):
    input_filters_div = input_filters//map_reduce

    branch_0 = conv2d_bn(x,input_filters_div,1,1,stride=stride)
    branch_0 = conv2d_bn(branch_0,input_filters_div,3,3,relu=False)

    branch_1 = conv2d_bn(x,input_filters_div,1,1)
    branch_1 = conv2d_bn(branch_1,input_filters_div,3,1)
    branch_1 = conv2d_bn(branch_1,input_filters_div,3,3,dilation_rate=3,relu=False)
    
    branch_2 = conv2d_bn(x,input_filters_div,1,1)
    branch_2 = conv2d_bn(branch_2,input_filters_div,1,3)
    branch_2 = conv2d_bn(branch_2,input_filters_div,3,3,dilation_rate=3,relu=False)

    branch_3 = conv2d_bn(x,input_filters_div,1,1)
    branch_3 = conv2d_bn(branch_3,input_filters_div,3,1)
    branch_3 = conv2d_bn(branch_3,input_filters_div,3,3,dilation_rate=5,relu=False)
    
    branch_4 = conv2d_bn(x,input_filters_div,1,1)
    branch_4 = conv2d_bn(branch_4,input_filters_div,1,3)
    branch_4 = conv2d_bn(branch_4,input_filters_div,3,3,dilation_rate=5,relu=False)

    branch_5 = conv2d_bn(x,input_filters_div//2,1,1)
    branch_5 = conv2d_bn(branch_5,(input_filters_div//4)*3,1,3)
    branch_5 = conv2d_bn(branch_5,input_filters_div,3,1,stride=stride)
    branch_5 = conv2d_bn(branch_5,input_filters_div,3,3,dilation_rate=7,relu=False)

    branch_6 = conv2d_bn(x,input_filters_div//2,1,1)
    branch_6 = conv2d_bn(branch_6,(input_filters_div//4)*3,3,1)
    branch_6 = conv2d_bn(branch_6,input_filters_div,1,3,stride=stride)
    branch_6 = conv2d_bn(branch_6,input_filters_div,3,3,dilation_rate=7,relu=False)

    out = concatenate([branch_0,branch_1,branch_2,branch_3,branch_4,branch_5,branch_6],axis=-1)
    out = conv2d_bn(out,output_filters,1,1,relu=False)

    short = conv2d_bn(x,output_filters,1,1,stride=stride,relu=False)
    out = Lambda(lambda x: x[0] + x[1])([out,short])
    out = Activation("relu")(out)
    return out


def Normalize(net):
    branch_0 = conv2d_bn(net["conv4_3"],256,1,1)
    branch_1 = conv2d_bn(net['fc7'],256,1,1)
    branch_1 = UpSampling2D()(branch_1)
    out = concatenate([branch_0,branch_1],axis=-1)
    out = BasicRFB_a(out,512,512)
    return out

def backbone(input_tensor):
    #----------------------------主幹特徵提取網絡開始---------------------------#
    # SSD結構,net字典
    net = {} 
    # Block 1
    net['input'] = input_tensor
    # 300,300,3 -> 150,150,64
    net['conv1_1'] = Conv2D(64, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv1_1')(net['input'])
    net['conv1_2'] = Conv2D(64, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv1_2')(net['conv1_1'])
    net['pool1'] = MaxPooling2D((2, 2), strides=(2, 2), padding='same',
                                name='pool1')(net['conv1_2'])

    
    # Block 2
    # 150,150,64 -> 75,75,128
    net['conv2_1'] = Conv2D(128, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv2_1')(net['pool1'])
    net['conv2_2'] = Conv2D(128, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv2_2')(net['conv2_1'])
    net['pool2'] = MaxPooling2D((2, 2), strides=(2, 2), padding='same',
                                name='pool2')(net['conv2_2'])
    # Block 3
    # 75,75,128 -> 38,38,256
    net['conv3_1'] = Conv2D(256, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv3_1')(net['pool2'])
    net['conv3_2'] = Conv2D(256, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv3_2')(net['conv3_1'])
    net['conv3_3'] = Conv2D(256, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv3_3')(net['conv3_2'])
    net['pool3'] = MaxPooling2D((2, 2), strides=(2, 2), padding='same',
                                name='pool3')(net['conv3_3'])
    # Block 4
    # 38,38,256 -> 19,19,512
    net['conv4_1'] = Conv2D(512, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv4_1')(net['pool3'])
    net['conv4_2'] = Conv2D(512, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv4_2')(net['conv4_1'])
    net['conv4_3'] = Conv2D(512, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv4_3')(net['conv4_2'])
    net['pool4'] = MaxPooling2D((2, 2), strides=(2, 2), padding='same',
                                name='pool4')(net['conv4_3'])
    # Block 5
    # 19,19,512 -> 19,19,512
    net['conv5_1'] = Conv2D(512, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv5_1')(net['pool4'])
    net['conv5_2'] = Conv2D(512, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv5_2')(net['conv5_1'])
    net['conv5_3'] = Conv2D(512, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv5_3')(net['conv5_2'])
    net['pool5'] = MaxPooling2D((3, 3), strides=(1, 1), padding='same',
                                name='pool5')(net['conv5_3'])
    # FC6
    # 19,19,512 -> 19,19,1024
    net['fc6'] = Conv2D(1024, kernel_size=(3,3), dilation_rate=(6, 6),
                                     activation='relu', padding='same',
                                     name='fc6')(net['pool5'])

    # x = Dropout(0.5, name='drop6')(x)
    # FC7
    # 19,19,1024 -> 19,19,1024
    net['fc7'] = Conv2D(1024, kernel_size=(1,1), activation='relu',
                               padding='same', name='fc7')(net['fc6'])

    net['norm'] = Normalize(net)

    net['rfb_1'] = BasicRFB(net['fc7'],1024,1024)

    net['rfb_2'] = BasicRFB(net['rfb_1'],1024,512,stride=2)

    net['rfb_3'] = BasicRFB(net['rfb_2'],512,256,stride=2)

    net['conv6_1'] = conv2d_bn(net['rfb_3'],128,1,1)

    net['conv6_2'] = conv2d_bn(net['conv6_1'],256,3,3,padding="valid")

    net['conv7_1'] = conv2d_bn(net['conv6_2'],128,1,1)

    net['conv7_2'] = conv2d_bn(net['conv7_1'],256,3,3,padding="valid")
    return net

2、從特徵獲取預測結果

在這裏插入圖片描述
由上圖我們可以知道,我們取conv4的第三次卷積的特徵、fc7的特徵進行組合後經過一個BasicRFB_a獲得P3作爲有效特徵層、還有上圖的P4、P5、P6、P7、P8作爲有效特徵層,爲了和普通特徵層區分,我們稱之爲有效特徵層,來獲取預測結果。

對獲取到的每一個有效特徵層,我們分別對其進行一次num_priors x 4的卷積、一次num_priors x num_classes的卷積、並需要計算每一個有效特徵層對應的先驗框。而num_priors指的是該特徵層所擁有的先驗框數量。

其中:
num_priors x 4的卷積 用於預測 該特徵層上 每一個網格點上 每一個先驗框的變化情況。(爲什麼說是變化情況呢,這是因爲ssd的預測結果需要結合先驗框獲得預測框,預測結果就是先驗框的變化情況。)

num_priors x num_classes的卷積 用於預測 該特徵層上 每一個網格點上 每一個預測框對應的種類。

每一個有效特徵層對應的先驗框對應着該特徵層上 每一個網格點上 預先設定好的三個框。

所有的特徵層對應的預測結果的shape如下:
在這裏插入圖片描述
實現代碼爲:

import keras.backend as K
from keras.layers import Activation
from keras.layers import Conv2D
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import GlobalAveragePooling2D
from keras.layers import Input
from keras.layers import MaxPooling2D
from keras.layers import merge, concatenate
from keras.layers import Reshape
from keras.layers import ZeroPadding2D
from keras.models import Model
from nets.backbone import backbone
from nets.rfb_layers import PriorBox


def rfb300(input_shape, num_classes=21):
    # 300,300,3
    input_tensor = Input(shape=input_shape)
    img_size = (input_shape[1], input_shape[0])

    # RFB結構,net字典
    net = backbone(input_tensor)
    #-----------------------將提取到的主幹特徵進行處理---------------------------#
    # 對norm後的結果進行處理 38,38,512
    net['norm'] = net['norm']
    num_priors = 6
    # 預測框的處理
    # num_priors表示每個網格點先驗框的數量,4是x,y,h,w的調整
    net['norm_mbox_loc'] = Conv2D(num_priors * 4, kernel_size=(3,3), padding='same', name='norm_mbox_loc')(net['norm'])
    net['norm_mbox_loc_flat'] = Flatten(name='norm_mbox_loc_flat')(net['norm_mbox_loc'])
    # num_priors表示每個網格點先驗框的數量,num_classes是所分的類
    net['norm_mbox_conf'] = Conv2D(num_priors * num_classes, kernel_size=(3,3), padding='same',name='norm_mbox_conf')(net['norm'])
    net['norm_mbox_conf_flat'] = Flatten(name='norm_mbox_conf_flat')(net['norm_mbox_conf'])
    priorbox = PriorBox(img_size, 30.0,max_size = 60.0, aspect_ratios=[2, 3],
                        variances=[0.1, 0.1, 0.2, 0.2],
                        name='norm_mbox_priorbox')
    net['norm_mbox_priorbox'] = priorbox(net['norm'])
    
    # 對rfb_1層進行處理 
    num_priors = 6
    # 預測框的處理
    # num_priors表示每個網格點先驗框的數量,4是x,y,h,w的調整
    net['rfb_1_mbox_loc'] = Conv2D(num_priors * 4, kernel_size=(3,3),padding='same',name='rfb_1_mbox_loc')(net['rfb_1'])
    net['rfb_1_mbox_loc_flat'] = Flatten(name='rfb_1_mbox_loc_flat')(net['rfb_1_mbox_loc'])
    # num_priors表示每個網格點先驗框的數量,num_classes是所分的類
    net['rfb_1_mbox_conf'] = Conv2D(num_priors * num_classes, kernel_size=(3,3),padding='same',name='rfb_1_mbox_conf')(net['rfb_1'])
    net['rfb_1_mbox_conf_flat'] = Flatten(name='rfb_1_mbox_conf_flat')(net['rfb_1_mbox_conf'])

    priorbox = PriorBox(img_size, 60.0, max_size=111.0, aspect_ratios=[2, 3],
                        variances=[0.1, 0.1, 0.2, 0.2],
                        name='rfb_1_mbox_priorbox')
    net['rfb_1_mbox_priorbox'] = priorbox(net['rfb_1'])

    # 對rfb_2進行處理
    num_priors = 6
    # 預測框的處理
    # num_priors表示每個網格點先驗框的數量,4是x,y,h,w的調整
    x = Conv2D(num_priors * 4, kernel_size=(3,3), padding='same',name='rfb_2_mbox_loc')(net['rfb_2'])
    net['rfb_2_mbox_loc'] = x
    net['rfb_2_mbox_loc_flat'] = Flatten(name='rfb_2_mbox_loc_flat')(net['rfb_2_mbox_loc'])
    # num_priors表示每個網格點先驗框的數量,num_classes是所分的類
    x = Conv2D(num_priors * num_classes, kernel_size=(3,3), padding='same',name='rfb_2_mbox_conf')(net['rfb_2'])
    net['rfb_2_mbox_conf'] = x
    net['rfb_2_mbox_conf_flat'] = Flatten(name='rfb_2_mbox_conf_flat')(net['rfb_2_mbox_conf'])

    priorbox = PriorBox(img_size, 111.0, max_size=162.0, aspect_ratios=[2, 3],
                        variances=[0.1, 0.1, 0.2, 0.2],
                        name='rfb_2_mbox_priorbox')
    net['rfb_2_mbox_priorbox'] = priorbox(net['rfb_2'])

    # 對rfb_3進行處理
    num_priors = 6
    # 預測框的處理
    # num_priors表示每個網格點先驗框的數量,4是x,y,h,w的調整
    x = Conv2D(num_priors * 4, kernel_size=(3,3), padding='same',name='rfb_3_mbox_loc')(net['rfb_3'])
    net['rfb_3_mbox_loc'] = x
    net['rfb_3_mbox_loc_flat'] = Flatten(name='rfb_3_mbox_loc_flat')(net['rfb_3_mbox_loc'])
    # num_priors表示每個網格點先驗框的數量,num_classes是所分的類
    x = Conv2D(num_priors * num_classes, kernel_size=(3,3), padding='same',name='rfb_3_mbox_conf')(net['rfb_3'])
    net['rfb_3_mbox_conf'] = x
    net['rfb_3_mbox_conf_flat'] = Flatten(name='rfb_3_mbox_conf_flat')(net['rfb_3_mbox_conf'])

    priorbox = PriorBox(img_size, 162.0, max_size=213.0, aspect_ratios=[2, 3],
                        variances=[0.1, 0.1, 0.2, 0.2],
                        name='rfb_3_mbox_priorbox')
    net['rfb_3_mbox_priorbox'] = priorbox(net['rfb_3'])

    # 對conv6_2進行處理
    num_priors = 4
    # 預測框的處理
    # num_priors表示每個網格點先驗框的數量,4是x,y,h,w的調整
    x = Conv2D(num_priors * 4, kernel_size=(3,3), padding='same',name='conv6_2_mbox_loc')(net['conv6_2'])
    net['conv6_2_mbox_loc'] = x
    net['conv6_2_mbox_loc_flat'] = Flatten(name='conv6_2_mbox_loc_flat')(net['conv6_2_mbox_loc'])
    # num_priors表示每個網格點先驗框的數量,num_classes是所分的類
    x = Conv2D(num_priors * num_classes, kernel_size=(3,3), padding='same',name='conv6_2_mbox_conf')(net['conv6_2'])
    net['conv6_2_mbox_conf'] = x
    net['conv6_2_mbox_conf_flat'] = Flatten(name='conv6_2_mbox_conf_flat')(net['conv6_2_mbox_conf'])

    priorbox = PriorBox(img_size, 213.0, max_size=264.0, aspect_ratios=[2],
                        variances=[0.1, 0.1, 0.2, 0.2],
                        name='conv6_2_mbox_priorbox')
    net['conv6_2_mbox_priorbox'] = priorbox(net['conv6_2'])

    # 對conv7_2進行處理
    num_priors = 4
    # 預測框的處理
    # num_priors表示每個網格點先驗框的數量,4是x,y,h,w的調整
    x = Conv2D(num_priors * 4, kernel_size=(3,3), padding='same',name='conv7_2_mbox_loc')(net['conv7_2'])
    net['conv7_2_mbox_loc'] = x
    net['conv7_2_mbox_loc_flat'] = Flatten(name='conv7_2_mbox_loc_flat')(net['conv7_2_mbox_loc'])
    # num_priors表示每個網格點先驗框的數量,num_classes是所分的類
    x = Conv2D(num_priors * num_classes, kernel_size=(3,3), padding='same',name='conv7_2_mbox_conf')(net['conv7_2'])
    net['conv7_2_mbox_conf'] = x
    net['conv7_2_mbox_conf_flat'] = Flatten(name='conv7_2_mbox_conf_flat')(net['conv7_2_mbox_conf'])
    
    priorbox = PriorBox(img_size, 264.0, max_size=315.0, aspect_ratios=[2],
                        variances=[0.1, 0.1, 0.2, 0.2],
                        name='conv7_2_mbox_priorbox')

    net['conv7_2_mbox_priorbox'] = priorbox(net['conv7_2'])

    # 將所有結果進行堆疊
    net['mbox_loc'] = concatenate([net['norm_mbox_loc_flat'],
                             net['rfb_1_mbox_loc_flat'],
                             net['rfb_2_mbox_loc_flat'],
                             net['rfb_3_mbox_loc_flat'],
                             net['conv6_2_mbox_loc_flat'],
                             net['conv7_2_mbox_loc_flat']],
                            axis=1, name='mbox_loc')
    net['mbox_conf'] = concatenate([net['norm_mbox_conf_flat'],
                              net['rfb_1_mbox_conf_flat'],
                              net['rfb_2_mbox_conf_flat'],
                              net['rfb_3_mbox_conf_flat'],
                              net['conv6_2_mbox_conf_flat'],
                              net['conv7_2_mbox_conf_flat']],
                             axis=1, name='mbox_conf')
    net['mbox_priorbox'] = concatenate([net['norm_mbox_priorbox'],
                                  net['rfb_1_mbox_priorbox'],
                                  net['rfb_2_mbox_priorbox'],
                                  net['rfb_3_mbox_priorbox'],
                                  net['conv6_2_mbox_priorbox'],
                                  net['conv7_2_mbox_priorbox']],
                                  axis=1, name='mbox_priorbox')

    if hasattr(net['mbox_loc'], '_keras_shape'):
        num_boxes = net['mbox_loc']._keras_shape[-1] // 4
    elif hasattr(net['mbox_loc'], 'int_shape'):
        num_boxes = K.int_shape(net['mbox_loc'])[-1] // 4

    net['mbox_loc'] = Reshape((num_boxes, 4),name='mbox_loc_final')(net['mbox_loc'])
    net['mbox_conf'] = Reshape((num_boxes, num_classes),name='mbox_conf_logits')(net['mbox_conf'])
    net['mbox_conf'] = Activation('softmax',name='mbox_conf_final')(net['mbox_conf'])

    net['predictions'] = concatenate([net['mbox_loc'],
                               net['mbox_conf'],
                               net['mbox_priorbox']],
                               axis=2, name='predictions')
                               
    model = Model(net['input'], net['predictions'])
    return model

3、預測結果的解碼

我們通過對每一個特徵層的處理,可以獲得三個內容,分別是:

num_priors x 4的卷積 用於預測 該特徵層上 每一個網格點上 每一個先驗框的變化情況。**

num_priors x num_classes的卷積 用於預測 該特徵層上 每一個網格點上 每一個預測框對應的種類。

每一個有效特徵層對應的先驗框對應着該特徵層上 每一個網格點上 預先設定好的多個框。

我們利用 num_priors x 4的卷積每一個有效特徵層對應的先驗框 獲得框的真實位置。

每一個有效特徵層對應的先驗框就是,如圖所示的作用:
每一個有效特徵層將整個圖片分成與其長寬對應的網格,如conv4-3和fl7組合成的特徵層就是將整個圖像分成38x38個網格;然後從每個網格中心建立多個先驗框,如conv4-3和fl7組合成的有效特徵層就是建立了6個先驗框;對於conv4-3和fl7組合成的特徵層來講,整個圖片被分成38x38個網格,每個網格中心對應6個先驗框,一共包含了,38x38x6個,8664個先驗框。
在這裏插入圖片描述
先驗框雖然可以代表一定的框的位置信息與框的大小信息,但是其是有限的,無法表示任意情況,因此還需要調整,RFBnet利用num_priors x 4的卷積的結果對先驗框進行調整。

num_priors x 4中的num_priors表示了這個網格點所包含的先驗框數量,其中的4表示了x_offset、y_offset、h和w的調整情況。

x_offset與y_offset代表了真實框距離先驗框中心的xy軸偏移情況。
h和w代表了真實框的寬與高相對於先驗框的變化情況。

RFBnet解碼過程就是將每個網格的中心點加上它對應的x_offset和y_offset,加完後的結果就是預測框的中心,然後再利用 先驗框和h、w結合 計算出預測框的長和寬。這樣就能得到整個預測框的位置了。

當然得到最終的預測結構後還要進行得分排序與非極大抑制篩選這一部分基本上是所有目標檢測通用的部分。
1、取出每一類得分大於self.obj_threshold的框和得分。
2、利用框的位置和得分進行非極大抑制。

實現代碼如下:

def decode_boxes(self, mbox_loc, mbox_priorbox, variances):
    # 獲得先驗框的寬與高
    prior_width = mbox_priorbox[:, 2] - mbox_priorbox[:, 0]
    prior_height = mbox_priorbox[:, 3] - mbox_priorbox[:, 1]
    # 獲得先驗框的中心點
    prior_center_x = 0.5 * (mbox_priorbox[:, 2] + mbox_priorbox[:, 0])
    prior_center_y = 0.5 * (mbox_priorbox[:, 3] + mbox_priorbox[:, 1])

    # 真實框距離先驗框中心的xy軸偏移情況
    decode_bbox_center_x = mbox_loc[:, 0] * prior_width * variances[:, 0]
    decode_bbox_center_x += prior_center_x
    decode_bbox_center_y = mbox_loc[:, 1] * prior_height * variances[:, 1]
    decode_bbox_center_y += prior_center_y
    
    # 真實框的寬與高的求取
    decode_bbox_width = np.exp(mbox_loc[:, 2] * variances[:, 2])
    decode_bbox_width *= prior_width
    decode_bbox_height = np.exp(mbox_loc[:, 3] * variances[:, 3])
    decode_bbox_height *= prior_height

    # 獲取真實框的左上角與右下角
    decode_bbox_xmin = decode_bbox_center_x - 0.5 * decode_bbox_width
    decode_bbox_ymin = decode_bbox_center_y - 0.5 * decode_bbox_height
    decode_bbox_xmax = decode_bbox_center_x + 0.5 * decode_bbox_width
    decode_bbox_ymax = decode_bbox_center_y + 0.5 * decode_bbox_height

    # 真實框的左上角與右下角進行堆疊
    decode_bbox = np.concatenate((decode_bbox_xmin[:, None],
                                    decode_bbox_ymin[:, None],
                                    decode_bbox_xmax[:, None],
                                    decode_bbox_ymax[:, None]), axis=-1)
    # 防止超出0與1
    decode_bbox = np.minimum(np.maximum(decode_bbox, 0.0), 1.0)
    return decode_bbox

def detection_out(self, predictions, background_label_id=0, keep_top_k=200,
                    confidence_threshold=0.5):
    # 網絡預測的結果
    mbox_loc = predictions[:, :, :4]
    # 0.1,0.1,0.2,0.2
    variances = predictions[:, :, -4:]
    # 先驗框
    mbox_priorbox = predictions[:, :, -8:-4]
    # 置信度
    mbox_conf = predictions[:, :, 4:-8]
    results = []
    # 對每一個特徵層進行處理
    for i in range(len(mbox_loc)):
        results.append([])
        decode_bbox = self.decode_boxes(mbox_loc[i], mbox_priorbox[i],  variances[i])

        for c in range(self.num_classes):
            if c == background_label_id:
                continue
            c_confs = mbox_conf[i, :, c]
            c_confs_m = c_confs > confidence_threshold
            if len(c_confs[c_confs_m]) > 0:
                # 取出得分高於confidence_threshold的框
                boxes_to_process = decode_bbox[c_confs_m]
                confs_to_process = c_confs[c_confs_m]
                # 進行iou的非極大抑制
                feed_dict = {self.boxes: boxes_to_process,
                                self.scores: confs_to_process}
                idx = self.sess.run(self.nms, feed_dict=feed_dict)
                # 取出在非極大抑制中效果較好的內容
                good_boxes = boxes_to_process[idx]
                confs = confs_to_process[idx][:, None]
                # 將label、置信度、框的位置進行堆疊。
                labels = c * np.ones((len(idx), 1))
                c_pred = np.concatenate((labels, confs, good_boxes),
                                        axis=1)
                # 添加進result裏
                results[-1].extend(c_pred)
        if len(results[-1]) > 0:
            # 按照置信度進行排序
            results[-1] = np.array(results[-1])
            argsort = np.argsort(results[-1][:, 1])[::-1]
            results[-1] = results[-1][argsort]
            # 選出置信度最大的keep_top_k個
            results[-1] = results[-1][:keep_top_k]
    return results

4、在原圖上進行繪製

通過第三步,我們可以獲得預測框在原圖上的位置,而且這些預測框都是經過篩選的。這些篩選後的框可以直接繪製在圖片上,就可以獲得結果了。

二、訓練部分

1、真實框的處理

從預測部分我們知道,每個特徵層的預測結果,num_priors x 4的卷積 用於預測 該特徵層上 每一個網格點上 每一個先驗框的變化情況。

也就是說,我們直接利用ssd網絡預測到的結果,並不是預測框在圖片上的真實位置,需要解碼才能得到真實位置。

而在訓練的時候,我們需要計算loss函數,這個loss函數是相對於RFB網絡的預測結果的。我們需要把圖片輸入到當前的RFB網絡中,得到預測結果;同時還需要把真實框的信息,進行編碼,這個編碼是把真實框的位置信息格式轉化爲RFB預測結果的格式信息

也就是,我們需要找到 每一張用於訓練的圖片每一個真實框對應的先驗框,並求出如果想要得到這樣一個真實框,我們的預測結果應該是怎麼樣的。

從預測結果獲得真實框的過程被稱作解碼,而從真實框獲得預測結果的過程就是編碼的過程。

因此我們只需要將解碼過程逆過來就是編碼過程了。

實現代碼如下:

def encode_box(self, box, return_iou=True):
    iou = self.iou(box)
    encoded_box = np.zeros((self.num_priors, 4 + return_iou))

    # 找到每一個真實框,重合程度較高的先驗框
    assign_mask = iou > self.overlap_threshold
    if not assign_mask.any():
        assign_mask[iou.argmax()] = True
    if return_iou:
        encoded_box[:, -1][assign_mask] = iou[assign_mask]
    # 找到對應的先驗框
    assigned_priors = self.priors[assign_mask]
    # 逆向編碼,將真實框轉化爲RFB預測結果的格式

    # 先計算真實框的中心與長寬
    box_center = 0.5 * (box[:2] + box[2:])
    box_wh = box[2:] - box[:2]
    # 再計算重合度較高的先驗框的中心與長寬
    assigned_priors_center = 0.5 * (assigned_priors[:, :2] +
                                    assigned_priors[:, 2:4])
    assigned_priors_wh = (assigned_priors[:, 2:4] -
                            assigned_priors[:, :2])
    
    # 逆向求取RFB應該有的預測結果
    encoded_box[:, :2][assign_mask] = box_center - assigned_priors_center
    encoded_box[:, :2][assign_mask] /= assigned_priors_wh
    # 除以0.1
    encoded_box[:, :2][assign_mask] /= assigned_priors[:, -4:-2]

    encoded_box[:, 2:4][assign_mask] = np.log(box_wh / assigned_priors_wh)
    # 除以0.2
    encoded_box[:, 2:4][assign_mask] /= assigned_priors[:, -2:]
    return encoded_box.ravel()

利用上述代碼我們可以獲得,真實框對應的所有的iou較大先驗框,並計算了真實框對應的所有iou較大的先驗框應該有的預測結果。

在訓練的時候我們只需要選擇iou最大的先驗框就行了,這個iou最大的先驗框就是我們用來預測這個真實框所用的先驗框。

因此我們還要經過一次篩選,將上述代碼獲得的真實框對應的所有的iou較大先驗框的預測結果中,iou最大的那個篩選出來。

通過assign_boxes我們就獲得了,輸入進來的這張圖片,應該有的預測結果是什麼樣子的。

實現代碼如下:

def assign_boxes(self, boxes):
    assignment = np.zeros((self.num_priors, 4 + self.num_classes + 8))
    assignment[:, 4] = 1.0
    if len(boxes) == 0:
        return assignment
    # 對每一個真實框都進行iou計算
    encoded_boxes = np.apply_along_axis(self.encode_box, 1, boxes[:, :4])
    # 每一個真實框的編碼後的值,和iou
    encoded_boxes = encoded_boxes.reshape(-1, self.num_priors, 5)
    
    # 取重合程度最大的先驗框,並且獲取這個先驗框的index
    best_iou = encoded_boxes[:, :, -1].max(axis=0)
    best_iou_idx = encoded_boxes[:, :, -1].argmax(axis=0)
    best_iou_mask = best_iou > 0
    best_iou_idx = best_iou_idx[best_iou_mask]
    assign_num = len(best_iou_idx)
    # 保留重合程度最大的先驗框的應該有的預測結果
    encoded_boxes = encoded_boxes[:, best_iou_mask, :]
    assignment[:, :4][best_iou_mask] = encoded_boxes[best_iou_idx,np.arange(assign_num),:4]
    # 4代表爲背景的概率,爲0
    assignment[:, 4][best_iou_mask] = 0
    assignment[:, 5:-8][best_iou_mask] = boxes[best_iou_idx, 4:]
    assignment[:, -8][best_iou_mask] = 1
    # 通過assign_boxes我們就獲得了,輸入進來的這張圖片,應該有的預測結果是什麼樣子的
    return assignment

2、利用處理完的真實框與對應圖片的預測結果計算loss

loss的計算分爲三個部分:
1、獲取所有正標籤的框的預測結果的迴歸loss。
2、獲取所有正標籤的種類的預測結果的交叉熵loss。
3、獲取一定負標籤的種類的預測結果的交叉熵loss。

由於在RFBnet的訓練過程中,正負樣本極其不平衡,即 存在對應真實框的先驗框可能只有十來個,但是不存在對應真實框的負樣本卻有幾千個,這就會導致負樣本的loss值極大,因此我們可以考慮減少負樣本的選取,對於ssd的訓練來講,常見的情況是取三倍正樣本數量的負樣本用於訓練。這個三倍呢,也可以修改,調整成自己喜歡的數字。

實現代碼如下:

class MultiboxLoss(object):
    def __init__(self, num_classes, alpha=1.0, neg_pos_ratio=3.0,
                 background_label_id=0, negatives_for_hard=100.0):
        self.num_classes = num_classes
        self.alpha = alpha
        self.neg_pos_ratio = neg_pos_ratio
        if background_label_id != 0:
            raise Exception('Only 0 as background label id is supported')
        self.background_label_id = background_label_id
        self.negatives_for_hard = negatives_for_hard

    def _l1_smooth_loss(self, y_true, y_pred):
        abs_loss = tf.abs(y_true - y_pred)
        sq_loss = 0.5 * (y_true - y_pred)**2
        l1_loss = tf.where(tf.less(abs_loss, 1.0), sq_loss, abs_loss - 0.5)
        return tf.reduce_mean(l1_loss, -1)

    def _softmax_loss(self, y_true, y_pred):
        y_pred = tf.maximum(tf.minimum(y_pred, 1 - 1e-7), 1e-7)
        softmax_loss = -tf.reduce_sum(y_true * tf.log(y_pred),
                                      axis=-1)
        return softmax_loss

    def compute_loss(self, y_true, y_pred):
        batch_size = tf.shape(y_true)[0]
        num_boxes = tf.to_float(tf.shape(y_true)[1])

        # 計算所有的loss
        # 分類的loss
        # batch_size,8732,21 -> batch_size,8732
        conf_loss = self._softmax_loss(y_true[:, :, 4:-8],
                                       y_pred[:, :, 4:-8])
        # 框的位置的loss
        # batch_size,8732,4 -> batch_size,8732
        loc_loss = self._l1_smooth_loss(y_true[:, :, :4],
                                        y_pred[:, :, :4])

        # 獲取所有的正標籤的loss
        # 每一張圖的pos的個數
        num_pos = tf.reduce_sum(y_true[:, :, -8], axis=-1)
        # 每一張圖的pos_loc_loss
        pos_loc_loss = tf.reduce_sum(loc_loss * y_true[:, :, -8],
                                     axis=1)
        # 每一張圖的pos_conf_loss
        pos_conf_loss = tf.reduce_sum(conf_loss * y_true[:, :, -8],
                                      axis=1)

        # 獲取一定的負樣本
        num_neg = tf.minimum(self.neg_pos_ratio * num_pos,
                             num_boxes - num_pos)

        # 找到了哪些值是大於0的
        pos_num_neg_mask = tf.greater(num_neg, 0)
        # 獲得一個1.0
        has_min = tf.to_float(tf.reduce_any(pos_num_neg_mask))
        num_neg = tf.concat( axis=0,values=[num_neg,
                                [(1 - has_min) * self.negatives_for_hard]])
        # 求平均每個圖片要取多少個負樣本
        num_neg_batch = tf.reduce_min(tf.boolean_mask(num_neg,
                                                      tf.greater(num_neg, 0)))
        num_neg_batch = tf.to_int32(num_neg_batch)

        # conf的起始
        confs_start = 4 + self.background_label_id + 1
        # conf的結束
        confs_end = confs_start + self.num_classes - 1

        # 找到實際上在該位置不應該有預測結果的框,求他們最大的置信度。
        max_confs = tf.reduce_max(y_pred[:, :, confs_start:confs_end],
                                  axis=2)

        # 取top_k個置信度,作爲負樣本
        _, indices = tf.nn.top_k(max_confs * (1 - y_true[:, :, -8]),
                                 k=num_neg_batch)

        # 找到其在1維上的索引
        batch_idx = tf.expand_dims(tf.range(0, batch_size), 1)
        batch_idx = tf.tile(batch_idx, (1, num_neg_batch))
        full_indices = (tf.reshape(batch_idx, [-1]) * tf.to_int32(num_boxes) +
                        tf.reshape(indices, [-1]))
        
        # full_indices = tf.concat(2, [tf.expand_dims(batch_idx, 2),
        #                              tf.expand_dims(indices, 2)])
        # neg_conf_loss = tf.gather_nd(conf_loss, full_indices)
        neg_conf_loss = tf.gather(tf.reshape(conf_loss, [-1]),
                                  full_indices)
        neg_conf_loss = tf.reshape(neg_conf_loss,
                                   [batch_size, num_neg_batch])
        neg_conf_loss = tf.reduce_sum(neg_conf_loss, axis=1)

        # loss is sum of positives and negatives
        
        num_pos = tf.where(tf.not_equal(num_pos, 0), num_pos,
                            tf.ones_like(num_pos))
        total_loss = tf.reduce_sum(pos_conf_loss) + tf.reduce_sum(neg_conf_loss)
        total_loss /= tf.reduce_sum(num_pos)
        total_loss += tf.reduce_sum(self.alpha * pos_loc_loss) / tf.reduce_sum(num_pos)
        return total_loss

訓練自己的RFB模型

RFB整體的文件夾構架如下:
在這裏插入圖片描述
本文使用VOC格式進行訓練。
訓練前將標籤文件放在VOCdevkit文件夾下的VOC2007文件夾下的Annotation中。
在這裏插入圖片描述
訓練前將圖片文件放在VOCdevkit文件夾下的VOC2007文件夾下的JPEGImages中。
在這裏插入圖片描述
在訓練前利用voc2ssd.py文件生成對應的txt。
在這裏插入圖片描述
再運行根目錄下的voc_annotation.py,運行前需要將classes改成你自己的classes。

classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

在這裏插入圖片描述
就會生成對應的2007_train.txt,每一行對應其圖片位置及其真實框的位置。
在這裏插入圖片描述
在訓練前需要修改model_data裏面的voc_classes.txt文件,需要將classes改成你自己的classes。
在這裏插入圖片描述
還有Train文件夾下面的Num_Classes,修改成分類的數量+1。
在這裏插入圖片描述
運行train.py即可開始訓練。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章