1 總體介紹
yolov3主要包括兩部分,一個是主干網(wǎng)絡(luò)(backbone)部分课幕,一個是使用特征金字塔(FPN)融合、加強(qiáng)特征提取并利用卷積進(jìn)行預(yù)測部分
yolov3網(wǎng)絡(luò)總體框圖如下:
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)扩灯,適合檢測較小的對象。
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