欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Keras搭建M2Det目標(biāo)檢測平臺示例

 更新時間:2022年05月09日 14:29:23   作者:Bubbliiiing  
這篇文章主要為大家介紹了Keras搭建M2Det目標(biāo)檢測平臺實現(xiàn)的源碼示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

什么是M2det目標(biāo)檢測算法

一起來看看M2det的keras實現(xiàn)吧,順便訓(xùn)練一下自己的數(shù)據(jù)。

常見的特征提取方法如圖所示有SSD形,F(xiàn)PN形,STDN形:

SSD型:使用了主干網(wǎng)絡(luò)的最后兩層,再加上4個使用stride=2卷積的下采樣層構(gòu)成;

FPN型:也稱為U型網(wǎng)絡(luò),經(jīng)過上采樣操作,然后對應(yīng)融合相同的scale;

STDN型:基于DenseNet的最后一個dense block,通過池化和scale-transfer操作來構(gòu)建;

這三者有一定的缺點:

一是均基于分類網(wǎng)絡(luò)作為主干提取,對目標(biāo)檢測任務(wù)而言特征表示可能不夠;

二是每個feature map僅由主干網(wǎng)絡(luò)的single level給出,不夠全面

M2det論文新提出MLFPN型,整體思想是Multi-level&Multi-scale。是一種更加有效的適合于檢測的特征金字塔結(jié)構(gòu)。

源碼下載

M2det實現(xiàn)思路

一、預(yù)測部分

1、主干網(wǎng)絡(luò)介紹

M2det采用可以采用VGG和ResNet101作為主干特征提取網(wǎng)絡(luò),上圖的backbone network指的就是VGG和Resnet101,本文以VGG為例介紹。

M2DET采用的主干網(wǎng)絡(luò)是VGG網(wǎng)絡(luò),關(guān)于VGG的介紹大家可以看我的另外一篇博客

http://www.dbjr.com.cn/article/246917.htm

在m2det中,我們?nèi)サ袅巳康娜B接層,只保留了卷積層和最大池化層,即Conv1到Conv5。

1、一張原始圖片被resize到(320,320,3)。

2、conv1兩次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為64,輸出為(320,320,64),再2X2最大池化,輸出net為(160,160,64)。

3、conv2兩次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為128,輸出net為(160,160,128),再2X2最大池化,輸出net為(80,80,128)。

4、conv3三次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為256,輸出net為(80,80,256),再2X2最大池化,輸出net為(40,40,256)。

5、conv4三次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為512,輸出net為(40,40,512),再2X2最大池化,此時不進行池化,輸出net為(40,40,512)。conv4-3的結(jié)果會進入FFM1進行特征的融合。

6、conv5三次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為1024,輸出net為(40,40,1024),再2X2最大池化,輸出net為(20,20,1024)。池化后的結(jié)果會進入FFM1進行特征的融合。

from keras import Model
from keras.layers import Conv2D, MaxPooling2D
def VGG16(inputs):
    net = {} 
    #------------------------#
    #   輸入默認(rèn)為320,320,3
    #------------------------#
    net['input'] = inputs
    #------------------------------------------------#
    #   第一個卷積部分  320,320,3 -> 160,160,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'])
    #------------------------------------------------#
    #   第二個卷積部分  160,160,64 -> 80,80,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'])
    y0 = net['pool2']
    #------------------------------------------------#
    #   第三個卷積部分  80,80,128 -> 40,40,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'])
    y1 = net['pool3']
    #------------------------------------------------#
    #   第四個卷積部分  40,40,256 -> 40,40,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='block4_pool')(net['conv4_3'])
    y2 = net['conv4_3']
    #------------------------------------------------#
    #   第五個卷積部分  40,40,512 -> 20,20,1024
    #------------------------------------------------#
    net['conv5_1'] = Conv2D(1024, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv5_1')(net['conv4_3'])
    net['conv5_2'] = Conv2D(1024, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv5_2')(net['conv5_1'])
    net['conv5_3'] = Conv2D(1024, kernel_size=(3,3),
                                   activation='relu',
                                   padding='same',
                                   name='conv5_3')(net['conv5_2'])
    net['pool5'] = MaxPooling2D((3, 3), strides=(2, 2), padding='same',
                                name='pool5')(net['conv5_3'])
    y3 = net['pool5']
    model = Model(inputs, [y0,y1,y2,y3], name='vgg16')
    return model

2、FFM1特征初步融合

FFM1具體的結(jié)構(gòu)如下:

FFM1會對VGG提取到的特征進行初步融合。

在利用VGG進行特征提取的時候,我們會取出shape為(40,40,512)、(20,20,1024)的特征層進行下一步的操作。

在FFM1中,其會對(20,20,1024)的特征層進行進行一個通道數(shù)為512、卷積核大小為3x3、步長為1x1的卷積,然后再進行上采樣,使其Shape變?yōu)?40,40,512);

同時會對(40,40,512)的特征層進行進行一個通道數(shù)為256、卷積核大小為1x1,步長為1x1的卷積,使其Shape變?yōu)?40,40,256);

然后將兩個卷積后的結(jié)果進行堆疊,變成一個(40,40,768)的初步融合特征層

實現(xiàn)代碼為:

def FFMv1(C4, C5, feature_size_1=256, feature_size_2=512, name='FFMv1'):
    #------------------------------------------------#
    #   C4特征層      40,40,512
    #   C5特征層      20,20,1024
    #------------------------------------------------#
    # 40,40,512 -> 40,40,256
    F4 = conv2d(C4, filters=feature_size_1, kernel_size=(3, 3), strides=(1, 1), padding='same', name='F4')
    # 20,20,1024 -> 20,20,512
    F5 = conv2d(C5, filters=feature_size_2, kernel_size=(1, 1), strides=(1, 1), padding='same', name='F5')
    # 20,20,512 -> 40,40,512
    F5 = keras.layers.UpSampling2D(size=(2, 2), name='F5_Up')(F5)
    # 40,40,256 + 40,40,512 -> 40,40,768
    outputs = keras.layers.Concatenate(name=name)([F4, F5])
    return outputs

3、細(xì)化U型模塊TUM

Tum的結(jié)構(gòu)具體如下:

當(dāng)我們給Tum輸入一個(40,40,256)的有效特征層之后,Tum會對輸入進來的特征層進行U型的特征提取,這里的結(jié)構(gòu)比較類似特征金字塔的結(jié)構(gòu),先對特征層進行不斷的特征壓縮,然后再不斷的上采樣進行特征融合,利用Tum我們可以獲得6個有效特征層,大小分別是(40,40,128)、(20,20,128)、(10,10,128)、(5,5,128)、(3,3,128)、(1,1,128)。

