【YOLOv3 net】網(wǎng)絡(luò)結(jié)構(gòu)及代碼詳解

1 總體介紹

yolov3主要包括兩部分,一個是主干網(wǎng)絡(luò)(backbone)部分课幕,一個是使用特征金字塔(FPN)融合、加強(qiáng)特征提取并利用卷積進(jìn)行預(yù)測部分

yolov3網(wǎng)絡(luò)總體框圖如下:


yolov3網(wǎng)絡(luò)結(jié)構(gòu)框圖

2 yolov3骨干網(wǎng)絡(luò)

采用darknet53網(wǎng)絡(luò),詳解見參考鏈接

3 FPN特征融合

YoloV3提取多特征層進(jìn)行目標(biāo)檢測,一共提取三個特征層驯绎。

三個特征層位于主干部分Darknet53的不同位置,分別位于中間層谋旦,中下層剩失,底層,三個特征層的shape分別為(52,52,256)册着、(26,26,512)拴孤、(13,13,1024)。

在獲得三個有效特征層后甲捏,利用這三個有效特征層進(jìn)行FPN層的構(gòu)建演熟。

構(gòu)建方式理解1

  • 13x13x1024的特征層進(jìn)行5次卷積處理,處理完后的結(jié)果分兩路走,一路利用YoloHead(下面一小節(jié)介紹)獲得預(yù)測結(jié)果芒粹,一路用于進(jìn)行卷積(降低通道數(shù))+上采樣UmSampling2d(c通道數(shù)不變兄纺,w,h尺寸變?yōu)樵瓉?倍)后與26x26x512特征層進(jìn)行結(jié)合,結(jié)合特征層的shape為(26,26,768)化漆。
  • 結(jié)合特征層再次進(jìn)行5次卷積處理估脆,處理完后的結(jié)果分兩路走,一路利用YoloHead(下面一小節(jié)介紹)獲得預(yù)測結(jié)果获三,一路用于進(jìn)行卷積(降低通道數(shù))+上采樣UmSampling2d(c通道數(shù)不變旁蔼,w,h尺寸變?yōu)樵瓉?倍)后與52x52x256特征層進(jìn)行結(jié)合,結(jié)合特征層的shape為(52,52,384)疙教。
  • 結(jié)合特征層再次進(jìn)行5次卷積處理棺聊,處理完后利用YoloHead獲得預(yù)測結(jié)果。

構(gòu)建方式理解2

  • backbone最后一層經(jīng)過5次卷積得到一層特征圖贞谓,從這兒分成兩路限佩,一路去做預(yù)測,一路上采樣和上一個Block進(jìn)行融合裸弦,類推形成特征金字塔(FPN祟同,F(xiàn)eature Pyramid Networks,用于解決目標(biāo)檢測中的多尺度變化問題)
  • 預(yù)測那一路包括分類預(yù)測和回歸預(yù)測理疙,使用的是3x3的卷積和1x1的卷積晕城,最后得到的特征圖尺寸為13x13,75的由來窖贤,每個像素點(diǎn)(網(wǎng)格思想)上有3個先驗(yàn)框砖顷,每個先驗(yàn)框?qū)儆诿恳活惖母怕?voc有20類)、是否有物體(1個參數(shù))赃梧、對應(yīng)的調(diào)整參數(shù)(4個參數(shù)滤蝠,中心點(diǎn)x,y坐標(biāo),框?qū)抴和高h(yuǎn))授嘀,因此最后的13,13,75的理解為:
13,13,75 ->13,13,3x25 -> 13,13,3x[20(屬于某一類的概率物咳,voc有20類)+1(是否有物體)+4(調(diào)整參數(shù))]
  • 上采樣那一路,和上一層的輸出特征圖融合后蹄皱,做5次卷積得到一層特征圖览闰,然后分成兩路,一路去做預(yù)測巷折,一路去焕济。。盔几。
    ps:先驗(yàn)框是預(yù)設(shè)的(見下方---9種尺度的先驗(yàn)框---解讀)晴弃,經(jīng)過網(wǎng)絡(luò)訓(xùn)練,參數(shù)調(diào)整后才能變成預(yù)測框

特征金字塔可以將不同shape的特征層進(jìn)行特征融合,有利于提取出更好的特征上鞠。

4 利用Yolo Head獲得預(yù)測結(jié)果

利用FPN特征金字塔际邻,可以獲得三個加強(qiáng)特征,這三個加強(qiáng)特征的shape分別為(13,13,512)芍阎、(26,26,256)世曾、(52,52,128),然后利用這三個shape的特征層傳入Yolo Head獲得預(yù)測結(jié)果谴咸。

