python神經(jīng)網(wǎng)絡(luò)Keras搭建RFBnet目標(biāo)檢測平臺
什么是RFBnet目標(biāo)檢測算法
RFBnet是SSD的一種加強(qiáng)版,主要是利用了膨脹卷積這一方法增大了感受野,相比于普通的ssd,RFBnet也是一種加強(qiáng)吧
RFBnet是改進(jìn)版的SSD,其整體的結(jié)構(gòu)與SSD相差不大,其主要特點(diǎn)是在SSD的特征提取網(wǎng)絡(luò)上用了RFB模塊。
RFB的全稱Receptive Field Block,是一種輕量級的、而且集成了各類檢測算法優(yōu)點(diǎn)的模塊,結(jié)合了Inception、蟲洞卷積的思想,以提高感受野的方式提高網(wǎng)絡(luò)的特征提取能力。
RFBnet實(shí)現(xiàn)思路
一、預(yù)測部分
1、主干網(wǎng)絡(luò)介紹
RFBnet采用的主干網(wǎng)絡(luò)是VGG網(wǎng)絡(luò),關(guān)于VGG的介紹大家可以看我的另外一篇博客http://www.dbjr.com.cn/article/246917.htm,這里的VGG網(wǎng)絡(luò)相比普通的VGG網(wǎng)絡(luò)有一定的修改,主要修改的地方就是:
1、將VGG16的FC6和FC7層轉(zhuǎn)化為卷積層。
2、增加了RFB模塊。
主要使用到的RFB模塊有兩種,一種是BasicRFB,另一種是BasicRFB_a。
二者使用的思想相同,構(gòu)造有些許不同。
BasicRFB的結(jié)構(gòu)如下:
BasicRFB_a和BasicRFB類似,并聯(lián)結(jié)構(gòu)增加,有8個(gè)并聯(lián)。
實(shí)現(xiàn)代碼:
from keras.layers import (Activation, BatchNormalization, Conv2D, Lambda, MaxPooling2D, UpSampling2D, concatenate) 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()(x) if relu: x = Activation("relu")(x) return x def BasicRFB(x,input_filters,output_filters,stride=1,map_reduce=8): #-------------------------------------------------------# # BasicRFB模塊是一個(gè)殘差結(jié)構(gòu) # 主干部分使用不同膨脹率的卷積進(jìn)行特征提取 # 殘差邊只包含一個(gè)調(diào)整寬高和通道的1x1卷積 #-------------------------------------------------------# 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) #-------------------------------------------------------# # 將不同膨脹率的卷積結(jié)果進(jìn)行堆疊 # 利用1x1卷積調(diào)整通道數(shù) #-------------------------------------------------------# 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_a(x, input_filters, output_filters, stride=1, map_reduce=8): #-------------------------------------------------------# # BasicRFB_a模塊也是一個(gè)殘差結(jié)構(gòu) # 主干部分使用不同膨脹率的卷積進(jìn)行特征提取 # 殘差邊只包含一個(gè)調(diào)整寬高和通道的1x1卷積 #-------------------------------------------------------# 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,stride=stride) 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,stride=stride) 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,stride=stride) 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,stride=stride) 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) #-------------------------------------------------------# # 將不同膨脹率的卷積結(jié)果進(jìn)行堆疊 # 利用1x1卷積調(diào)整通道數(shù) #-------------------------------------------------------# 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 #--------------------------------# # 取Conv4_3和fc7進(jìn)行特征融合 #--------------------------------# def Normalize(net): # 38,38,512 -> 38,38,256 branch_0 = conv2d_bn(net["conv4_3"], 256, 1, 1) # 19,19,512 -> 38,38,256 branch_1 = conv2d_bn(net['fc7'], 256, 1, 1) branch_1 = UpSampling2D()(branch_1) # 38,38,256 + 38,38,256 -> 38,38,512 out = concatenate([branch_0,branch_1],axis=-1) # 38,38,512 -> 38,38,512 out = BasicRFB_a(out,512,512) return out def backbone(input_tensor): #----------------------------主干特征提取網(wǎng)絡(luò)開始---------------------------# # RFB結(jié)構(gòu),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']) #----------------------------------------------------------# # conv4_3 38,38,512 -> 38,38,512 net['norm'] # fc7 19,19,1024 -> #----------------------------------------------------------# net['norm'] = Normalize(net) # 19,19,1024 -> 19,19,1024 net['rfb_1'] = BasicRFB(net['fc7'],1024,1024) # 19,19,1024 -> 10,10,512 net['rfb_2'] = BasicRFB(net['rfb_1'],1024,512,stride=2) # 10,10,512 -> 5,5,256 net['rfb_3'] = BasicRFB(net['rfb_2'],512,256,stride=2) # 5,5,256 -> 5,5,128 net['conv6_1'] = conv2d_bn(net['rfb_3'],128,1,1) # 5,5,128 -> 3,3,256 net['conv6_2'] = conv2d_bn(net['conv6_1'],256,3,3,padding="valid") # 3,3,256 -> 3,3,128 net['conv7_1'] = conv2d_bn(net['conv6_2'],128,1,1) # 3,3,128 -> 1,1,256 net['conv7_2'] = conv2d_bn(net['conv7_1'],256,3,3,padding="valid") return net
2、從特征獲取預(yù)測結(jié)果
由上圖我們可以知道,我們?nèi)onv4的第三次卷積的特征、fc7的特征進(jìn)行組合后經(jīng)過一個(gè)BasicRFB_a獲得P3作為有效特征層、還有上圖的P4、P5、P6、P7、P8作為有效特征層,為了和普通特征層區(qū)分,我們稱之為有效特征層,來獲取預(yù)測結(jié)果。
對獲取到的每一個(gè)有效特征層,我們分別對其進(jìn)行一次num_anchors x 4的卷積、一次num_anchors x num_classes的卷積。而num_anchors指的是該特征層所擁有的先驗(yàn)框數(shù)量。
其中:num_anchors x 4的卷積 用于預(yù)測 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)先驗(yàn)框的變化情況。(為什么說是變化情況呢,這是因?yàn)閟sd的預(yù)測結(jié)果需要結(jié)合先驗(yàn)框獲得預(yù)測框,預(yù)測結(jié)果就是先驗(yàn)框的變化情況。)
num_anchors x num_classes的卷積 用于預(yù)測 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)預(yù)測框?qū)?yīng)的種類。
每一個(gè)有效特征層對應(yīng)的先驗(yàn)框?qū)?yīng)著該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 預(yù)先設(shè)定好的三個(gè)框。
所有的特征層對應(yīng)的預(yù)測結(jié)果的shape如下:
實(shí)現(xiàn)代碼為:
from keras.layers import (Activation, Concatenate, Conv2D, Flatten, Input, Reshape) from keras.models import Model from nets.backbone import backbone def RFB300(input_shape, num_classes=21): #---------------------------------# # 典型的輸入大小為[300,300,3] #---------------------------------# input_tensor = Input(shape=input_shape) # net變量里面包含了整個(gè)RFB的結(jié)構(gòu),通過層名可以找到對應(yīng)的特征層 net = backbone(input_tensor) #-----------------------將提取到的主干特征進(jìn)行處理---------------------------# # 對conv4_3的通道進(jìn)行l(wèi)2標(biāo)準(zhǔn)化處理 # 38,38,512 num_anchors = 6 # 預(yù)測框的處理 # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,4是x,y,h,w的調(diào)整 net['norm_mbox_loc'] = Conv2D(num_anchors * 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_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,num_classes是所分的類 net['norm_mbox_conf'] = Conv2D(num_anchors * 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']) # 對rfb_1層進(jìn)行處理 # 19,19,1024 num_anchors = 6 # 預(yù)測框的處理 # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,4是x,y,h,w的調(diào)整 net['rfb_1_mbox_loc'] = Conv2D(num_anchors * 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_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,num_classes是所分的類 net['rfb_1_mbox_conf'] = Conv2D(num_anchors * 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']) # 對rfb_2進(jìn)行處理 # 10,10,512 num_anchors = 6 # 預(yù)測框的處理 # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,4是x,y,h,w的調(diào)整 net['rfb_2_mbox_loc'] = Conv2D(num_anchors * 4, kernel_size=(3,3), padding='same',name='rfb_2_mbox_loc')(net['rfb_2']) net['rfb_2_mbox_loc_flat'] = Flatten(name='rfb_2_mbox_loc_flat')(net['rfb_2_mbox_loc']) # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,num_classes是所分的類 net['rfb_2_mbox_conf'] = Conv2D(num_anchors * num_classes, kernel_size=(3,3), padding='same',name='rfb_2_mbox_conf')(net['rfb_2']) net['rfb_2_mbox_conf_flat'] = Flatten(name='rfb_2_mbox_conf_flat')(net['rfb_2_mbox_conf']) # 對rfb_3進(jìn)行處理 # 5,5,256 num_anchors = 6 # 預(yù)測框的處理 # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,4是x,y,h,w的調(diào)整 net['rfb_3_mbox_loc'] = Conv2D(num_anchors * 4, kernel_size=(3,3), padding='same',name='rfb_3_mbox_loc')(net['rfb_3']) net['rfb_3_mbox_loc_flat'] = Flatten(name='rfb_3_mbox_loc_flat')(net['rfb_3_mbox_loc']) # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,num_classes是所分的類 net['rfb_3_mbox_conf'] = Conv2D(num_anchors * num_classes, kernel_size=(3,3), padding='same',name='rfb_3_mbox_conf')(net['rfb_3']) net['rfb_3_mbox_conf_flat'] = Flatten(name='rfb_3_mbox_conf_flat')(net['rfb_3_mbox_conf']) # 對conv6_2進(jìn)行處理 # 3,3,256 num_anchors = 4 # 預(yù)測框的處理 # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,4是x,y,h,w的調(diào)整 net['conv6_2_mbox_loc'] = Conv2D(num_anchors * 4, kernel_size=(3,3), padding='same',name='conv6_2_mbox_loc')(net['conv6_2']) net['conv6_2_mbox_loc_flat'] = Flatten(name='conv6_2_mbox_loc_flat')(net['conv6_2_mbox_loc']) # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,num_classes是所分的類 net['conv6_2_mbox_conf'] = Conv2D(num_anchors * num_classes, kernel_size=(3,3), padding='same',name='conv6_2_mbox_conf')(net['conv6_2']) net['conv6_2_mbox_conf_flat'] = Flatten(name='conv6_2_mbox_conf_flat')(net['conv6_2_mbox_conf']) # 對conv7_2進(jìn)行處理 # 1,1,256 num_anchors = 4 # 預(yù)測框的處理 # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,4是x,y,h,w的調(diào)整 net['conv7_2_mbox_loc'] = Conv2D(num_anchors * 4, kernel_size=(3,3), padding='same',name='conv7_2_mbox_loc')(net['conv7_2']) net['conv7_2_mbox_loc_flat'] = Flatten(name='conv7_2_mbox_loc_flat')(net['conv7_2_mbox_loc']) # num_anchors表示每個(gè)網(wǎng)格點(diǎn)先驗(yàn)框的數(shù)量,num_classes是所分的類 net['conv7_2_mbox_conf'] = Conv2D(num_anchors * num_classes, kernel_size=(3,3), padding='same',name='conv7_2_mbox_conf')(net['conv7_2']) net['conv7_2_mbox_conf_flat'] = Flatten(name='conv7_2_mbox_conf_flat')(net['conv7_2_mbox_conf']) # 將所有結(jié)果進(jìn)行堆疊 net['mbox_loc'] = Concatenate(axis=1, name='mbox_loc')([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']]) net['mbox_conf'] = Concatenate(axis=1, name='mbox_conf')([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']]) # 11620,4 net['mbox_loc'] = Reshape((-1, 4), name='mbox_loc_final')(net['mbox_loc']) # 11620,21 net['mbox_conf'] = Reshape((-1, num_classes), name='mbox_conf_logits')(net['mbox_conf']) net['mbox_conf'] = Activation('softmax', name='mbox_conf_final')(net['mbox_conf']) # 11620,25 net['predictions'] = Concatenate(axis =-1, name='predictions')([net['mbox_loc'], net['mbox_conf']]) model = Model(net['input'], net['predictions']) return model
3、預(yù)測結(jié)果的解碼
我們通過對每一個(gè)特征層的處理,可以獲得三個(gè)內(nèi)容,分別是:
num_anchors x 4的卷積 用于預(yù)測 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)先驗(yàn)框的變化情況。**
num_anchors x num_classes的卷積 用于預(yù)測 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)預(yù)測框?qū)?yīng)的種類。
每一個(gè)有效特征層對應(yīng)的先驗(yàn)框?qū)?yīng)著該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 預(yù)先設(shè)定好的多個(gè)框。
我們利用 num_anchors x 4的卷積 與 每一個(gè)有效特征層對應(yīng)的先驗(yàn)框 獲得框的真實(shí)位置。
每一個(gè)有效特征層對應(yīng)的先驗(yàn)框就是,如圖所示的作用:
每一個(gè)有效特征層將整個(gè)圖片分成與其長寬對應(yīng)的網(wǎng)格,如conv4-3和fl7組合成的特征層就是將整個(gè)圖像分成38x38個(gè)網(wǎng)格;然后從每個(gè)網(wǎng)格中心建立多個(gè)先驗(yàn)框,如conv4-3和fl7組合成的有效特征層就是建立了6個(gè)先驗(yàn)框;
對于conv4-3和fl7組合成的特征層來講,整個(gè)圖片被分成38x38個(gè)網(wǎng)格,每個(gè)網(wǎng)格中心對應(yīng)6個(gè)先驗(yàn)框,一共包含了,38x38x6個(gè),8664個(gè)先驗(yàn)框。
先驗(yàn)框雖然可以代表一定的框的位置信息與框的大小信息,但是其是有限的,無法表示任意情況,因此還需要調(diào)整,RFBnet利用num_anchors x 4的卷積的結(jié)果對先驗(yàn)框進(jìn)行調(diào)整。
num_anchors x 4中的num_anchors表示了這個(gè)網(wǎng)格點(diǎn)所包含的先驗(yàn)框數(shù)量,其中的4表示了x_offset、y_offset、h和w的調(diào)整情況。
x_offset與y_offset代表了真實(shí)框距離先驗(yàn)框中心的xy軸偏移情況。h和w代表了真實(shí)框的寬與高相對于先驗(yàn)框的變化情況。
RFBnet解碼過程就是將每個(gè)網(wǎng)格的中心點(diǎn)加上它對應(yīng)的x_offset和y_offset,加完后的結(jié)果就是預(yù)測框的中心,然后再利用 先驗(yàn)框和h、w結(jié)合 計(jì)算出預(yù)測框的長和寬。這樣就能得到整個(gè)預(yù)測框的位置了。
當(dāng)然得到最終的預(yù)測結(jié)構(gòu)后還要進(jìn)行得分排序與非極大抑制篩選這一部分基本上是所有目標(biāo)檢測通用的部分。
1、取出每一類得分大于self.obj_threshold的框和得分。
2、利用框的位置和得分進(jìn)行非極大抑制。
實(shí)現(xiàn)代碼如下:
def decode_boxes(self, mbox_loc, anchors, variances): # 獲得先驗(yàn)框的寬與高 anchor_width = anchors[:, 2] - anchors[:, 0] anchor_height = anchors[:, 3] - anchors[:, 1] # 獲得先驗(yàn)框的中心點(diǎn) anchor_center_x = 0.5 * (anchors[:, 2] + anchors[:, 0]) anchor_center_y = 0.5 * (anchors[:, 3] + anchors[:, 1]) # 真實(shí)框距離先驗(yàn)框中心的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 # 真實(shí)框的寬與高的求取 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 # 獲取真實(shí)框的左上角與右下角 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 # 真實(shí)框的左上角與右下角進(jìn)行堆疊 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 = [] #----------------------------------------------------------------------------------------------------------------# # 對每一張圖片進(jìn)行處理,由于在predict.py的時(shí)候,我們只輸入一張圖片,所以for i in range(len(mbox_loc))只進(jìn)行一次 #----------------------------------------------------------------------------------------------------------------# for i in range(len(mbox_loc)): results.append([]) #--------------------------------# # 利用回歸結(jié)果對先驗(yàn)框進(jìn)行解碼 #--------------------------------# 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] #-----------------------------------------# # 進(jìn)行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、置信度、框的位置進(jìn)行堆疊。 #-----------------------------------------# c_pred = np.concatenate((good_boxes, labels, confs), axis=1) # 添加進(jìn)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
4、在原圖上進(jìn)行繪制
通過第三步,我們可以獲得預(yù)測框在原圖上的位置,而且這些預(yù)測框都是經(jīng)過篩選的。這些篩選后的框可以直接繪制在圖片上,就可以獲得結(jié)果了。
二、訓(xùn)練部分
1、真實(shí)框的處理
從預(yù)測部分我們知道,每個(gè)特征層的預(yù)測結(jié)果,num_anchors x 4的卷積 用于預(yù)測 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)先驗(yàn)框的變化情況。
也就是說,我們直接利用ssd網(wǎng)絡(luò)預(yù)測到的結(jié)果,并不是預(yù)測框在圖片上的真實(shí)位置,需要解碼才能得到真實(shí)位置。
而在訓(xùn)練的時(shí)候,我們需要計(jì)算loss函數(shù),這個(gè)loss函數(shù)是相對于RFB網(wǎng)絡(luò)的預(yù)測結(jié)果的。我們需要把圖片輸入到當(dāng)前的RFB網(wǎng)絡(luò)中,得到預(yù)測結(jié)果;同時(shí)還需要把真實(shí)框的信息,進(jìn)行編碼,這個(gè)編碼是把真實(shí)框的位置信息格式轉(zhuǎn)化為RFB預(yù)測結(jié)果的格式信息。
也就是,我們需要找到 每一張用于訓(xùn)練的圖片的每一個(gè)真實(shí)框?qū)?yīng)的先驗(yàn)框,并求出如果想要得到這樣一個(gè)真實(shí)框,我們的預(yù)測結(jié)果應(yīng)該是怎么樣的。
從預(yù)測結(jié)果獲得真實(shí)框的過程被稱作解碼,而從真實(shí)框獲得預(yù)測結(jié)果的過程就是編碼的過程。
因此我們只需要將解碼過程逆過來就是編碼過程了。
實(shí)現(xiàn)代碼如下:
def encode_box(self, box, return_iou=True, variances = [0.1, 0.1, 0.2, 0.2]): #---------------------------------------------# # 計(jì)算當(dāng)前真實(shí)框和先驗(yàn)框的重合情況 # iou [self.num_anchors] # encoded_box [self.num_anchors, 5] #---------------------------------------------# iou = self.iou(box) encoded_box = np.zeros((self.num_anchors, 4 + return_iou)) #---------------------------------------------# # 找到每一個(gè)真實(shí)框,重合程度較高的先驗(yàn)框 # 真實(shí)框可以由這個(gè)先驗(yàn)框來負(fù)責(zé)預(yù)測 #---------------------------------------------# assign_mask = iou > self.overlap_threshold #---------------------------------------------# # 如果沒有一個(gè)先驗(yàn)框重合度大于self.overlap_threshold # 則選擇重合度最大的為正樣本 #---------------------------------------------# if not assign_mask.any(): assign_mask[iou.argmax()] = True #---------------------------------------------# # 利用iou進(jìn)行賦值 #---------------------------------------------# if return_iou: encoded_box[:, -1][assign_mask] = iou[assign_mask] #---------------------------------------------# # 找到對應(yīng)的先驗(yàn)框 #---------------------------------------------# assigned_anchors = self.anchors[assign_mask] #---------------------------------------------# # 逆向編碼,將真實(shí)框轉(zhuǎn)化為rfb預(yù)測結(jié)果的格式 # 先計(jì)算真實(shí)框的中心與長寬 #---------------------------------------------# box_center = 0.5 * (box[:2] + box[2:]) box_wh = box[2:] - box[:2] #---------------------------------------------# # 再計(jì)算重合度較高的先驗(yàn)框的中心與長寬 #---------------------------------------------# assigned_anchors_center = (assigned_anchors[:, 0:2] + assigned_anchors[:, 2:4]) * 0.5 assigned_anchors_wh = (assigned_anchors[:, 2:4] - assigned_anchors[:, 0:2]) #------------------------------------------------# # 逆向求取rfb應(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()
利用上述代碼我們可以獲得,真實(shí)框?qū)?yīng)的所有的iou較大先驗(yàn)框,并計(jì)算了真實(shí)框?qū)?yīng)的所有iou較大的先驗(yàn)框應(yīng)該有的預(yù)測結(jié)果。
在訓(xùn)練的時(shí)候我們只需要選擇iou最大的先驗(yàn)框就行了,這個(gè)iou最大的先驗(yàn)框就是我們用來預(yù)測這個(gè)真實(shí)框所用的先驗(yàn)框。
因此我們還要經(jīng)過一次篩選,將上述代碼獲得的真實(shí)框?qū)?yīng)的所有的iou較大先驗(yàn)框的預(yù)測結(jié)果中,iou最大的那個(gè)篩選出來。
通過assign_boxes我們就獲得了,輸入進(jìn)來的這張圖片,應(yīng)該有的預(yù)測結(jié)果是什么樣子的。
實(shí)現(xiàn)代碼如下:
def assign_boxes(self, boxes): #---------------------------------------------------# # assignment分為3個(gè)部分 # :4 的內(nèi)容為網(wǎng)絡(luò)應(yīng)該有的回歸預(yù)測結(jié)果 # 4:-1 的內(nèi)容為先驗(yàn)框所對應(yīng)的種類,默認(rèn)為背景 # -1 的內(nèi)容為當(dāng)前先驗(yàn)框是否包含目標(biāo) #---------------------------------------------------# assignment = np.zeros((self.num_anchors, 4 + self.num_classes + 1)) assignment[:, 4] = 1.0 if len(boxes) == 0: return assignment # 對每一個(gè)真實(shí)框都進(jìn)行iou計(jì)算 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]求取每一個(gè)先驗(yàn)框重合度最大的真實(shí)框 #---------------------------------------------------# 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] #---------------------------------------------------# # 計(jì)算一共有多少先驗(yàn)框滿足需求 #---------------------------------------------------# assign_num = len(best_iou_idx) # 將編碼后的真實(shí)框取出 encoded_boxes = encoded_boxes[:, best_iou_mask, :] #---------------------------------------------------# # 編碼后的真實(shí)框的賦值 #---------------------------------------------------# assignment[:, :4][best_iou_mask] = encoded_boxes[best_iou_idx,np.arange(assign_num),:4] #----------------------------------------------------------# # 4代表為背景的概率,設(shè)定為0,因?yàn)檫@些先驗(yàn)框有對應(yīng)的物體 #----------------------------------------------------------# assignment[:, 4][best_iou_mask] = 0 assignment[:, 5:-1][best_iou_mask] = boxes[best_iou_idx, 4:] #----------------------------------------------------------# # -1表示先驗(yàn)框是否有對應(yīng)的物體 #----------------------------------------------------------# assignment[:, -1][best_iou_mask] = 1 # 通過assign_boxes我們就獲得了,輸入進(jìn)來的這張圖片,應(yīng)該有的預(yù)測結(jié)果是什么樣子的 return assignment
2、利用處理完的真實(shí)框與對應(yīng)圖片的預(yù)測結(jié)果計(jì)算loss
loss的計(jì)算分為三個(gè)部分:
1、獲取所有正標(biāo)簽的框的預(yù)測結(jié)果的回歸loss。
2、獲取所有正標(biāo)簽的種類的預(yù)測結(jié)果的交叉熵loss。
3、獲取一定負(fù)標(biāo)簽的種類的預(yù)測結(jié)果的交叉熵loss。
由于在RFBnet的訓(xùn)練過程中,正負(fù)樣本極其不平衡,即 存在對應(yīng)真實(shí)框的先驗(yàn)框可能只有十來個(gè),但是不存在對應(yīng)真實(shí)框的負(fù)樣本卻有幾千個(gè),這就會導(dǎo)致負(fù)樣本的loss值極大,因此我們可以考慮減少負(fù)樣本的選取,對于ssd的訓(xùn)練來講,常見的情況是取三倍正樣本數(shù)量的負(fù)樣本用于訓(xùn)練。這個(gè)三倍呢,也可以修改,調(diào)整成自己喜歡的數(shù)字。
實(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): # --------------------------------------------- # # y_true batch_size, 11620, 4 + self.num_classes + 1 # y_pred batch_size, 11620, 4 + self.num_classes # --------------------------------------------- # num_boxes = tf.to_float(tf.shape(y_true)[1]) # --------------------------------------------- # # 分類的loss # batch_size,11620,21 -> batch_size,11620 # --------------------------------------------- # conf_loss = self._softmax_loss(y_true[:, :, 4:-1], y_pred[:, :, 4:]) # --------------------------------------------- # # 框的位置的loss # batch_size,11620,4 -> batch_size,11620 # --------------------------------------------- # 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) # --------------------------------------------- # # 每一張圖的正樣本的個(gè)數(shù) # num_pos [batch_size,] # --------------------------------------------- # num_pos = tf.reduce_sum(y_true[:, :, -1], axis=-1) # --------------------------------------------- # # 每一張圖的負(fù)樣本的個(gè)數(shù) # num_neg [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個(gè)先驗(yàn)框作為負(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ù)樣本選取方式存在一些問題, # 我對該部分代碼進(jìn)行重構(gòu)。 # 求整個(gè)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é)果進(jìn)行判斷,如果該先驗(yàn)框沒有包含物體 # 那么它的不屬于背景的預(yù)測概率過大的話 # 就是難分類樣本 # --------------------------------------------- # confs_start = 4 + self.background_label_id + 1 confs_end = confs_start + self.num_classes - 1 # --------------------------------------------- # # batch_size,11620 # 把不是背景的概率求和,求和后的概率越大 # 代表越難分類。 # --------------------------------------------- # max_confs = tf.reduce_sum(y_pred[:, :, confs_start:confs_end], axis=2) # --------------------------------------------------- # # 只有沒有包含物體的先驗(yàn)框才得到保留 # 我們在整個(gè)batch里面選取最難分類的num_neg_batch個(gè) # 先驗(yàn)框作為負(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) # 進(jìn)行歸一化 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)練自己的RFB模型
首先前往Github下載對應(yīng)的倉庫,下載完后利用解壓軟件解壓,之后用編程軟件打開文件夾。注意打開的根目錄必須正確,否則相對目錄不正確的情況下,代碼將無法運(yùn)行。一定要注意打開后的根目錄是文件存放的目錄。
一、數(shù)據(jù)集的準(zhǔn)備
本文使用VOC格式進(jìn)行訓(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í)數(shù)據(jù)集的擺放已經(jīng)結(jié)束。
二、數(shù)據(jù)集的處理
在完成數(shù)據(jù)集的擺放之后,我們需要對數(shù)據(jù)集進(jìn)行下一步的處理,目的是獲得訓(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用于指定該文件運(yùn)行時(shí)計(jì)算的內(nèi)容 annotation_mode為0代表整個(gè)標(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)信息 那么就是因?yàn)閏lasses沒有設(shè)定正確 僅在annotation_mode為0和2的時(shí)候有效 ''' classes_path = 'model_data/voc_classes.txt' ''' trainval_percent用于指定(訓(xùn)練集+驗(yàn)證集)與測試集的比例,默認(rèn)情況下 (訓(xùn)練集+驗(yàn)證集):測試集 = 9:1 train_percent用于指定(訓(xùn)練集+驗(yàn)證集)中訓(xùn)練集與驗(yàn)證集的比例,默認(rèn)情況下 訓(xùn)練集:驗(yàn)證集 = 9:1 僅在annotation_mode為0和1的時(shí)候有效 ''' 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ù)集時(shí),可以自己建立一個(gè)cls_classes.txt,里面寫自己所需要區(qū)分的類別。
三、開始網(wǎng)絡(luò)訓(xùn)練
通過voc_annotation.py我們已經(jīng)生成了2007_train.txt以及2007_val.txt,此時(shí)我們可以開始訓(xùn)練了。
訓(xùn)練的參數(shù)較多,大家可以在下載庫后仔細(xì)看注釋,其中最重要的部分依然是train.py里的classes_path。
classes_path用于指向檢測類別所對應(yīng)的txt,這個(gè)txt和voc_annotation.py里面的txt一樣!訓(xùn)練自己的數(shù)據(jù)集必須要修改!
修改完classes_path后就可以運(yùn)行train.py開始訓(xùn)練了,在訓(xùn)練多個(gè)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àn)樘卣魇峭ㄓ玫摹? # 預(yù)訓(xùn)練權(quán)重對于99%的情況都必須要用,不用的話權(quán)值太過隨機(jī),特征提取效果不明顯,網(wǎng)絡(luò)訓(xùn)練的結(jié)果也不會好。 # 訓(xùn)練自己的數(shù)據(jù)集時(shí)提示維度不匹配正常,預(yù)測的東西都不一樣了自然維度不匹配 # # 如果想要斷點(diǎn)續(xù)練就將model_path設(shè)置成logs文件夾下已經(jīng)訓(xùn)練的權(quán)值文件。 # 當(dāng)model_path = ''的時(shí)候不加載整個(gè)模型的權(quán)值。 # # 此處使用的是整個(gè)模型的權(quán)重,因此是在train.py進(jìn)行加載的。 # 如果想要讓模型從主干的預(yù)訓(xùn)練權(quán)值開始訓(xùn)練,則設(shè)置model_path為主干網(wǎng)絡(luò)的權(quán)值,此時(shí)僅加載主干。 # 如果想要讓模型從0開始訓(xùn)練,則設(shè)置model_path = '',F(xiàn)reeze_Train = Fasle,此時(shí)從0開始訓(xùn)練,且沒有凍結(jié)主干的過程。 # 一般來講,從0開始訓(xùn)練效果會很差,因?yàn)闄?quán)值太過隨機(jī),特征提取效果不明顯。 #----------------------------------------------------------------------------------------------------------------------------# model_path = 'model_data/rfb_weights.h5' #------------------------------------------------------# # 輸入的shape大小 #------------------------------------------------------# input_shape = [300, 300] #----------------------------------------------------# # 可用于設(shè)定先驗(yàn)框的大小,默認(rèn)的anchors_size # 是根據(jù)voc數(shù)據(jù)集設(shè)定的,大多數(shù)情況下都是通用的! # 如果想要檢測小物體,可以修改anchors_size # 一般調(diào)小淺層先驗(yàn)框的大小就行了!因?yàn)闇\層負(fù)責(zé)小物體檢測! # 比如anchors_size = [21, 45, 99, 153, 207, 261, 315] #----------------------------------------------------# anchors_size = [30, 60, 111, 162, 213, 264, 315] #----------------------------------------------------# # 訓(xùn)練分為兩個(gè)階段,分別是凍結(jié)階段和解凍階段。 # 顯存不足與數(shù)據(jù)集大小無關(guān),提示顯存不足請調(diào)小batch_size。 # 受到BatchNorm層影響,batch_size最小為2,不能為1。 #----------------------------------------------------# #----------------------------------------------------# # 凍結(jié)階段訓(xùn)練參數(shù) # 此時(shí)模型的主干被凍結(jié)了,特征提取網(wǎng)絡(luò)不發(fā)生改變 # 占用的顯存較小,僅對網(wǎng)絡(luò)進(jìn)行微調(diào) #----------------------------------------------------# Init_Epoch = 0 Freeze_Epoch = 50 Freeze_batch_size = 16 Freeze_lr = 5e-4 #----------------------------------------------------# # 解凍階段訓(xùn)練參數(shù) # 此時(shí)模型的主干不被凍結(jié)了,特征提取網(wǎng)絡(luò)會發(fā)生改變 # 占用的顯存較大,網(wǎng)絡(luò)所有的參數(shù)都會發(fā)生改變 #----------------------------------------------------# UnFreeze_Epoch = 100 Unfreeze_batch_size = 8 Unfreeze_lr = 1e-4 #------------------------------------------------------# # 是否進(jìn)行凍結(jié)訓(xùn)練,默認(rèn)先凍結(jié)主干訓(xùn)練后解凍訓(xùn)練。 #------------------------------------------------------# Freeze_Train = True #------------------------------------------------------# # 用于設(shè)置是否使用多線程讀取數(shù)據(jù),0代表關(guān)閉多線程 # 開啟后會加快數(shù)據(jù)讀取速度,但是會占用更多內(nèi)存 # keras里開啟多線程有些時(shí)候速度反而慢了許多 # 在IO為瓶頸的時(shí)候再開啟多線程,即GPU運(yùn)算速度遠(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ù)測需要用到兩個(gè)文件,分別是yolo.py和predict.py。我們首先需要去yolo.py里面修改model_path以及classes_path,這兩個(gè)參數(shù)必須要修改。
model_path指向訓(xùn)練好的權(quán)值文件,在logs文件夾里。
classes_path指向檢測類別所對應(yīng)的txt。
完成修改后就可以運(yùn)行predict.py進(jìn)行檢測了。運(yùn)行后輸入圖片路徑即可檢測。
以上就是python神經(jīng)網(wǎng)絡(luò)Keras搭建RFBnet目標(biāo)檢測平臺的詳細(xì)內(nèi)容,更多關(guān)于Keras搭建RFBnet目標(biāo)檢測的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
pytorch中F.avg_pool1d()和F.avg_pool2d()的使用操作
這篇文章主要介紹了pytorch中F.avg_pool1d()和F.avg_pool2d()的使用操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-05-05Python實(shí)現(xiàn)數(shù)據(jù)地址實(shí)體抽取
大家好,本篇文章主要講的是Python實(shí)現(xiàn)數(shù)據(jù)地址實(shí)體抽取,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下2022-02-02pandas學(xué)習(xí)之txt與sql文件的基本操作指南
Pandas是Python的第三方庫,提供高性能易用的數(shù)據(jù)類型和分析工具,下面這篇文章主要給大家介紹了關(guān)于pandas學(xué)習(xí)之txt與sql文件的基本操作指南,需要的朋友可以參考下2021-08-08Python控制windows系統(tǒng)音量實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了Python控制windows系統(tǒng)音量實(shí)現(xiàn)實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01Python?OpenCV超詳細(xì)講解調(diào)整大小與圖像操作的實(shí)現(xiàn)
OpenCV用C++語言編寫,它具有C?++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac?OS,OpenCV主要傾向于實(shí)時(shí)視覺應(yīng)用,并在可用時(shí)利用MMX和SSE指令,本篇文章帶你通過OpenCV實(shí)現(xiàn)重調(diào)大小與圖像裁剪2022-04-04