def TUM(stage, inputs, feature_size=256, name="TUM"): #---------------------------------# # 進行下采樣的部分 #---------------------------------# # 40,40,256 f1 = inputs # 40,40,256 -> 20,20,256 f2 = conv2d(f1, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='same',name=name + "_" + str(stage) + '_f2') # 20,20,256 -> 10,10,256 f3 = conv2d(f2, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='same',name=name + "_" + str(stage) + '_f3') # 10,10,256 -> 5,5,256 f4 = conv2d(f3, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='same',name=name + "_" + str(stage) + '_f4') # 5,5,256 -> 3,3,256 f5 = conv2d(f4, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='same',name=name + "_" + str(stage) + '_f5') # 3,3,256 -> 1,1,256 f6 = conv2d(f5, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='valid',name=name + "_" + str(stage) + '_f6') size_buffer = [] # 40,40 size_buffer.append([int(f1.shape[1]), int(f1.shape[2])]) # 20,20 size_buffer.append([int(f2.shape[1]), int(f2.shape[2])]) # 10,10 size_buffer.append([int(f3.shape[1]), int(f3.shape[2])]) # 5,5 size_buffer.append([int(f4.shape[1]), int(f4.shape[2])]) # 3,3 size_buffer.append([int(f5.shape[1]), int(f5.shape[2])]) #---------------------------------# # 進行上采樣與特征融合的部分 #---------------------------------# c6 = f6 # 1,1,256 -> 1,1,256 c5 = conv2d(c6, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same',name=name + "_" + str(stage) + '_c5') # 1,1,256 -> 3,3,256 c5 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[4]), name=name + "_" + str(stage) + '_upsample_add5')(c5) c5 = keras.layers.Add()([c5, f5]) # 3,3,256 -> 3,3,256 c4 = conv2d(c5, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same', name=name + "_" + str(stage) + '_c4') # 3,3,256 -> 5,5,256 c4 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[3]), name=name + "_" + str(stage) + '_upsample_add4')(c4) c4 = keras.layers.Add()([c4, f4]) # 5,5,256 -> 5,5,256 c3 = conv2d(c4, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same', name=name + "_" + str(stage) + '_c3') # 5,5,256 -> 10,10,256 c3 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[2]), name=name + "_" + str(stage) + '_upsample_add3')(c3) c3 = keras.layers.Add()([c3, f3]) # 10,10,256 -> 10,10,256 c2 = conv2d(c3, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same', name=name + "_" + str(stage) + '_c2') # 10,10,256 -> 20,20,256 c2 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[1]), name=name + "_" + str(stage) + '_upsample_add2')(c2) c2 = keras.layers.Add()([c2, f2]) # 20,20,256 -> 20,20,256 c1 = conv2d(c2, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same', name=name + "_" + str(stage) + '_c1') # 20,20,256 -> 40,40,256 c1 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[0]), name=name + "_" + str(stage) + '_upsample_add1')(c1) c1 = keras.layers.Add()([c1, f1]) #---------------------------------# # 利用1x1卷積調(diào)整通道數(shù)后輸出 #---------------------------------# output_features = feature_size // 2 # 40,40,256 -> 40,40,128 o1 = conv2d(c1, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o1') # 20,20,256 -> 20,20,128 o2 = conv2d(c2, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o2') # 10,10,256 -> 10,10,128 o3 = conv2d(c3, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o3') # 5,5,256 -> 5,5,128 o4 = conv2d(c4, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o4') # 3,3,256 -> 3,3,128 o5 = conv2d(c5, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o5') # 1,1,256 -> 1,1,128 o6 = conv2d(c6, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o6') outputs = [o1, o2, o3, o4, o5, o6] return outputs
def TUM(stage, inputs, feature_size=256, name="TUM"):
    #---------------------------------#
    #   進行下采樣的部分
    #---------------------------------#
    # 40,40,256
    f1 = inputs
    # 40,40,256 -> 20,20,256
    f2 = conv2d(f1, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='same',name=name + "_" + str(stage) + '_f2')
    # 20,20,256 -> 10,10,256
    f3 = conv2d(f2, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='same',name=name + "_" + str(stage) + '_f3')
    # 10,10,256 -> 5,5,256   
    f4 = conv2d(f3, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='same',name=name + "_" + str(stage) + '_f4')
    # 5,5,256 -> 3,3,256
    f5 = conv2d(f4, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='same',name=name + "_" + str(stage) + '_f5')
    # 3,3,256 -> 1,1,256
    f6 = conv2d(f5, filters=feature_size, kernel_size=(3, 3), strides=(2, 2), padding='valid',name=name + "_" + str(stage) + '_f6')
    size_buffer = []
    # 40,40
    size_buffer.append([int(f1.shape[1]), int(f1.shape[2])])
    # 20,20
    size_buffer.append([int(f2.shape[1]), int(f2.shape[2])])
    # 10,10
    size_buffer.append([int(f3.shape[1]), int(f3.shape[2])])
    # 5,5
    size_buffer.append([int(f4.shape[1]), int(f4.shape[2])])
    # 3,3
    size_buffer.append([int(f5.shape[1]), int(f5.shape[2])])
    #---------------------------------#
    #   進行上采樣與特征融合的部分
    #---------------------------------#
    c6 = f6
    # 1,1,256 -> 1,1,256
    c5 = conv2d(c6, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same',name=name + "_" + str(stage) + '_c5')
    # 1,1,256 -> 3,3,256
    c5 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[4]), name=name + "_" + str(stage) + '_upsample_add5')(c5)
    c5 = keras.layers.Add()([c5, f5])
    # 3,3,256 -> 3,3,256
    c4 = conv2d(c5, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same', name=name + "_" + str(stage) + '_c4')
    # 3,3,256 -> 5,5,256
    c4 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[3]), name=name + "_" + str(stage) + '_upsample_add4')(c4)
    c4 = keras.layers.Add()([c4, f4])
    # 5,5,256 -> 5,5,256
    c3 = conv2d(c4, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same', name=name + "_" + str(stage) + '_c3')
    # 5,5,256 -> 10,10,256
    c3 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[2]), name=name + "_" + str(stage) + '_upsample_add3')(c3)
    c3 = keras.layers.Add()([c3, f3])
    # 10,10,256 -> 10,10,256
    c2 = conv2d(c3, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same', name=name + "_" + str(stage) + '_c2')
    # 10,10,256 -> 20,20,256
    c2 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[1]), name=name + "_" + str(stage) + '_upsample_add2')(c2)
    c2 = keras.layers.Add()([c2, f2])
    # 20,20,256 -> 20,20,256
    c1 = conv2d(c2, filters=feature_size, kernel_size=(3, 3), strides=(1, 1), padding='same', name=name + "_" + str(stage) + '_c1')
    # 20,20,256 -> 40,40,256
    c1 = keras.layers.Lambda(lambda x: tf.image.resize_bilinear(x, size=size_buffer[0]), name=name + "_" + str(stage) + '_upsample_add1')(c1)
    c1 = keras.layers.Add()([c1, f1])
    #---------------------------------#
    #   利用1x1卷積調(diào)整通道數(shù)后輸出
    #---------------------------------#
    output_features = feature_size // 2
    # 40,40,256 -> 40,40,128 
    o1 = conv2d(c1, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o1')
    # 20,20,256 -> 20,20,128
    o2 = conv2d(c2, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o2')
    # 10,10,256 -> 10,10,128
    o3 = conv2d(c3, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o3')
    # 5,5,256 -> 5,5,128
    o4 = conv2d(c4, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o4')
    # 3,3,256 -> 3,3,128
    o5 = conv2d(c5, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o5')
    # 1,1,256 -> 1,1,128
    o6 = conv2d(c6, filters=output_features, kernel_size=(1, 1), strides=(1, 1), padding='valid',name=name + "_" + str(stage) + '_o6')
    outputs = [o1, o2, o3, o4, o5, o6]
    return outputs

4、FFM2特征加強融合

通過TUM,我們可以獲得六個有效特征層,為了進一步加強網(wǎng)絡(luò)的特征提取能力,M2det將6個有效特征層中的(40,40,128)特征層取出,和FFM1提取出來的初步融合特征層進行加強融合,再次輸出一個(40,40,256)的加強融合的特征層。

此時FFM2輸出的加強融合特征層可以再一次傳入到TUM中進行U形特征提取。

如上圖所示,我們可以進一步利用多個TUM模塊進行特征提取,利用多個TUM模塊我們可以獲得多次有效特征層。

TUM模塊的數(shù)量我們可以根據(jù)自身需要進行修改,本文使用4次TUM模塊,可以分別獲得四次(40,40,128)、(20,20,128)、(10,10,128)、(5,5,128)、(3,3,128)、(1,1,128)的有效特征層。(論文中做了實驗,用8次TUM模塊會有比較好的效果)。

我們可以將獲得的有效特征層,按照shape進行堆疊,最終獲得(40,40,512)、(20,20,512)、(10,10,512)、(5,5,512)、(3,3,512)、(1,1,512)六個有效特征層。

def FFMv2(stage, base, tum, base_size=(40,40,768), tum_size=(40,40,128), feature_size=128, name='FFMv2'):
    # 40,40,128
    outputs = conv2d(base, filters=feature_size, kernel_size=(1, 1), strides=(1, 1), padding='same', name=name+"_"+str(stage) + '_base_feature')
    outputs = keras.layers.Concatenate(name=name+"_"+str(stage))([outputs, tum])
    # 40,40,256
    return outputs
def _create_feature_pyramid(base_feature, stage=8):
    features = [[],[],[],[],[],[]]
    # 將輸入進來的
    inputs = keras.layers.Conv2D(filters=256, kernel_size=1, strides=1, padding='same')(base_feature)
    # 第一個TUM模塊
    outputs = TUM(1,inputs)
    max_output = outputs[0]
    for j in range(len(features)):
        features[j].append(outputs[j])
    # 第2,3,4個TUM模塊,需要將上一個Tum模塊輸出的40x40x128的內(nèi)容,傳入到下一個Tum模塊中
    for i in range(2, stage+1):
        # 將Tum模塊的輸出和基礎(chǔ)特征層傳入到FFmv2層當(dāng)中
        # 輸入為base_feature 40x40x768,max_output 40x40x128
        # 輸出為40x40x256
        inputs = FFMv2(i - 1,base_feature, max_output)
        # 輸出為40x40x128、20x20x128、10x10x128、5x5x128、3x3x128、1x1x128
        outputs = TUM(i,inputs)
        max_output = outputs[0]
        for j in range(len(features)):
            features[j].append(outputs[j])
    # 進行了4次TUM
    # 將獲得的同樣大小的特征層堆疊到一起
    concatenate_features = []
    for feature in features:
        concat = keras.layers.Concatenate()([f for f in feature])
        concatenate_features.append(concat)
    return concatenate_features

5、注意力機制模塊SFAM

注意力機制模塊如下:

其會對上一步獲得的(40,40,512)、(20,20,512)、(10,10,512)、(5,5,512)、(3,3,512)、(1,1,512)六個有效特征層。進行各個通道的注意力機制調(diào)整,判斷每一個通道數(shù)應(yīng)該有的權(quán)重。

# 注意力機制
def SE_block(inputs, input_size, compress_ratio=16, name='SE_block'):
    pool = keras.layers.GlobalAveragePooling2D()(inputs)
    reshape = keras.layers.Reshape((1, 1, input_size[2]))(pool)
    fc1 = keras.layers.Conv2D(filters=input_size[2] // compress_ratio, kernel_size=1, strides=1, padding='valid',
                              activation='relu', name=name+'_fc1')(reshape)
    fc2 = keras.layers.Conv2D(filters=input_size[2], kernel_size=1, strides=1, padding='valid', activation='sigmoid',
                              name=name+'_fc2')(fc1)
    reweight = keras.layers.Multiply(name=name+'_reweight')([inputs, fc2])
    return reweight
def SFAM(feature_pyramid,input_sizes, compress_ratio=16, name='SFAM'):
    outputs = []
    for i in range(len(input_sizes)):
        input_size = input_sizes[i]
        _input = feature_pyramid[i]
        _output = SE_block(_input, input_size, compress_ratio=compress_ratio, name='SE_block_' + str(i))
        outputs.append(_output)
    return outputs

6、從特征獲取預(yù)測結(jié)果

通過第五步,我們獲取了6個融合了注意力機制的有效特征層。

對獲取到的每一個有效特征層,我們分別對其進行一次num_anchors x 4的卷積、一次num_anchors x num_classes的卷積、并需要計算每一個有效特征層對應(yīng)的先驗框。而num_anchors指的是該特征層所擁有的先驗框數(shù)量。

其中:

num_anchors x 4的卷積 用于預(yù)測 該特征層上 每一個網(wǎng)格點上 每一個先驗框的變化情況。(為什么說是變化情況呢,這是因為M2DET的預(yù)測結(jié)果需要結(jié)合先驗框獲得預(yù)測框,預(yù)測結(jié)果就是先驗框的變化情況。)

num_anchors x num_classes的卷積 用于預(yù)測 該特征層上 每一個網(wǎng)格點上 每一個預(yù)測框?qū)?yīng)的種類。

每一個有效特征層對應(yīng)的先驗框?qū)?yīng)著該特征層上 每一個網(wǎng)格點上 預(yù)先設(shè)定好的六個框。

所有的特征層對應(yīng)的預(yù)測結(jié)果的shape如下:

實現(xiàn)代碼為:

def m2det(input_shape, num_classes=21, num_anchors = 6):
    inputs = keras.layers.Input(shape=input_shape)
    #------------------------------------------------#
    #   利用主干特征提取網(wǎng)絡(luò)獲得兩個有效特征層
    #   分別是C4      40,40,512
    #   分別是C5      20,20,1024
    #------------------------------------------------#
    C4, C5 = VGG16(inputs).outputs[2:]
    # base_feature的shape為40,40,768
    base_feature = FFMv1(C4, C5, feature_size_1=256, feature_size_2=512)
    #---------------------------------------------------------------------------------------------------#
    #   在_create_feature_pyramid函數(shù)里,我們會使用TUM模塊對輸入進來的特征層進行特征提取
    #   最終輸出的特征層有六個,由于進行了四次的TUM模塊,所以六個有效特征層由4次TUM模塊的輸出堆疊而成
    #   o1      40,40,128*4         40,40,512
    #   o2      20,20,128*4         20,20,512
    #   o3      10,10,128*4         10,10,512
    #   o4      5,5,128*4           5,5,512
    #   o5      3,3,128*4           3,3,512
    #   o6      1,1,128*4           1,1,512
    #---------------------------------------------------------------------------------------------------#
    feature_pyramid = _create_feature_pyramid(base_feature, stage=4)
    #-------------------------------------------------#
    #   給合并后的特征層添加上注意力機制
    #-------------------------------------------------#
    outputs = SFAM(feature_pyramid)
    #-------------------------------------------------#
    #   將有效特征層轉(zhuǎn)換成輸出結(jié)果
    #-------------------------------------------------#
    classifications = []
    regressions = []
    for feature in outputs:
        classification = keras.layers.Conv2D(filters = num_anchors * num_classes, kernel_size=3, strides=1, padding='same')(feature)
        classification = keras.layers.Reshape((-1, num_classes))(classification)
        classification = keras.layers.Activation('softmax')(classification)
        regression = keras.layers.Conv2D(filters = num_anchors * 4, kernel_size=3, strides=1, padding='same')(feature)
        regression = keras.layers.Reshape((-1, 4))(regression)
        classifications.append(classification)
        regressions.append(regression)
    classifications = keras.layers.Concatenate(axis=1, name="classification")(classifications)
    regressions     = keras.layers.Concatenate(axis=1, name="regression")(regressions)
    pyramids        = keras.layers.Concatenate(axis=-1, name="out")([regressions, classifications])
    return keras.models.Model(inputs=inputs, outputs=pyramids)

7、預(yù)測結(jié)果的解碼

我們通過對每一個特征層的處理,可以獲得兩個內(nèi)容,分別是:

num_anchors x 4的卷積 用于預(yù)測 該特征層上 每一個網(wǎng)格點上 每一個先驗框的變化情況。

num_anchors x num_classes的卷積 用于預(yù)測 該特征層上 每一個網(wǎng)格點上 每一個預(yù)測框?qū)?yīng)的種類。