Yolo Head本質(zhì)上是一次3x3卷積加上一次1x1卷積轮听,3x3卷積的作用是特征整合,1x1卷積的作用是調(diào)整通道數(shù)岭佳。

對所獲得的三個加強(qiáng)特征層分別進(jìn)行處理血巍,假設(shè)預(yù)測是的VOC數(shù)據(jù)集,則輸出層的shape分別為(13,13,75)珊随,(26,26,75)述寡,(52,52,75),最后一個維度為75是因?yàn)樵摾邮腔?strong>voc數(shù)據(jù)集的叶洞,它的類別數(shù)為20種鲫凶,YoloV3針對每一個特征層的每一個特征點(diǎn)存在3個先驗(yàn)框,所以預(yù)測結(jié)果的通道數(shù)為3x25(20個類別衩辟,每個類別都有一個概率 + 4調(diào)整參數(shù) + 1是否有物體)螟炫;

如果使用的是coco訓(xùn)練集,類別數(shù)則為80種艺晴,最后的維度應(yīng)該為255 = 3x85昼钻,三個特征層的shape為(13,13,255),(26,26,255)财饥,(52,52,255)换吧。coco小總結(jié):輸入N張416x416的圖片折晦,在經(jīng)過多層的運(yùn)算后钥星,會輸出三個shape分別為(N,13,13,255),(N,26,26,255)满着,(N,52,52,255)的數(shù)據(jù)谦炒,分別對應(yīng)每個圖為13x13、26x26风喇、52x52的網(wǎng)格上3個先驗(yàn)框的位置預(yù)測情況宁改。

5 不同尺度的先驗(yàn)框anchor box

5.1 理論介紹

定義:anchor box是從訓(xùn)練集的所有g(shù)round truth box中統(tǒng)計(jì)(使用k-means)出來的在訓(xùn)練集中最經(jīng)常出現(xiàn)的幾個box形狀和尺寸。

9種尺度先驗(yàn)框:隨著輸出的特征圖的數(shù)量和尺度的變化魂莫,先驗(yàn)框的尺寸也需要相應(yīng)的調(diào)整还蹲。YOLOv2已經(jīng)開始采用K-means聚類得到先驗(yàn)框的尺寸,YOLOv3延續(xù)了這種方法,為每種下采樣尺度設(shè)定3種先驗(yàn)框谜喊,總共聚類出9種尺寸的先驗(yàn)框潭兽。在COCO數(shù)據(jù)集這9個先驗(yàn)框(voc用這個也ok,一般不改)是:(10x13)斗遏,(16x30)山卦,(33x23),(30x61)诵次,(62x45)账蓉,(59x119),(116x90)逾一,(156x198)铸本,(373x326)。

如何分配:在最小的13x13特征圖上(有最大的感受野)應(yīng)用較大的先驗(yàn)框(116x90)嬉荆,(156x198)归敬,(373x326),適合檢測較大的對象鄙早。中等的26x26特征圖上(中等感受野)應(yīng)用中等的先驗(yàn)框(30x61)汪茧,(62x45),(59x119)限番,適合檢測中等大小的對象舱污。較大的52x52特征圖上(較小的感受野)應(yīng)用較小的先驗(yàn)框(10x13),(16x30)弥虐,(33x23)扩灯,適合檢測較小的對象。

特征圖-感受野-先驗(yàn)框

5.2 代碼讀取

yolo_anchors.txt中存有下列數(shù)據(jù)

10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
import numpy as np
#---------------------------------------------------#
#   獲得先驗(yàn)框
#---------------------------------------------------#
def get_anchors(anchors_path):
    '''loads the anchors from a file'''
    with open(anchors_path, encoding='utf-8') as f:
        anchors = f.readline()
    anchors = [float(x) for x in anchors.split(',')]
    anchors = np.array(anchors).reshape(-1, 2)
    return anchors, len(anchors)

anchors_path    = 'model_data/yolo_anchors.txt'
# anchors: ndarray:(9,2)        num_anchors:int  9   
anchors, num_anchors     = get_anchors(anchors_path)
print(anchors)
[[ 10.  13.]
 [ 16.  30.]
 [ 33.  23.]
 [ 30.  61.]
 [ 62.  45.]
 [ 59. 119.]
 [116.  90.]
 [156. 198.]
 [373. 326.]]

6 yolov3整體網(wǎng)絡(luò)結(jié)構(gòu)代碼理解

結(jié)合backbone------darknet53的代碼霜瘪,可直接運(yùn)行下列代碼

from collections import OrderedDict
import torch
import torch.nn as nn
from nets.darknet import darknet53      # darknet53的分析可見http://www.reibang.com/p/6b4675a9f378


