版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
第Pytorch搭建YoloV5目標(biāo)檢測平臺實現(xiàn)過程目錄學(xué)習(xí)前言源碼下載YoloV5改進的部分(不完全)一、整體結(jié)構(gòu)解析二、網(wǎng)絡(luò)結(jié)構(gòu)解析1、主干網(wǎng)絡(luò)Backbone介紹2、構(gòu)建FPN特征金字塔進行加強特征提取3、利用YoloHead獲得預(yù)測結(jié)果三、預(yù)測結(jié)果的解碼1、獲得預(yù)測框與得分2、得分篩選與非極大抑制四、訓(xùn)練部分1、計算loss所需內(nèi)容2、正樣本的匹配過程a、匹配先驗框b、匹配特征點3、計算Loss訓(xùn)練自己的YoloV5模型一、數(shù)據(jù)集的準(zhǔn)備二、數(shù)據(jù)集的處理三、開始網(wǎng)絡(luò)訓(xùn)練四、訓(xùn)練結(jié)果預(yù)測
學(xué)習(xí)前言
這個很久都沒有學(xué),最終還是決定看看,復(fù)現(xiàn)的是YoloV5的第5版,V5有好多版本在,作者也一直在更新,我選了這個時間的倒數(shù)第二個版本。
源碼下載
/bubbliiiing/yolov5-pytorch
YoloV5改進的部分(不完全)
1、主干部分:使用了Focus網(wǎng)絡(luò)結(jié)構(gòu),具體操作是在一張圖片中每隔一個像素拿到一個值,這個時候獲得了四個獨立的特征層,然后將四個獨立的特征層進行堆疊,此時寬高信息就集中到了通道信息,輸入通道擴充了四倍。該結(jié)構(gòu)在YoloV5第5版之前有所應(yīng)用,最新版本中未使用。
2、數(shù)據(jù)增強:Mosaic數(shù)據(jù)增強、Mosaic利用了四張圖片進行拼接實現(xiàn)數(shù)據(jù)中增強,根據(jù)論文所說其擁有一個巨大的優(yōu)點是豐富檢測物體的背景!且在BN計算的時候一下子會計算四張圖片的數(shù)據(jù)!
3、多正樣本匹配:在之前的Yolo系列里面,在訓(xùn)練時每一個真實框?qū)?yīng)一個正樣本,即在訓(xùn)練時,每一個真實框僅由一個先驗框負(fù)責(zé)預(yù)測。YoloV5中為了加快模型的訓(xùn)練效率,增加了正樣本的數(shù)量,在訓(xùn)練時,每一個真實框可以由多個先驗框負(fù)責(zé)預(yù)測。
以上并非全部的改進部分,還存在一些其它的改進,這里只列出來了一些我比較感興趣,而且非常有效的改進。
一、整體結(jié)構(gòu)解析
在學(xué)習(xí)YoloV5之前,我們需要對YoloV5所作的工作有一定的了解,這有助于我們后面去了解網(wǎng)絡(luò)的細(xì)節(jié)。
和之前版本的Yolo類似,整個YoloV5可以依然可以分為三個部分,分別是Backbone,F(xiàn)PN以及YoloHead。
Backbone可以被稱作YoloV5的主干特征提取網(wǎng)絡(luò),根據(jù)它的結(jié)構(gòu)以及之前Yolo主干的叫法,我一般叫它CSPDarknet,輸入的圖片首先會在CSPDarknet里面進行特征提取,提取到的特征可以被稱作特征層,是輸入圖片的特征集合。在主干部分,我們獲取了三個特征層進行下一步網(wǎng)絡(luò)的構(gòu)建,這三個特征層我稱它為有效特征層。
FPN可以被稱作YoloV5的加強特征提取網(wǎng)絡(luò),在主干部分獲得的三個有效特征層會在這一部分進行特征融合,特征融合的目的是結(jié)合不同尺度的特征信息。在FPN部分,已經(jīng)獲得的有效特征層被用于繼續(xù)提取特征。在YoloV5里依然使用到了Panet的結(jié)構(gòu),我們不僅會對特征進行上采樣實現(xiàn)特征融合,還會對特征再次進行下采樣實現(xiàn)特征融合。
YoloHead是YoloV5的分類器與回歸器,通過CSPDarknet和FPN,我們已經(jīng)可以獲得三個加強過的有效特征層。每一個特征層都有寬、高和通道數(shù),此時我們可以將特征圖看作一個又一個特征點的集合,每一個特征點都有通道數(shù)個特征。YoloHead實際上所做的工作就是對特征點進行判斷,判斷特征點是否有物體與其對應(yīng)。與以前版本的Yolo一樣,YoloV5所用的解耦頭是一起的,也就是分類和回歸在一個1X1卷積里實現(xiàn)。
因此,整個YoloV5網(wǎng)絡(luò)所作的工作就是特征提取-特征加強-預(yù)測特征點對應(yīng)的物體情況。
二、網(wǎng)絡(luò)結(jié)構(gòu)解析
1、主干網(wǎng)絡(luò)Backbone介紹
YoloV5所使用的主干特征提取網(wǎng)絡(luò)為CSPDarknet,它具有五個重要特點:
1、使用了殘差網(wǎng)絡(luò)Residual,CSPDarknet中的殘差卷積可以分為兩個部分,主干部分是一次1X1的卷積和一次3X3的卷積;殘差邊部分不做任何處理,直接將主干的輸入與輸出結(jié)合。
整個YoloV5的主干部分都由殘差卷積構(gòu)成:
classBottleneck(nn.Module):
#Standardbottleneck
def__init__(self,c1,c2,shortcut=True,g=1,e=0.5):#ch_in,ch_out,shortcut,groups,expansion
super(Bottleneck,self).__init__()
c_=int(c2*e)#hiddenchannels
self.cv1=Conv(c1,c_,1,1)
self.cv2=Conv(c_,c2,3,1,g=g)
self.add=shortcutandc1==c2
defforward(self,x):
returnx+self.cv2(self.cv1(x))ifself.addelseself.cv2(self.cv1(x))
殘差網(wǎng)絡(luò)的特點是容易優(yōu)化,并且能夠通過增加相當(dāng)?shù)纳疃葋硖岣邷?zhǔn)確率。其內(nèi)部的殘差塊使用了跳躍連接,緩解了在深度神經(jīng)網(wǎng)絡(luò)中增加深度帶來的梯度消失問題。
2、使用CSPnet網(wǎng)絡(luò)結(jié)構(gòu),CSPnet結(jié)構(gòu)并不算復(fù)雜,就是將原來的殘差塊的堆疊進行了一個拆分,拆成左右兩部分:
主干部分繼續(xù)進行原來的殘差塊的堆疊;
另一部分則像一個殘差邊一樣,經(jīng)過少量處理直接連接到最后。
因此可以認(rèn)為CSP中存在一個大的殘差邊。
classC3(nn.Module):
#CSPBottleneckwith3convolutions
def__init__(self,c1,c2,n=1,shortcut=True,g=1,e=0.5):#ch_in,ch_out,number,shortcut,groups,expansion
super(C3,self).__init__()
c_=int(c2*e)#hiddenchannels
self.cv1=Conv(c1,c_,1,1)
self.cv2=Conv(c1,c_,1,1)
self.cv3=Conv(2*c_,c2,1)#act=FReLU(c2)
self.m=nn.Sequential(*[Bottleneck(c_,c_,shortcut,g,e=1.0)for_inrange(n)])
#self.m=nn.Sequential(*[CrossConv(c_,c_,3,1,g,1.0,shortcut)for_inrange(n)])
defforward(self,x):
returnself.cv3(torch.cat((self.m(self.cv1(x)),self.cv2(x)),dim=1))
3、使用了Focus網(wǎng)絡(luò)結(jié)構(gòu),這個網(wǎng)絡(luò)結(jié)構(gòu)是在YoloV5里面使用到比較有趣的網(wǎng)絡(luò)結(jié)構(gòu),具體操作是在一張圖片中每隔一個像素拿到一個值,這個時候獲得了四個獨立的特征層,然后將四個獨立的特征層進行堆疊,此時寬高信息就集中到了通道信息,輸入通道擴充了四倍。拼接起來的特征層相對于原先的三通道變成了十二個通道,下圖很好的展示了Focus結(jié)構(gòu),一看就能明白。
classFocus(nn.Module):
def__init__(self,c1,c2,k=1,s=1,p=None,g=1,act=True):#ch_in,ch_out,kernel,stride,padding,groups
super(Focus,self).__init__()
self.conv=Conv(c1*4,c2,k,s,p,g,act)
defforward(self,x):
returnself.conv(torch.cat([x[...,::2,::2],x[...,1::2,::2],x[...,::2,1::2],x[...,1::2,1::2]],1))
4、使用了SiLU激活函數(shù),SiLU是Sigmoid和ReLU的改進版。SiLU具備無上界有下界、平滑、非單調(diào)的特性。SiLU在深層模型上的效果優(yōu)于ReLU。可以看做是平滑的ReLU激活函數(shù)。
classSiLU(nn.Module):
@staticmethod
defforward(x):
returnx*torch.sigmoid(x)
5、使用了SPP結(jié)構(gòu),通過不同池化核大小的最大池化進行特征提取,提高網(wǎng)絡(luò)的感受野。在YoloV4中,SPP是用在FPN里面的,在YoloV5中,SPP模塊被用在了主干特征提取網(wǎng)絡(luò)中。
classSPP(nn.Module):
#SpatialpyramidpoolinglayerusedinYOLOv3-SPP
def__init__(self,c1,c2,k=(5,9,13)):
super(SPP,self).__init__()
c_=c1//2#hiddenchannels
self.cv1=Conv(c1,c_,1,1)
self.cv2=Conv(c_*(len(k)+1),c2,1,1)
self.m=nn.ModuleList([nn.MaxPool2d(kernel_size=x,stride=1,padding=x//2)forxink])
defforward(self,x):
x=self.cv1(x)
returnself.cv2(torch.cat([x]+[m(x)forminself.m],1))
整個主干實現(xiàn)代碼為:
importtorch
importtorch.nnasnn
classSiLU(nn.Module):
@staticmethod
defforward(x):
returnx*torch.sigmoid(x)
defautopad(k,p=None):
ifpisNone:
p=k//2ifisinstance(k,int)else[x//2forxink]
returnp
classFocus(nn.Module):
def__init__(self,c1,c2,k=1,s=1,p=None,g=1,act=True):#ch_in,ch_out,kernel,stride,padding,groups
super(Focus,self).__init__()
self.conv=Conv(c1*4,c2,k,s,p,g,act)
defforward(self,x):
returnself.conv(torch.cat([x[...,::2,::2],x[...,1::2,::2],x[...,::2,1::2],x[...,1::2,1::2]],1))
classConv(nn.Module):
def__init__(self,c1,c2,k=1,s=1,p=None,g=1,act=True):
super(Conv,self).__init__()
self.conv=nn.Conv2d(c1,c2,k,s,autopad(k,p),groups=g,bias=False)
self.bn=nn.BatchNorm2d(c2,eps=0.001,momentum=0.03)
self.act=SiLU()ifactisTrueelse(actifisinstance(act,nn.Module)elsenn.Identity())
defforward(self,x):
returnself.act(self.bn(self.conv(x)))
deffuseforward(self,x):
returnself.act(self.conv(x))
classBottleneck(nn.Module):
#Standardbottleneck
def__init__(self,c1,c2,shortcut=True,g=1,e=0.5):#ch_in,ch_out,shortcut,groups,expansion
super(Bottleneck,self).__init__()
c_=int(c2*e)#hiddenchannels
self.cv1=Conv(c1,c_,1,1)
self.cv2=Conv(c_,c2,3,1,g=g)
self.add=shortcutandc1==c2
defforward(self,x):
returnx+self.cv2(self.cv1(x))ifself.addelseself.cv2(self.cv1(x))
classC3(nn.Module):
#CSPBottleneckwith3convolutions
def__init__(self,c1,c2,n=1,shortcut=True,g=1,e=0.5):#ch_in,ch_out,number,shortcut,groups,expansion
super(C3,self).__init__()
c_=int(c2*e)#hiddenchannels
self.cv1=Conv(c1,c_,1,1)
self.cv2=Conv(c1,c_,1,1)
self.cv3=Conv(2*c_,c2,1)#act=FReLU(c2)
self.m=nn.Sequential(*[Bottleneck(c_,c_,shortcut,g,e=1.0)for_inrange(n)])
#self.m=nn.Sequential(*[CrossConv(c_,c_,3,1,g,1.0,shortcut)for_inrange(n)])
defforward(self,x):
returnself.cv3(torch.cat((self.m(self.cv1(x)),self.cv2(x)),dim=1))
classSPP(nn.Module):
#SpatialpyramidpoolinglayerusedinYOLOv3-SPP
def__init__(self,c1,c2,k=(5,9,13)):
super(SPP,self).__init__()
c_=c1//2#hiddenchannels
self.cv1=Conv(c1,c_,1,1)
self.cv2=Conv(c_*(len(k)+1),c2,1,1)
self.m=nn.ModuleList([nn.MaxPool2d(kernel_size=x,stride=1,padding=x//2)forxink])
defforward(self,x):
x=self.cv1(x)
returnself.cv2(torch.cat([x]+[m(x)forminself.m],1))
classCSPDarknet(nn.Module):
def__init__(self,base_channels,base_depth):
super().__init__()
#-----------------------------------------------#
#輸入圖片是640,640,3
#初始的基本通道是64
#-----------------------------------------------#
#-----------------------------------------------#
#利用focus網(wǎng)絡(luò)結(jié)構(gòu)進行特征提取
#640,640,3-320,320,12-320,320,64
#-----------------------------------------------#
self.stem=Focus(3,base_channels,k=3)
#-----------------------------------------------#
#完成卷積之后,320,320,64-160,160,128
#完成CSPlayer之后,160,160,128-160,160,128
#-----------------------------------------------#
self.dark2=nn.Sequential(
Conv(base_channels,base_channels*2,3,2),
C3(base_channels*2,base_channels*2,base_depth),
#-----------------------------------------------#
#完成卷積之后,160,160,128-80,80,256
#完成CSPlayer之后,80,80,256-80,80,256
#-----------------------------------------------#
self.dark3=nn.Sequential(
Conv(base_channels*2,base_channels*4,3,2),
C3(base_channels*4,base_channels*4,base_depth*3),
#-----------------------------------------------#
#完成卷積之后,80,80,256-40,40,512
#完成CSPlayer之后,40,40,512-40,40,512
#-----------------------------------------------#
self.dark4=nn.Sequential(
Conv(base_channels*4,base_channels*8,3,2),
C3(base_channels*8,base_channels*8,base_depth*3),
#-----------------------------------------------#
#完成卷積之后,40,40,512-20,20,1024
#完成SPP之后,20,20,1024-20,20,1024
#完成CSPlayer之后,20,20,1024-20,20,1024
#-----------------------------------------------#
self.dark5=nn.Sequential(
Conv(base_channels*8,base_channels*16,3,2),
SPP(base_channels*16,base_channels*16),
C3(base_channels*16,base_channels*16,base_depth,shortcut=False),
defforward(self,x):
x=self.stem(x)
x=self.dark2(x)
#-----------------------------------------------#
#dark3的輸出為80,80,256,是一個有效特征層
#-----------------------------------------------#
x=self.dark3(x)
feat1=x
#-----------------------------------------------#
#dark4的輸出為40,40,512,是一個有效特征層
#-----------------------------------------------#
x=self.dark4(x)
feat2=x
#-----------------------------------------------#
#dark5的輸出為20,20,1024,是一個有效特征層
#-----------------------------------------------#
x=self.dark5(x)
feat3=x
returnfeat1,feat2,feat3
2、構(gòu)建FPN特征金字塔進行加強特征提取
在特征利用部分,YoloV5提取多特征層進行目標(biāo)檢測,一共提取三個特征層。
三個特征層位于主干部分CSPdarknet的不同位置,分別位于中間層,中下層,底層,當(dāng)輸入為(640,640,3)的時候,三個特征層的shape分別為feat1=(80,80,256)、feat2=(40,40,512)、feat3=(20,20,1024)。
在獲得三個有效特征層后,我們利用這三個有效特征層進行FPN層的構(gòu)建,構(gòu)建方式為:
feat3=(20,20,1024)的特征層進行1次1X1卷積調(diào)整通道后獲得P5,P5進行上采樣UmSampling2d后與feat2=(40,40,512)特征層進行結(jié)合,然后使用CSPLayer進行特征提取獲得P5_upsample,此時獲得的特征層為(40,40,512)。P5_upsample=(40,40,512)的特征層進行1次1X1卷積調(diào)整通道后獲得P4,P4進行上采樣UmSampling2d后與feat1=(80,80,256)特征層進行結(jié)合,然后使用CSPLayer進行特征提取P3_out,此時獲得的特征層為(80,80,256)。P3_out=(80,80,256)的特征層進行一次3x3卷積進行下采樣,下采樣后與P4堆疊,然后使用CSPLayer進行特征提取P4_out,此時獲得的特征層為(40,40,512)。P4_out=(40,40,512)的特征層進行一次3x3卷積進行下采樣,下采樣后與P5堆疊,然后使用CSPLayer進行特征提取P5_out,此時獲得的特征層為(20,20,1024)。
特征金字塔可以將不同shape的特征層進行特征融合,有利于提取出更好的特征。
importtorch
importtorch.nnasnn
fromnets.CSPdarknetimportCSPDarknet,C3,Conv
#---------------------------------------------------#
#yolo_body
#---------------------------------------------------#
classYoloBody(nn.Module):
def__init__(self,anchors_mask,num_classes,phi):
super(YoloBody,self).__init__()
depth_dict={'s':0.33,'m':0.67,'l':1.00,'x':1.33,}
width_dict={'s':0.50,'m':0.75,'l':1.00,'x':1.25,}
dep_mul,wid_mul=depth_dict[phi],width_dict[phi]
base_channels=int(wid_mul*64)#64
base_depth=max(round(dep_mul*3),1)#3
#-----------------------------------------------#
#輸入圖片是640,640,3
#初始的基本通道是64
#-----------------------------------------------#
#---------------------------------------------------#
#生成CSPdarknet53的主干模型
#獲得三個有效特征層,他們的shape分別是:
#80,80,256
#40,40,512
#20,20,1024
#---------------------------------------------------#
self.backbone=CSPDarknet(base_channels,base_depth)
self.upsample=nn.Upsample(scale_factor=2,mode="nearest")
self.conv_for_feat3=Conv(base_channels*16,base_channels*8,1,1)
self.conv3_for_upsample1=C3(base_channels*16,base_channels*8,base_depth,shortcut=False)
self.conv_for_feat2=Conv(base_channels*8,base_channels*4,1,1)
self.conv3_for_upsample2=C3(base_channels*8,base_channels*4,base_depth,shortcut=False)
self.down_sample1=Conv(base_channels*4,base_channels*4,3,2)
self.conv3_for_downsample1=C3(base_channels*8,base_channels*8,base_depth,shortcut=False)
self.down_sample2=Conv(base_channels*8,base_channels*8,3,2)
self.conv3_for_downsample2=C3(base_channels*16,base_channels*16,base_depth,shortcut=False)
self.yolo_head_P3=nn.Conv2d(base_channels*4,len(anchors_mask[2])*(5+num_classes),1)
self.yolo_head_P4=nn.Conv2d(base_channels*8,len(anchors_mask[1])*(5+num_classes),1)
self.yolo_head_P5=nn.Conv2d(base_channels*16,len(anchors_mask[0])*(5+num_classes),1)
defforward(self,x):
#backbone
feat1,feat2,feat3=self.backbone(x)
P5=self.conv_for_feat3(feat3)
P5_upsample=self.upsample(P5)
P4=torch.cat([P5_upsample,feat2],1)
P4=self.conv3_for_upsample1(P4)
P4=self.conv_for_feat2(P4)
P4_upsample=self.upsample(P4)
P3=torch.cat([P4_upsample,feat1],1)
P3=self.conv3_for_upsample2(P3)
P3_downsample=self.down_sample1(P3)
P4=torch.cat([P3_downsample,P4],1)
P4=self.conv3_for_downsample1(P4)
P4_downsample=self.down_sample2(P4)
P5=torch.cat([P4_downsample,P5],1)
P5=self.conv3_for_downsample2(P5)
#---------------------------------------------------#
#第三個特征層
#y3=(batch_size,75,80,80)
#---------------------------------------------------#
out2=self.yolo_head_P3(P3)
#---------------------------------------------------#
#第二個特征層
#y2=(batch_size,75,40,40)
#---------------------------------------------------#
out1=self.yolo_head_P4(P4)
#---------------------------------------------------#
#第一個特征層
#y1=(batch_size,75,20,20)
#---------------------------------------------------#
out0=self.yolo_head_P5(P5)
returnout0,out1,out2
3、利用YoloHead獲得預(yù)測結(jié)果
利用FPN特征金字塔,我們可以獲得三個加強特征,這三個加強特征的shape分別為(20,20,1024)、(40,40,512)、(80,80,256),然后我們利用這三個shape的特征層傳入YoloHead獲得預(yù)測結(jié)果。
對于每一個特征層,我們可以獲得利用一個卷積調(diào)整通道數(shù),最終的通道數(shù)和需要區(qū)分的種類個數(shù)相關(guān),在YoloV5里,每一個特征層上每一個特征點存在3個先驗框。
如果使用的是voc訓(xùn)練集,類則為20種,最后的維度應(yīng)該為75=3x25,三個特征層的shape為(20,20,75),(40,40,75),(80,80,75)。
最后的75可以拆分成3個25,對應(yīng)3個先驗框的25個參數(shù),25可以拆分成4+1+20。前4個參數(shù)用于判斷每一個特征點的回歸參數(shù),回歸參數(shù)調(diào)整后可以獲得預(yù)測框;第5個參數(shù)用于判斷每一個特征點是否包含物體;最后20個參數(shù)用于判斷每一個特征點所包含的物體種類。
如果使用的是coco訓(xùn)練集,類則為80種,最后的維度應(yīng)該為255=3x85,三個特征層的shape為(20,20,255),(40,40,255),(80,80,255)
最后的255可以拆分成3個85,對應(yīng)3個先驗框的85個參數(shù),85可以拆分成4+1+80。前4個參數(shù)用于判斷每一個特征點的回歸參數(shù),回歸參數(shù)調(diào)整后可以獲得預(yù)測框;第5個參數(shù)用于判斷每一個特征點是否包含物體;最后80個參數(shù)用于判斷每一個特征點所包含的物體種類。
實現(xiàn)代碼如下:
importtorch
importtorch.nnasnn
fromnets.CSPdarknetimportCSPDarknet,C3,Conv
#---------------------------------------------------#
#yolo_body
#---------------------------------------------------#
classYoloBody(nn.Module):
def__init__(self,anchors_mask,num_classes,phi):
super(YoloBody,self).__init__()
depth_dict={'s':0.33,'m':0.67,'l':1.00,'x':1.33,}
width_dict={'s':0.50,'m':0.75,'l':1.00,'x':1.25,}
dep_mul,wid_mul=depth_dict[phi],width_dict[phi]
base_channels=int(wid_mul*64)#64
base_depth=max(round(dep_mul*3),1)#3
#-----------------------------------------------#
#輸入圖片是640,640,3
#初始的基本通道是64
#-----------------------------------------------#
#---------------------------------------------------#
#生成CSPdarknet53的主干模型
#獲得三個有效特征層,他們的shape分別是:
#80,80,256
#40,40,512
#20,20,1024
#---------------------------------------------------#
self.backbone=CSPDarknet(base_channels,base_depth)
self.upsample=nn.Upsample(scale_factor=2,mode="nearest")
self.conv_for_feat3=Conv(base_channels*16,base_channels*8,1,1)
self.conv3_for_upsample1=C3(base_channels*16,base_channels*8,base_depth,shortcut=False)
self.conv_for_feat2=Conv(base_channels*8,base_channels*4,1,1)
self.conv3_for_upsample2=C3(base_channels*8,base_channels*4,base_depth,shortcut=False)
self.down_sample1=Conv(base_channels*4,base_channels*4,3,2)
self.conv3_for_downsample1=C3(base_channels*8,base_channels*8,base_depth,shortcut=False)
self.down_sample2=Conv(base_channels*8,base_channels*8,3,2)
self.conv3_for_downsample2=C3(base_channels*16,base_channels*16,base_depth,shortcut=False)
self.yolo_head_P3=nn.Conv2d(base_channels*4,len(anchors_mask[2])*(5+num_classes),1)
self.yolo_head_P4=nn.Conv2d(base_channels*8,len(anchors_mask[1])*(5+num_classes),1)
self.yolo_head_P5=nn.Conv2d(base_channels*16,len(anchors_mask[0])*(5+num_classes),1)
defforward(self,x):
#backbone
feat1,feat2,feat3=self.backbone(x)
P5=self.conv_for_feat3(feat3)
P5_upsample=self.upsample(P5)
P4=torch.cat([P5_upsample,feat2],1)
P4=self.conv3_for_upsample1(P4)
P4=self.conv_for_feat2(P4)
P4_upsample=self.upsample(P4)
P3=torch.cat([P4_upsample,feat1],1)
P3=self.conv3_for_upsample2(P3)
P3_downsample=self.down_sample1(P3)
P4=torch.cat([P3_downsample,P4],1)
P4=self.conv3_for_downsample1(P4)
P4_downsample=self.down_sample2(P4)
P5=torch.cat([P4_downsample,P5],1)
P5=self.conv3_for_downsample2(P5)
#---------------------------------------------------#
#第三個特征層
#y3=(batch_size,75,80,80)
#---------------------------------------------------#
out2=self.yolo_head_P3(P3)
#---------------------------------------------------#
#第二個特征層
#y2=(batch_size,75,40,40)
#---------------------------------------------------#
out1=self.yolo_head_P4(P4)
#---------------------------------------------------#
#第一個特征層
#y1=(batch_size,75,20,20)
#---------------------------------------------------#
out0=self.yolo_head_P5(P5)
returnout0,out1,out2
三、預(yù)測結(jié)果的解碼
1、獲得預(yù)測框與得分
由第二步我們可以獲得三個特征層的預(yù)測結(jié)果,shape分別為(N,20,20,255),(N,40,40,255),(N,80,80,255)的數(shù)據(jù)。
但是這個預(yù)測結(jié)果并不對應(yīng)著最終的預(yù)測框在圖片上的位置,還需要解碼才可以完成。在YoloV5里,每一個特征層上每一個特征點存在3個先驗框。
每個特征層最后的255可以拆分成3個85,對應(yīng)3個先驗框的85個參數(shù),我們先將其reshape一下,其結(jié)果為(N,20,20,3,85),(N,40.40,3,85),(N,80,80,3,85)。
其中的85可以拆分成4+1+80。
前4個參數(shù)用于判斷每一個特征點的回歸參數(shù),回歸參數(shù)調(diào)整后可以獲得預(yù)測框;第5個參數(shù)用于判斷每一個特征點是否包含物體;最后80個參數(shù)用于判斷每一個特征點所包含的物體種類。
以(N,20,20,3,85)這個特征層為例,該特征層相當(dāng)于將圖像劃分成20x20個特征點,如果某個特征點落在物體的對應(yīng)框內(nèi),就用于預(yù)測該物體。
如圖所示,藍(lán)色的點為20x20的特征點,此時我們對左圖黑色點的三個先驗框進行解碼操作演示:
1、進行中心預(yù)測點的計算,利用Regression預(yù)測結(jié)果前兩個序號的內(nèi)容對特征點的三個先驗框中心坐標(biāo)進行偏移,偏移后是右圖紅色的三個點;
2、進行預(yù)測框?qū)捀叩挠嬎?,利用Regression預(yù)測結(jié)果后兩個序號的內(nèi)容求指數(shù)后獲得預(yù)測框的寬高;
3、此時獲得的預(yù)測框就可以繪制在圖片上了。
除去這樣的解碼操作,還有非極大抑制的操作需要進行,防止同一種類的框的堆積。
defdecode_box(self,inputs):
outputs=[]
fori,inputinenumerate(inputs):
#-----------------------------------------------#
#輸入的input一共有三個,他們的shape分別是
#batch_size,255,20,20
#batch_size,255,40,40
#batch_size,255,80,80
#-----------------------------------------------#
batch_size=input.size(0)
input_height=input.size(2)
input_width=input.size(3)
#-----------------------------------------------#
#輸入為416x416時
#stride_h=stride_w=32、16、8
#-----------------------------------------------#
stride_h=self.input_shape[0]/input_height
stride_w=self.input_shape[1]/input_width
#-------------------------------------------------#
#此時獲得的scaled_anchors大小是相對于特征層的
#-------------------------------------------------#
scaled_anchors=[(anchor_width/stride_w,anchor_height/stride_h)foranchor_width,anchor_heightinself.anchors[self.anchors_mask[i]]]
#-----------------------------------------------#
#輸入的input一共有三個,他們的shape分別是
#batch_size,3,20,20,85
#batch_size,3,40,40,85
#batch_size,3,80,80,85
#-----------------------------------------------#
prediction=input.view(batch_size,len(self.anchors_mask[i]),
self.bbox_attrs,input_height,input_width).permute(0,1,3,4,2).contiguous()
#-----------------------------------------------#
#先驗框的中心位置的調(diào)整參數(shù)
#-----------------------------------------------#
x=torch.sigmoid(prediction[...,0])
y=torch.sigmoid(prediction[...,1])
#-----------------------------------------------#
#先驗框的寬高調(diào)整參數(shù)
#-----------------------------------------------#
w=torch.sigmoid(prediction[...,2])
h=torch.sigmoid(prediction[...,3])
#-----------------------------------------------#
#獲得置信度,是否有物體
#-----------------------------------------------#
conf=torch.sigmoid(prediction[...,4])
#-----------------------------------------------#
#種類置信度
#-----------------------------------------------#
pred_cls=torch.sigmoid(prediction[...,5:])
FloatTensor=torch.cuda.FloatTensorifx.is_cudaelsetorch.FloatTensor
LongTensor=torch.cuda.LongTensorifx.is_cudaelsetorch.LongTensor
#----------------------------------------------------------#
#生成網(wǎng)格,先驗框中心,網(wǎng)格左上角
#batch_size,3,20,20
#----------------------------------------------------------#
grid_x=torch.linspace(0,input_width-1,input_width).repeat(input_height,1).repeat(
batch_size*len(self.anchors_mask[i]),1,1).view(x.shape).type(FloatTensor)
grid_y=torch.linspace(0,input_height-1,input_height).repeat(input_width,1).t().repeat(
batch_size*len(self.anchors_mask[i]),1,1).view(y.shape).type(FloatTensor)
#----------------------------------------------------------#
#按照網(wǎng)格格式生成先驗框的寬高
#batch_size,3,20,20
#----------------------------------------------------------#
anchor_w=FloatTensor(scaled_anchors).index_select(1,LongTensor([0]))
anchor_h=FloatTensor(scaled_anchors).index_select(1,LongTensor([1]))
anchor_w=anchor_w.repeat(batch_size,1).repeat(1,1,input_height*input_width).view(w.shape)
anchor_h=anchor_h.repeat(batch_size,1).repeat(1,1,input_height*input_width).view(h.shape)
#----------------------------------------------------------#
#利用預(yù)測結(jié)果對先驗框進行調(diào)整
#首先調(diào)整先驗框的中心,從先驗框中心向右下角偏移
#再調(diào)整先驗框的寬高。
#----------------------------------------------------------#
pred_boxes=FloatTensor(prediction[...,:4].shape)
pred_boxes[...,0]=x.data*2.-0.5+grid_x
pred_boxes[...,1]=y.data*2.-0.5+grid_y
pred_boxes[...,2]=(w.data*2)**2*anchor_w
pred_boxes[...,3]=(h.data*2)**2*anchor_h
#----------------------------------------------------------#
#將輸出結(jié)果歸一化成小數(shù)的形式
#----------------------------------------------------------#
_scale=torch.Tensor([input_width,input_height,input_width,input_height]).type(FloatTensor)
output=torch.cat((pred_boxes.view(batch_size,-1,4)/_scale,
conf.view(batch_size,-1,1),pred_cls.view(batch_size,-1,self.num_classes)),-1)
outputs.append(output.data)
returnoutputs
2、得分篩選與非極大抑制
得到最終的預(yù)測結(jié)果后還要進行得分排序與非極大抑制篩選。
得分篩選就是篩選出得分滿足confidence置信度的預(yù)測框。
非極大抑制就是篩選出一定區(qū)域內(nèi)屬于同一種類得分最大的框。
得分篩選與非極大抑制的過程可以概括如下:
1、找出該圖片中得分大于門限函數(shù)的框。在進行重合框篩選前就進行得分的篩選可以大幅度減少框的數(shù)量。
2、對種類進行循環(huán),非極大抑制的作用是篩選出一定區(qū)域內(nèi)屬于同一種類得分最大的框,對種類進行循環(huán)可以幫助我們對每一個類分別進行非極大抑制。
3、根據(jù)得分對該種類進行從大到小排序。
4、每次取出得分最大的框,計算其與其它所有預(yù)測框的重合程度,重合程度過大的則剔除。
得分篩選與非極大抑制后的結(jié)果就可以用于繪制預(yù)測框了。
下圖是經(jīng)過非極大抑制的。
下圖是未經(jīng)過非極大抑制的。
實現(xiàn)代碼為:
defnon_max_suppression(self,prediction,num_classes,input_shape,image_shape,letterbox_image,conf_thres=0.5,nms_thres=0.4):
#----------------------------------------------------------#
#將預(yù)測結(jié)果的格式轉(zhuǎn)換成左上角右下角的格式。
#prediction[batch_size,num_anchors,85]
#----------------------------------------------------------#
box_corner=prediction.new(prediction.shape)
box_corner[:,:,0]=prediction[:,:,0]-prediction[:,:,2]/2
box_corner[:,:,1]=prediction[:,:,1]-prediction[:,:,3]/2
box_corner[:,:,2]=prediction[:,:,0]+prediction[:,:,2]/2
box_corner[:,:,3]=prediction[:,:,1]+prediction[:,:,3]/2
prediction[:,:,:4]=box_corner[:,:,:4]
output=[Nonefor_inrange(len(prediction))]
fori,image_predinenumerate(prediction):
#----------------------------------------------------------#
#對種類預(yù)測部分取max。
#class_conf[num_anchors,1]種類置信度
#class_pred[num_anchors,1]種類
#----------------------------------------------------------#
class_conf,class_pred=torch.max(image_pred[:,5:5+num_classes],1,keepdim=True)
#----------------------------------------------------------#
#利用置信度進行第一輪篩選
#----------------------------------------------------------#
conf_mask=(image_pred[:,4]*class_conf[:,0]=conf_thres).squeeze()
#----------------------------------------------------------#
#根據(jù)置信度進行預(yù)測結(jié)果的篩選
#----------------------------------------------------------#
image_pred=image_pred[conf_mask]
class_conf=class_conf[conf_mask]
class_pred=class_pred[conf_mask]
ifnotimage_pred.size(0):
continue
#-------------------------------------------------------------------------#
#detections[num_anchors,7]
#7的內(nèi)容為:x1,y1,x2,y2,obj_conf,class_conf,class_pred
#-------------------------------------------------------------------------#
detections=torch.cat((image_pred[:,:5],class_conf.float(),class_pred.float()),1)
#------------------------------------------#
#獲得預(yù)測結(jié)果中包含的所有種類
#------------------------------------------#
unique_labels=detections[:,-1].cpu().unique()
ifprediction.is_cuda:
unique_labels=unique_labels.cuda()
detections=detections.cuda()
forcinunique_labels:
#------------------------------------------#
#獲得某一類得分篩選后全部的預(yù)測結(jié)果
#------------------------------------------#
detections_class=detections[detections[:,-1]==c]
#------------------------------------------#
#使用官方自帶的非極大抑制會速度更快一些!
#------------------------------------------#
keep=nms(
detections_class[:,:4],
detections_class[:,4]*detections_class[:,5],
nms_thres
max_detections=detections_class[keep]
##按照存在物體的置信度排序
#_,conf_sort_index=torch.sort(detections_class[:,4]*detections_class[:,5],descending=True)
#detections_class=detections_class[conf_sort_index]
##進行非極大抑制
#max_detections=[]
#whiledetections_class.size(0):
##取出這一類置信度最高的,一步一步往下判斷,判斷重合程度是否大于nms_thres,如果是則去除掉
#max_detections.append(detections_class[0].unsqueeze(0))
#iflen(detections_class)==1:
#break
#ious=bbox_iou(max_detections[-1],detections_class[1:])
#detections_class=detections_class[1:][iousnms_thres]
##堆疊
#max_detections=torch.cat(max_detections).data
#Addmaxdetectionstooutputs
output[i]=max_detectionsifoutput[i]isNoneelsetorch.cat((output[i],max_detections))
ifoutput[i]isnotNone:
output[i]=output[i].cpu().numpy()
box_xy,box_wh=(output[i][:,0:2]+output[i][:,2:4])/2,output[i][:,2:4]-output[i][:,0:2]
output[i][:,:4]=self.yolo_correct_boxes(box_xy,box_wh,input_shape,image_shape,letterbox_image)
returnoutput
四、訓(xùn)練部分
1、計算loss所需內(nèi)容
計算loss實際上是網(wǎng)絡(luò)的預(yù)測結(jié)果和網(wǎng)絡(luò)的真實結(jié)果的對比。
和網(wǎng)絡(luò)的預(yù)測結(jié)果一樣,網(wǎng)絡(luò)的損失也由三個部分組成,分別是Reg部分、Obj部分、Cls部分。Reg部分是特征點的回歸參數(shù)判斷、Obj部分是特征點是否包含物體判斷、Cls部分是特征點包含的物體的種類。
2、正樣本的匹配過程
在YoloV5中,訓(xùn)練時正樣本的匹配過程可以分為兩部分。
a、匹配先驗框。
b、匹配特征點。
所謂正樣本匹配,就是尋找哪些先驗框被認(rèn)為有對應(yīng)的真實框,并且負(fù)責(zé)這個真實框的預(yù)測。
a、匹配先驗框
在YoloV5網(wǎng)絡(luò)中,一共設(shè)計了9個不同大小的先驗框。每個輸出的特征層對應(yīng)3個先驗框。
對于任何一個真實框gt,YoloV5不再使用iou進行正樣本的匹配,而是直接采用高寬比進行匹
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 木里木外培訓(xùn)
- 消防安全信息發(fā)布方案
- 心理健康教育知識講座
- 施工過程監(jiān)控與驗收方案
- 橋梁施工現(xiàn)場應(yīng)急預(yù)案方案
- 施工地域環(huán)境影響評估方案
- 建筑周邊環(huán)境保護方案
- 供電系統(tǒng)安裝驗收技術(shù)方案
- 農(nóng)田排水系統(tǒng)優(yōu)化設(shè)計方案
- 水電安裝工程驗收方案
- 2026年及未來5年市場數(shù)據(jù)中國集裝箱物流行業(yè)市場發(fā)展數(shù)據(jù)監(jiān)測及投資戰(zhàn)略規(guī)劃報告
- 中小學(xué)人工智能教育三年發(fā)展規(guī)劃(2026-2028)7500字完整方案目標(biāo)務(wù)實真能落地
- 七年級地理下冊(人教版)東半球其他的國家和地區(qū)-歐洲西部自然環(huán)境教學(xué)設(shè)計
- 口腔現(xiàn)場義診培訓(xùn)
- 學(xué)校中層管理崗位職責(zé)及分工明細(xì)(2026年版)
- 江蘇省南京市六校聯(lián)合體2026屆高一數(shù)學(xué)第一學(xué)期期末監(jiān)測試題含解析
- 食堂設(shè)備使用及保養(yǎng)培訓(xùn)
- 村莊異地搬遷安置點項目可行性研究報告
- 《正常人體形態(tài)學(xué)》考試復(fù)習(xí)題庫大全(含答案)
- 抗洪搶險先進事跡2023
- 鋁材廠煲模作業(yè)指導(dǎo)書
評論
0/150
提交評論