每一個有效特征層對應(yīng)的先驗框?qū)?yīng)著該特征層上 每一個網(wǎng)格點上 預(yù)先設(shè)定好的六個框。

我們利用 num_anchors x 4的卷積 與 每一個有效特征層對應(yīng)的先驗框 獲得框的真實位置。

每一個有效特征層對應(yīng)的先驗框就是,如圖所示的作用:

每一個有效特征層將整個圖片分成與其長寬對應(yīng)的網(wǎng)格,如conv4-3和fl7組合成的特征層就是將整個圖像分成38x38個網(wǎng)格;然后從每個網(wǎng)格中心建立多個先驗框,如conv4-3和fl7組合成的有效特征層就是建立了6個先驗框;對于conv4-3和fl7組合成的特征層來講,整個圖片被分成38x38個網(wǎng)格,每個網(wǎng)格中心對應(yīng)6個先驗框,一共包含了,38x38x6個,8664個先驗框。

先驗框雖然可以代表一定的框的位置信息與框的大小信息,但是其是有限的,無法表示任意情況,因此還需要調(diào)整,RFBnet利用num_anchors x 4的卷積的結(jié)果對先驗框進行調(diào)整。

num_anchors x 4中的num_anchors表示了這個網(wǎng)格點所包含的先驗框數(shù)量,其中的4表示了x_offset、y_offset、h和w的調(diào)整情況。

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

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