def conv2d(filter_in, filter_out, kernel_size):
    pad = (kernel_size - 1) // 2 if kernel_size else 0
    return nn.Sequential(OrderedDict([
        ("conv", nn.Conv2d(filter_in, filter_out, kernel_size=kernel_size, stride=1, padding=pad, bias=False)),
        ("bn", nn.BatchNorm2d(filter_out)),
        ("relu", nn.LeakyReLU(0.1)),
    ]))

#------------------------------------------------------------------------#
#   make_last_layers里面一共有七個卷積珠插,前五個用于提取特征。
#   后兩個用于獲得yolo網(wǎng)絡(luò)的預(yù)測結(jié)果颖对,稱之為yolo head
#------------------------------------------------------------------------#
def make_last_layers(filters_list, in_filters, out_filter):
    m = nn.Sequential(
        conv2d(in_filters, filters_list[0], 1),         # 1表示kernel_size
        conv2d(filters_list[0], filters_list[1], 3),
        conv2d(filters_list[1], filters_list[0], 1),
        conv2d(filters_list[0], filters_list[1], 3),
        conv2d(filters_list[1], filters_list[0], 1),
        conv2d(filters_list[0], filters_list[1], 3),
        nn.Conv2d(filters_list[1], out_filter, kernel_size=1, stride=1, padding=0, bias=True)
    )
    return m

# ---------------------------------------------------#
#   獲得類捻撑,`voc_classes.txt`中包含voc數(shù)據(jù)集中所有類別名稱
# ---------------------------------------------------#
def get_classes(classes_path):
    with open(classes_path, encoding='utf-8') as f:
        class_names = f.readlines()
    class_names = [c.strip() for c in class_names]
    return class_names, len(class_names)

class YoloBody(nn.Module):
    def __init__(self, anchors_mask, num_classes):
        super(YoloBody, self).__init__()
        #---------------------------------------------------#   
        #   生成darknet53的主干模型
        #   獲得三個有效特征層,他們的shape分別是:
        #   52,52,256
        #   26,26,512
        #   13,13,1024
        #---------------------------------------------------#
        self.backbone = darknet53()

        #---------------------------------------------------#
        #   out_filters : [64, 128, 256, 512, 1024]缤底,利用最后三個進(jìn)行FPN融合
        #---------------------------------------------------#
        out_filters = self.backbone.layers_out_filters      # 表示Darknet53網(wǎng)絡(luò)幾個結(jié)構(gòu)塊的輸出通道數(shù)顾患,make_last_layers中用到此處

        #------------------------------------------------------------------------#
        #   計(jì)算yolo_head的輸出通道數(shù),對于voc數(shù)據(jù)集而言
        #   final_out_filter0 = final_out_filter1 = final_out_filter2 = 75
        #   final_out_filter0 = len(anchors_mask[0]) * (num_classes + 5) = 3*(20+5)
        #   3*(20+5)含義:
        #       3表示網(wǎng)格點(diǎn)上先驗(yàn)框個數(shù)个唧,
        #       20表示voc分類類別數(shù)江解,coco是80類,5:
        #       4個先驗(yàn)框框調(diào)整參數(shù)+1表示網(wǎng)格內(nèi)是否有物體
        #   anchors_mask:表示先驗(yàn)框尺寸變化徙歼,通常有9種犁河,一般不改鳖枕,具體詳見正文分析
        #------------------------------------------------------------------------#
        self.last_layer0            = make_last_layers([512, 1024], out_filters[-1], len(anchors_mask[0]) * (num_classes + 5))

        self.last_layer1_conv       = conv2d(512, 256, 1)   # 2D卷積,降低通道數(shù)
        self.last_layer1_upsample   = nn.Upsample(scale_factor=2, mode='nearest')   # 上采樣:c通道數(shù)不變桨螺,w,h尺寸變?yōu)樵瓉?倍
        self.last_layer1            = make_last_layers([256, 512], out_filters[-2] + 256, len(anchors_mask[1]) * (num_classes + 5))

        self.last_layer2_conv       = conv2d(256, 128, 1)
        self.last_layer2_upsample   = nn.Upsample(scale_factor=2, mode='nearest')
        self.last_layer2            = make_last_layers([128, 256], out_filters[-3] + 128, len(anchors_mask[2]) * (num_classes + 5))

    def forward(self, x):
        #---------------------------------------------------#   
        #   獲得三個有效特征層耕魄,他們的shape分別是:
        #   52,52,256;26,26,512彭谁;13,13,1024
        #---------------------------------------------------#
        x2, x1, x0 = self.backbone(x)       # backbone return out3, out4, out5

        #---------------------------------------------------#
        #   第一個特征層
        #   out0 = (batch_size,255,13,13)
        #---------------------------------------------------#
        # 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512
        # yolo head中有七層卷積(nn.Sequential整合的)吸奴,前5層提取特征,同時其輸出要進(jìn)行 卷積+上采樣 去和上一個layer輸出融合形成FPN缠局。
        # 故這個地方[:5]和[5:]
        out0_branch = self.last_layer0[:5](x0)
        out0        = self.last_layer0[5:](out0_branch)     # torch.size([1,75,13,13])

        # 13,13,512 -> 13,13,256 -> 26,26,256
        x1_in = self.last_layer1_conv(out0_branch)      # {Tensor:1}
        x1_in = self.last_layer1_upsample(x1_in)        # {Tensor:1}

        # 26,26,256 + 26,26,512 -> 26,26,768
        x1_in = torch.cat([x1_in, x1], 1)       # 所謂融合也就是特征圖拼接则奥,層數(shù)變多   # 后一個參數(shù)1的作用  {Tensor:1}  torch.size([1,768,26,26])
        #---------------------------------------------------#
        #   第二個特征層
        #   out1 = (batch_size,255,26,26)
        #---------------------------------------------------#
        # 26,26,768 -> 26,26,256 -> 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256
        out1_branch = self.last_layer1[:5](x1_in)
        out1        = self.last_layer1[5:](out1_branch)     # torch.size([1,75,26,26])

        # 26,26,256 -> 26,26,128 -> 52,52,128
        x2_in = self.last_layer2_conv(out1_branch)
        x2_in = self.last_layer2_upsample(x2_in)

        # 52,52,128 + 52,52,256 -> 52,52,384
        x2_in = torch.cat([x2_in, x2], 1)           # torch.size([1,384,52,52])
        #---------------------------------------------------#
        #   第一個特征層
        #   out3 = (batch_size,255,52,52)
        #---------------------------------------------------#
        # 52,52,384 -> 52,52,128 -> 52,52,256 -> 52,52,128 -> 52,52,256 -> 52,52,128
        out2 = self.last_layer2(x2_in)      # torch.size([1,75,52,52])
        return out0, out1, out2