當(dāng)然得到最終的預(yù)測結(jié)構(gòu)后還要進行得分排序與非極大抑制篩選這一部分基本上是所有目標(biāo)檢測通用的部分。

1、取出每一類得分大于self.obj_threshold的框和得分。

2、利用框的位置和得分進行非極大抑制。

實現(xiàn)代碼如下:

import numpy as np
import tensorflow as tf
import keras.backend as K
class BBoxUtility(object):
    def __init__(self, num_classes, nms_thresh=0.45, top_k=300):
        self.num_classes    = num_classes
        self._nms_thresh    = nms_thresh
        self._top_k         = top_k
        self.boxes          = K.placeholder(dtype='float32', shape=(None, 4))
        self.scores         = K.placeholder(dtype='float32', shape=(None,))
        self.nms            = tf.image.non_max_suppression(self.boxes, self.scores, self._top_k, iou_threshold=self._nms_thresh)
        self.sess           = K.get_session()
    def ssd_correct_boxes(self, box_xy, box_wh, input_shape, image_shape, letterbox_image):
        #-----------------------------------------------------------------#
        #   把y軸放前面是因為方便預(yù)測框和圖像的寬高進行相乘
        #-----------------------------------------------------------------#
        box_yx = box_xy[..., ::-1]
        box_hw = box_wh[..., ::-1]
        input_shape = np.array(input_shape)
        image_shape = np.array(image_shape)
        if letterbox_image:
            #-----------------------------------------------------------------#
            #   這里求出來的offset是圖像有效區(qū)域相對于圖像左上角的偏移情況
            #   new_shape指的是寬高縮放情況
            #-----------------------------------------------------------------#
            new_shape = np.round(image_shape * np.min(input_shape/image_shape))
            offset  = (input_shape - new_shape)/2./input_shape
            scale   = input_shape/new_shape
            box_yx  = (box_yx - offset) * scale
            box_hw *= scale
        box_mins    = box_yx - (box_hw / 2.)
        box_maxes   = box_yx + (box_hw / 2.)
        boxes  = np.concatenate([box_mins[..., 0:1], box_mins[..., 1:2], box_maxes[..., 0:1], box_maxes[..., 1:2]], axis=-1)
        boxes *= np.concatenate([image_shape, image_shape], axis=-1)
        return boxes
    def decode_boxes(self, mbox_loc, anchors, variances):
        # 獲得先驗框的寬與高
        anchor_width     = anchors[:, 2] - anchors[:, 0]
        anchor_height    = anchors[:, 3] - anchors[:, 1]
        # 獲得先驗框的中心點
        anchor_center_x  = 0.5 * (anchors[:, 2] + anchors[:, 0])
        anchor_center_y  = 0.5 * (anchors[:, 3] + anchors[:, 1])
        # 真實框距離先驗框中心的xy軸偏移情況
        decode_bbox_center_x = mbox_loc[:, 0] * anchor_width * variances[0]
        decode_bbox_center_x += anchor_center_x
        decode_bbox_center_y = mbox_loc[:, 1] * anchor_height * variances[1]
        decode_bbox_center_y += anchor_center_y
        # 真實框的寬與高的求取
        decode_bbox_width   = np.exp(mbox_loc[:, 2] * variances[2])
        decode_bbox_width   *= anchor_width
        decode_bbox_height  = np.exp(mbox_loc[:, 3] * variances[3])
        decode_bbox_height  *= anchor_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 decode_box(self, predictions, anchors, image_shape, input_shape, letterbox_image, variances = [0.1, 0.1, 0.2, 0.2], confidence=0.5):
        #---------------------------------------------------#
        #   :4是回歸預(yù)測結(jié)果
        #---------------------------------------------------#
        mbox_loc        = predictions[:, :, :4]
        #---------------------------------------------------#
        #   獲得種類的置信度
        #---------------------------------------------------#
        mbox_conf       = predictions[:, :, 4:]
        results = []
        #----------------------------------------------------------------------------------------------------------------#
        #   對每一張圖片進行處理,由于在predict.py的時候,我們只輸入一張圖片,所以for i in range(len(mbox_loc))只進行一次
        #----------------------------------------------------------------------------------------------------------------#
        for i in range(len(mbox_loc)):
            results.append([])
            #--------------------------------#
            #   利用回歸結(jié)果對先驗框進行解碼
            #--------------------------------#
            decode_bbox = self.decode_boxes(mbox_loc[i], anchors, variances)
            for c in range(1, self.num_classes):
                #--------------------------------#
                #   取出屬于該類的所有框的置信度
                #   判斷是否大于門限
                #--------------------------------#
                c_confs     = mbox_conf[i, :, c]
                c_confs_m   = c_confs > confidence
                if len(c_confs[c_confs_m]) > 0:
                    #-----------------------------------------#
                    #   取出得分高于confidence的框
                    #-----------------------------------------#
                    boxes_to_process = decode_bbox[c_confs_m]
                    confs_to_process = c_confs[c_confs_m]
                    #-----------------------------------------#
                    #   進行iou的非極大抑制
                    #-----------------------------------------#
                    idx         = self.sess.run(self.nms, feed_dict={self.boxes: boxes_to_process, self.scores: confs_to_process})
                    #-----------------------------------------#
                    #   取出在非極大抑制中效果較好的內(nèi)容
                    #-----------------------------------------#
                    good_boxes  = boxes_to_process[idx]
                    confs       = confs_to_process[idx][:, None]
                    labels      = (c - 1) * np.ones((len(idx), 1))
                    #-----------------------------------------#
                    #   將label、置信度、框的位置進行堆疊。
                    #-----------------------------------------#
                    c_pred      = np.concatenate((good_boxes, labels, confs), axis=1)
                    # 添加進result里
                    results[-1].extend(c_pred)
            if len(results[-1]) > 0:
                results[-1] = np.array(results[-1])
                box_xy, box_wh = (results[-1][:, 0:2] + results[-1][:, 2:4])/2, results[-1][:, 2:4] - results[-1][:, 0:2]
                results[-1][:, :4] = self.ssd_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image)
        return results

8、在原圖上進行繪制

通過第三步,我們可以獲得預(yù)測框在原圖上的位置,而且這些預(yù)測框都是經(jīng)過篩選的。這些篩選后的框可以直接繪制在圖片上,就可以獲得結(jié)果了。

二、訓(xùn)練部分

1、真實框的處理

從預(yù)測部分我們知道,每個特征層的預(yù)測結(jié)果,num_anchors x 4的卷積 用于預(yù)測 該特征層上 每一個網(wǎng)格點上 每一個先驗框的變化情況。

也就是說,我們直接利用M2DET網(wǎng)絡(luò)預(yù)測到的結(jié)果,并不是預(yù)測框在圖片上的真實位置,需要解碼才能得到真實位置。

而在訓(xùn)練的時候,我們需要計算loss函數(shù),這個loss函數(shù)是相對于M2DET網(wǎng)絡(luò)的預(yù)測結(jié)果的。我們需要把圖片輸入到當(dāng)前的M2DET網(wǎng)絡(luò)中,得到預(yù)測結(jié)果;同時還需要把真實框的信息,進行編碼,這個編碼是把真實框的位置信息格式轉(zhuǎn)化為M2DET預(yù)測結(jié)果的格式信息。

也就是,我們需要找到 每一張用于訓(xùn)練的圖片的每一個真實框?qū)?yīng)的先驗框,并求出如果想要得到這樣一個真實框,我們的預(yù)測結(jié)果應(yīng)該是怎么樣的。

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

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

實現(xiàn)代碼如下:

def iou(self, box):
    #---------------------------------------------#
    #   計算出每個真實框與所有的先驗框的iou
    #   判斷真實框與先驗框的重合情況
    #---------------------------------------------#
    inter_upleft    = np.maximum(self.anchors[:, :2], box[:2])
    inter_botright  = np.minimum(self.anchors[:, 2:4], box[2:])
    inter_wh    = inter_botright - inter_upleft
    inter_wh    = np.maximum(inter_wh, 0)
    inter       = inter_wh[:, 0] * inter_wh[:, 1]
    #---------------------------------------------# 
    #   真實框的面積
    #---------------------------------------------#
    area_true = (box[2] - box[0]) * (box[3] - box[1])
    #---------------------------------------------#
    #   先驗框的面積
    #---------------------------------------------#
    area_gt = (self.anchors[:, 2] - self.anchors[:, 0])*(self.anchors[:, 3] - self.anchors[:, 1])
    #---------------------------------------------#
    #   計算iou
    #---------------------------------------------#
    union = area_true + area_gt - inter
    iou = inter / union
    return iou