if __name__ == '__main__':
    # -------------voc_classes.txt內(nèi)容-----------------------#
    # aeroplane
    # bicycle
    # bird
    # boat
    # bottle
    # bus
    # car
    # cat
    # chair
    # cow
    # diningtable
    # dog
    # horse
    # motorbike
    # person
    # pottedplant
    # sheep
    # sofa
    # train
    # tvmonitor
    # ------------------------------------------------#
    classes_path = '../model_data/voc_classes.txt'
    class_names, num_classes = get_classes(classes_path)
    anchors_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]]  # 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
    model = YoloBody(anchors_mask, num_classes)
    print(model)      # 包括網(wǎng)絡(luò)結(jié)構(gòu)和參數(shù)

    from thop import profile            # 用來測試網(wǎng)絡(luò)能夠跑通,同時可查看FLOPs和params
    input = torch.randn(1, 3, 416, 416)     # 1張3通道尺寸為416x416的圖片作為輸入
    flops, params = profile(model, (input,))
    print(flops, params)

感謝鏈接

https://blog.csdn.net/weixin_44791964/article/details/105310627
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狭园,一起剝皮案震驚了整個濱河市读处,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唱矛,老刑警劉巖罚舱,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绎谦,居然都是意外死亡管闷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門窃肠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來包个,“玉大人,你說我怎么就攤上這事冤留”棠遥” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵纤怒,是天一觀的道長糯而。 經(jīng)常有香客問我,道長泊窘,這世上最難降的妖魔是什么熄驼? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮州既,結(jié)果婚禮上谜洽,老公的妹妹穿的比我還像新娘萝映。我一直安慰自己吴叶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布序臂。 她就那樣靜靜地躺著蚌卤,像睡著了一般实束。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逊彭,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天咸灿,我揣著相機(jī)與錄音,去河邊找鬼侮叮。 笑死避矢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的囊榜。 我是一名探鬼主播审胸,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卸勺!你這毒婦竟也來了砂沛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤曙求,失蹤者是張志新(化名)和其女友劉穎碍庵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悟狱,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡静浴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了挤渐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片马绝。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挣菲,靈堂內(nèi)的尸體忽然破棺而出富稻,到底是詐尸還是另有隱情,我是刑警寧澤白胀,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布椭赋,位于F島的核電站,受9級特大地震影響或杠,放射性物質(zhì)發(fā)生泄漏哪怔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一向抢、第九天 我趴在偏房一處隱蔽的房頂上張望认境。 院中可真熱鬧,春花似錦挟鸠、人聲如沸叉信。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硼身。三九已至硅急,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佳遂,已是汗流浹背营袜。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丑罪,地道東北人荚板。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像吩屹,于是被迫代替她去往敵國和親啸驯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容