def encode_box(self, box, return_iou=True, variances = [0.1, 0.1, 0.2, 0.2]):
    #---------------------------------------------#
    #   計算當(dāng)前真實框和先驗框的重合情況
    #   iou [self.num_anchors]
    #   encoded_box [self.num_anchors, 5]
    #---------------------------------------------#
    iou = self.iou(box)
    encoded_box = np.zeros((self.num_anchors, 4 + return_iou))
    #---------------------------------------------#
    #   找到每一個真實框,重合程度較高的先驗框
    #   真實框可以由這個先驗框來負(fù)責(zé)預(yù)測
    #---------------------------------------------#
    assign_mask = iou > self.overlap_threshold
    #---------------------------------------------#
    #   如果沒有一個先驗框重合度大于self.overlap_threshold
    #   則選擇重合度最大的為正樣本
    #---------------------------------------------#
    if not assign_mask.any():
        assign_mask[iou.argmax()] = True
    #---------------------------------------------#
    #   利用iou進行賦值 
    #---------------------------------------------#
    if return_iou:
        encoded_box[:, -1][assign_mask] = iou[assign_mask]
    #---------------------------------------------#
    #   找到對應(yīng)的先驗框
    #---------------------------------------------#
    assigned_anchors = self.anchors[assign_mask]
    #---------------------------------------------#
    #   逆向編碼,將真實框轉(zhuǎn)化為M2det預(yù)測結(jié)果的格式
    #   先計算真實框的中心與長寬
    #---------------------------------------------#
    box_center  = 0.5 * (box[:2] + box[2:])
    box_wh      = box[2:] - box[:2]
    #---------------------------------------------#
    #   再計算重合度較高的先驗框的中心與長寬
    #---------------------------------------------#
    assigned_anchors_center = (assigned_anchors[:, 0:2] + assigned_anchors[:, 2:4]) * 0.5
    assigned_anchors_wh     = (assigned_anchors[:, 2:4] - assigned_anchors[:, 0:2])
    #------------------------------------------------#
    #   逆向求取M2det應(yīng)該有的預(yù)測結(jié)果
    #   先求取中心的預(yù)測結(jié)果,再求取寬高的預(yù)測結(jié)果
    #   存在改變數(shù)量級的參數(shù),默認(rèn)為[0.1,0.1,0.2,0.2]
    #------------------------------------------------#
    encoded_box[:, :2][assign_mask] = box_center - assigned_anchors_center
    encoded_box[:, :2][assign_mask] /= assigned_anchors_wh
    encoded_box[:, :2][assign_mask] /= np.array(variances)[:2]
    encoded_box[:, 2:4][assign_mask] = np.log(box_wh / assigned_anchors_wh)
    encoded_box[:, 2:4][assign_mask] /= np.array(variances)[2:4]
    return encoded_box.ravel()

利用上述代碼我們可以獲得,真實框?qū)?yīng)的所有的iou較大先驗框,并計算了真實框?qū)?yīng)的所有iou較大的先驗框應(yīng)該有的預(yù)測結(jié)果。

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

因此我們還要經(jīng)過一次篩選,將上述代碼獲得的真實框?qū)?yīng)的所有的iou較大先驗框的預(yù)測結(jié)果中,iou最大的那個篩選出來。

通過assign_boxes我們就獲得了,輸入進來的這張圖片,應(yīng)該有的預(yù)測結(jié)果是什么樣子的。

實現(xiàn)代碼如下:

def assign_boxes(self, boxes):
    #---------------------------------------------------#
    #   assignment分為3個部分
    #   :4      的內(nèi)容為網(wǎng)絡(luò)應(yīng)該有的回歸預(yù)測結(jié)果
    #   4:-1    的內(nèi)容為先驗框所對應(yīng)的種類,默認(rèn)為背景
    #   -1      的內(nèi)容為當(dāng)前先驗框是否包含目標(biāo)
    #---------------------------------------------------#
    assignment          = np.zeros((self.num_anchors, 4 + self.num_classes + 1))
    assignment[:, 4]    = 1.0
    if len(boxes) == 0:
        return assignment
    # 對每一個真實框都進行iou計算
    encoded_boxes   = np.apply_along_axis(self.encode_box, 1, boxes[:, :4])
    #---------------------------------------------------#
    #   在reshape后,獲得的encoded_boxes的shape為:
    #   [num_true_box, num_anchors, 4 + 1]
    #   4是編碼后的結(jié)果,1為iou
    #---------------------------------------------------#
    encoded_boxes   = encoded_boxes.reshape(-1, self.num_anchors, 5)
    #---------------------------------------------------#
    #   [num_anchors]求取每一個先驗框重合度最大的真實框
    #---------------------------------------------------#
    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代表為背景的概率,設(shè)定為0,因為這些先驗框有對應(yīng)的物體
    #----------------------------------------------------------#
    assignment[:, 4][best_iou_mask]     = 0
    assignment[:, 5:-1][best_iou_mask]  = boxes[best_iou_idx, 4:]
    #----------------------------------------------------------#
    #   -1表示先驗框是否有對應(yīng)的物體
    #----------------------------------------------------------#
    assignment[:, -1][best_iou_mask]    = 1
    # 通過assign_boxes我們就獲得了,輸入進來的這張圖片,應(yīng)該有的預(yù)測結(jié)果是什么樣子的
    return assignment

2、利用處理完的真實框與對應(yīng)圖片的預(yù)測結(jié)果計算loss

loss的計算分為三個部分:

1、獲取所有正標(biāo)簽的框的預(yù)測結(jié)果的回歸loss。

2、獲取所有正標(biāo)簽的種類的預(yù)測結(jié)果的交叉熵loss。

3、獲取一定負(fù)標(biāo)簽的種類的預(yù)測結(jié)果的交叉熵loss。

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

實現(xiàn)代碼如下:

import tensorflow as tf
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_sum(l1_loss, -1)
    def _softmax_loss(self, y_true, y_pred):
        y_pred = tf.maximum(y_pred, 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):
        num_boxes = tf.to_float(tf.shape(y_true)[1])
        # --------------------------------------------- #
        #   分類的loss
        #   batch_size,8732,21 -> batch_size,8732
        # --------------------------------------------- #
        conf_loss = self._softmax_loss(y_true[:, :, 4:-1],
                                       y_pred[:, :, 4:])
        # --------------------------------------------- #
        #   框的位置的loss
        #   batch_size,8732,4 -> batch_size,8732
        # --------------------------------------------- #
        loc_loss = self._l1_smooth_loss(y_true[:, :, :4],
                                        y_pred[:, :, :4])
        # --------------------------------------------- #
        #   獲取所有的正標(biāo)簽的loss
        # --------------------------------------------- #
        pos_loc_loss = tf.reduce_sum(loc_loss * y_true[:, :, -1],
                                     axis=1)
        pos_conf_loss = tf.reduce_sum(conf_loss * y_true[:, :, -1],
                                      axis=1)
        # --------------------------------------------- #
        #   每一張圖的正樣本的個數(shù)
        #   batch_size,
        # --------------------------------------------- #
        num_pos = tf.reduce_sum(y_true[:, :, -1], axis=-1)
        # --------------------------------------------- #
        #   每一張圖的負(fù)樣本的個數(shù)
        #   batch_size,
        # --------------------------------------------- #
        num_neg = tf.minimum(self.neg_pos_ratio * num_pos, num_boxes - num_pos)
        # 找到了哪些值是大于0的
        pos_num_neg_mask = tf.greater(num_neg, 0)
        # --------------------------------------------- #
        #   如果所有的圖,正樣本的數(shù)量均為0
        #   那么則默認(rèn)選取100個先驗框作為負(fù)樣本
        # --------------------------------------------- #
        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]])
        # --------------------------------------------- #
        #   從這里往后,與視頻中看到的代碼有些許不同。
        #   由于以前的負(fù)樣本選取方式存在一些問題,
        #   我對該部分代碼進行重構(gòu)。
        #   求整個batch應(yīng)該的負(fù)樣本數(shù)量總和
        # --------------------------------------------- #
        num_neg_batch = tf.reduce_sum(tf.boolean_mask(num_neg, tf.greater(num_neg, 0)))
        num_neg_batch = tf.to_int32(num_neg_batch)
        # --------------------------------------------- #
        #   對預(yù)測結(jié)果進行判斷,如果該先驗框沒有包含物體
        #   那么它的不屬于背景的預(yù)測概率過大的話
        #   就是難分類樣本
        # --------------------------------------------- #
        confs_start = 4 + self.background_label_id + 1
        confs_end   = confs_start + self.num_classes - 1
        # --------------------------------------------- #
        #   batch_size,8732
        #   把不是背景的概率求和,求和后的概率越大
        #   代表越難分類。
        # --------------------------------------------- #
        max_confs = tf.reduce_sum(y_pred[:, :, confs_start:confs_end], axis=2)
        # --------------------------------------------------- #
        #   只有沒有包含物體的先驗框才得到保留
        #   我們在整個batch里面選取最難分類的num_neg_batch個
        #   先驗框作為負(fù)樣本。
        # --------------------------------------------------- #
        max_confs   = tf.reshape(max_confs * (1 - y_true[:, :, -1]), [-1])
        _, indices  = tf.nn.top_k(max_confs, k=num_neg_batch)
        neg_conf_loss = tf.gather(tf.reshape(conf_loss, [-1]), indices)
        # 進行歸一化
        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) + tf.reduce_sum(self.alpha * pos_loc_loss)
        total_loss /= tf.reduce_sum(num_pos)
        return total_loss

訓(xùn)練自己的M2Det模型

首先前往Github下載對應(yīng)的倉庫,下載完后利用解壓軟件解壓,之后用編程軟件打開文件夾。

注意打開的根目錄必須正確,否則相對目錄不正確的情況下,代碼將無法運行。

一定要注意打開后的根目錄是文件存放的目錄。

一、數(shù)據(jù)集的準(zhǔn)備

本文使用VOC格式進行訓(xùn)練,訓(xùn)練前需要自己制作好數(shù)據(jù)集,如果沒有自己的數(shù)據(jù)集,可以通過Github連接下載VOC12+07的數(shù)據(jù)集嘗試下。訓(xùn)練前將標(biāo)簽文件放在VOCdevkit文件夾下的VOC2007文件夾下的Annotation中。

訓(xùn)練前將圖片文件放在VOCdevkit文件夾下的VOC2007文件夾下的JPEGImages中。

此時數(shù)據(jù)集的擺放已經(jīng)結(jié)束。

二、數(shù)據(jù)集的處理

在完成數(shù)據(jù)集的擺放之后,我們需要對數(shù)據(jù)集進行下一步的處理,目的是獲得訓(xùn)練用的2007_train.txt以及2007_val.txt,需要用到根目錄下的voc_annotation.py。

voc_annotation.py里面有一些參數(shù)需要設(shè)置。

分別是annotation_mode、classes_path、trainval_percent、train_percent、VOCdevkit_path,第一次訓(xùn)練可以僅修改classes_path

'''
annotation_mode用于指定該文件運行時計算的內(nèi)容
annotation_mode為0代表整個標(biāo)簽處理過程,包括獲得VOCdevkit/VOC2007/ImageSets里面的txt以及訓(xùn)練用的2007_train.txt、2007_val.txt
annotation_mode為1代表獲得VOCdevkit/VOC2007/ImageSets里面的txt
annotation_mode為2代表獲得訓(xùn)練用的2007_train.txt、2007_val.txt
'''
annotation_mode     = 0
'''
必須要修改,用于生成2007_train.txt、2007_val.txt的目標(biāo)信息
與訓(xùn)練和預(yù)測所用的classes_path一致即可
如果生成的2007_train.txt里面沒有目標(biāo)信息
那么就是因為classes沒有設(shè)定正確
僅在annotation_mode為0和2的時候有效
'''
classes_path        = 'model_data/voc_classes.txt'
'''
trainval_percent用于指定(訓(xùn)練集+驗證集)與測試集的比例,默認(rèn)情況下 (訓(xùn)練集+驗證集):測試集 = 9:1
train_percent用于指定(訓(xùn)練集+驗證集)中訓(xùn)練集與驗證集的比例,默認(rèn)情況下 訓(xùn)練集:驗證集 = 9:1
僅在annotation_mode為0和1的時候有效
'''
trainval_percent    = 0.9
train_percent       = 0.9
'''
指向VOC數(shù)據(jù)集所在的文件夾
默認(rèn)指向根目錄下的VOC數(shù)據(jù)集
'''
VOCdevkit_path  = 'VOCdevkit'

classes_path用于指向檢測類別所對應(yīng)的txt,以voc數(shù)據(jù)集為例,我們用的txt為:

訓(xùn)練自己的數(shù)據(jù)集時,可以自己建立一個cls_classes.txt,里面寫自己所需要區(qū)分的類別。

三、開始網(wǎng)絡(luò)訓(xùn)練

通過voc_annotation.py我們已經(jīng)生成了2007_train.txt以及2007_val.txt,此時我們可以開始訓(xùn)練了。

訓(xùn)練的參數(shù)較多,大家可以在下載庫后仔細(xì)看注釋,其中最重要的部分依然是train.py里的classes_path。

classes_path用于指向檢測類別所對應(yīng)的txt,這個txt和voc_annotation.py里面的txt一樣!訓(xùn)練自己的數(shù)據(jù)集必須要修改!

修改完classes_path后就可以運行train.py開始訓(xùn)練了,在訓(xùn)練多個epoch后,權(quán)值會生成在logs文件夾中。

其它參數(shù)的作用如下:

#--------------------------------------------------------#
#   訓(xùn)練前一定要修改classes_path,使其對應(yīng)自己的數(shù)據(jù)集
#--------------------------------------------------------#
classes_path    = 'model_data/voc_classes.txt'
#----------------------------------------------------------------------------------------------------------------------------#
#   權(quán)值文件請看README,百度網(wǎng)盤下載。數(shù)據(jù)的預(yù)訓(xùn)練權(quán)重對不同數(shù)據(jù)集是通用的,因為特征是通用的。
#   預(yù)訓(xùn)練權(quán)重對于99%的情況都必須要用,不用的話權(quán)值太過隨機,特征提取效果不明顯,網(wǎng)絡(luò)訓(xùn)練的結(jié)果也不會好。
#   訓(xùn)練自己的數(shù)據(jù)集時提示維度不匹配正常,預(yù)測的東西都不一樣了自然維度不匹配
#
#   如果想要斷點續(xù)練就將model_path設(shè)置成logs文件夾下已經(jīng)訓(xùn)練的權(quán)值文件。 
#   當(dāng)model_path = ''的時候不加載整個模型的權(quán)值。
#
#   此處使用的是整個模型的權(quán)重,因此是在train.py進行加載的。
#   如果想要讓模型從主干的預(yù)訓(xùn)練權(quán)值開始訓(xùn)練,則設(shè)置model_path為主干網(wǎng)絡(luò)的權(quán)值,此時僅加載主干。
#   如果想要讓模型從0開始訓(xùn)練,則設(shè)置model_path = '',F(xiàn)reeze_Train = Fasle,此時從0開始訓(xùn)練,且沒有凍結(jié)主干的過程。
#   一般來講,從0開始訓(xùn)練效果會很差,因為權(quán)值太過隨機,特征提取效果不明顯。
#----------------------------------------------------------------------------------------------------------------------------#
model_path      = 'model_data/M2det_weights.h5'
#------------------------------------------------------#
#   輸入的shape大小,32的倍數(shù)
#------------------------------------------------------#
input_shape     = [320, 320]
#----------------------------------------------------#
#   可用于設(shè)定先驗框的大小,默認(rèn)的anchors_size
#   是根據(jù)voc數(shù)據(jù)集設(shè)定的,大多數(shù)情況下都是通用的!
#   如果想要檢測小物體,可以修改anchors_size
#   一般調(diào)小淺層先驗框的大小就行了!因為淺層負(fù)責(zé)小物體檢測!
#   比如anchors_size = [21, 45, 99, 153, 207, 261, 315]
#----------------------------------------------------#
anchors_size    = [26, 48, 106, 163, 221, 278, 336]
#----------------------------------------------------#
#   訓(xùn)練分為兩個階段,分別是凍結(jié)階段和解凍階段。
#   顯存不足與數(shù)據(jù)集大小無關(guān),提示顯存不足請調(diào)小batch_size。
#   受到BatchNorm層影響,batch_size最小為2,不能為1。
#----------------------------------------------------#
#----------------------------------------------------#
#   凍結(jié)階段訓(xùn)練參數(shù)
#   此時模型的主干被凍結(jié)了,特征提取網(wǎng)絡(luò)不發(fā)生改變
#   占用的顯存較小,僅對網(wǎng)絡(luò)進行微調(diào)
#----------------------------------------------------#
Init_Epoch          = 0
Freeze_Epoch        = 50
Freeze_batch_size   = 8
Freeze_lr           = 5e-4
#----------------------------------------------------#
#   解凍階段訓(xùn)練參數(shù)
#   此時模型的主干不被凍結(jié)了,特征提取網(wǎng)絡(luò)會發(fā)生改變
#   占用的顯存較大,網(wǎng)絡(luò)所有的參數(shù)都會發(fā)生改變
#----------------------------------------------------#
UnFreeze_Epoch      = 100
Unfreeze_batch_size = 4
Unfreeze_lr         = 1e-4
#------------------------------------------------------#
#   是否進行凍結(jié)訓(xùn)練,默認(rèn)先凍結(jié)主干訓(xùn)練后解凍訓(xùn)練。
#------------------------------------------------------#
Freeze_Train        = True
#------------------------------------------------------#
#   用于設(shè)置是否使用多線程讀取數(shù)據(jù),0代表關(guān)閉多線程
#   開啟后會加快數(shù)據(jù)讀取速度,但是會占用更多內(nèi)存
#   keras里開啟多線程有些時候速度反而慢了許多
#   在IO為瓶頸的時候再開啟多線程,即GPU運算速度遠(yuǎn)大于讀取圖片的速度。
#------------------------------------------------------#
num_workers         = 0
#----------------------------------------------------#
#   獲得圖片路徑和標(biāo)簽
#----------------------------------------------------#
train_annotation_path   = '2007_train.txt'
val_annotation_path     = '2007_val.txt'

四、訓(xùn)練結(jié)果預(yù)測

訓(xùn)練結(jié)果預(yù)測需要用到兩個文件,分別是yolo.py和predict.py。

我們首先需要去yolo.py里面修改model_path以及classes_path,這兩個參數(shù)必須要修改。

model_path指向訓(xùn)練好的權(quán)值文件,在logs文件夾里。

classes_path指向檢測類別所對應(yīng)的txt。

完成修改后就可以運行predict.py進行檢測了。運行后輸入圖片路徑即可檢測。

以上就是Keras搭建M2Det目標(biāo)檢測平臺示例的詳細(xì)內(nèi)容,更多關(guān)于Keras M2Det目標(biāo)檢測的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • python 雙循環(huán)遍歷list 變量判斷代碼

    python 雙循環(huán)遍歷list 變量判斷代碼

    這篇文章主要介紹了python 雙循環(huán)遍歷list 變量判斷代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-05-05
  • python 自動識別并連接串口的實現(xiàn)

    python 自動識別并連接串口的實現(xiàn)

    這篇文章主要介紹了python 自動識別并連接串口的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Python打包exe時各種異常處理方案總結(jié)

    Python打包exe時各種異常處理方案總結(jié)

    今天教大家用Python打包exe時各種異常處理的方案總結(jié),下文中有非常詳細(xì)的介紹,對正在學(xué)習(xí)python的小伙伴們很有幫助喲,需要的朋友可以參考下
    2021-05-05
  • python redis 批量設(shè)置過期key過程解析

    python redis 批量設(shè)置過期key過程解析

    這篇文章主要介紹了python redis 批量設(shè)置過期key過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-11-11
  • 詳解Django模版中加載靜態(tài)文件配置方法

    詳解Django模版中加載靜態(tài)文件配置方法

    這篇文章主要介紹了Django模版中加載靜態(tài)文件配置方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-07-07
  • python3 遍歷刪除特定后綴名文件的方法

    python3 遍歷刪除特定后綴名文件的方法

    下面小編就為大家分享一篇python3 遍歷刪除特定后綴名文件的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-04-04
  • Python如何在腳本中設(shè)置環(huán)境變量

    Python如何在腳本中設(shè)置環(huán)境變量

    環(huán)境變量是與系統(tǒng)進程交互的一種深入方式,它允許用戶獲得有關(guān)系統(tǒng)屬性、路徑和已經(jīng)存在的變量的更詳細(xì)信息,下面我們就來看看Python是如何通過腳本來設(shè)置環(huán)境變量的吧
    2023-10-10
  • Python生成隨機MAC地址

    Python生成隨機MAC地址

    這篇文章主要介紹了Python生成隨機MAC地址的相關(guān)資料,需要的朋友可以參考下
    2015-03-03
  • 一起來學(xué)習(xí)一下python的數(shù)據(jù)類型

    一起來學(xué)習(xí)一下python的數(shù)據(jù)類型

    這篇文章主要為大家詳細(xì)介紹了python的數(shù)據(jù)類型,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下希望能夠給你帶來幫助
    2022-01-01
  • python通過索引遍歷列表的方法

    python通過索引遍歷列表的方法

    這篇文章主要介紹了python通過索引遍歷列表的方法,實例分析了Python遍歷列表的相關(guān)技巧,非常具有實用價值,需要的朋友可以參考下
    2015-05-05

最新評論