從代碼的角度理解YOLO V5的工作。YOLO V5的網絡結構圖如下:
1、與YOLO V4的區別
Yolov4在Yolov3的基礎上進行了很多的創新。比如輸入端采用mosaic數據增強,Backbone上采用了CSPDarknet53、Mish激活函數、Dropblock等方式,Neck中采用了SPP、FPN+PAN的結構,輸出端則采用CIOU_Loss、DIOU_nms操作。因此Yolov4對Yolov3的各個部分都進行了很多的整合創新。這里給出YOLO V4的網絡結構圖:
Yolov5的結構其實和Yolov4的結構還是有一定的相似之處的,但也有一些不同,這里還是按照從整體到細節的方式,對每個板塊進行講解。這里給出YOLO V4的網絡結構圖:
通過Yolov5的網絡結構圖可以看到,依舊是把模型分為4個部分,分別是:輸入端、Backbone、Neck、Prediction。
1.1、輸入端的區別
1 Mosaic數據增強
Mosaic是參考CutMix數據增強的方式,但CutMix只使用了兩張圖片進行拼接,而Mosaic數據增強則采用了4張圖片,隨機縮放、隨機裁剪、隨機排布的方式進行拼接。
主要有幾個優點:
1、豐富數據集:隨機使用4張圖片,隨機縮放,再隨機分布進行拼接,大大豐富了檢測數據集,特別是隨機縮放增加了很多小目標,讓網絡的魯棒性更好。
2、減少GPU:可能會有人說,隨機縮放,普通的數據增強也可以做,但作者考慮到很多人可能只有一個GPU,因此Mosaic增強訓練時,可以直接計算4張圖片的數據,使得Mini-batch大小并不需要很大,一個GPU就可以達到比較好的效果。
2 自適應錨框計算
在Yolov3、Yolov4中,訓練不同的數據集時,計算初始錨框的值是通過單獨的程序運行的。但Yolov5中將此功能嵌入到代碼中,每次訓練時,自適應的計算不同訓練集中的最佳錨框值。
比如Yolov5在Coco數據集上初始設定的錨框:
3 自適應圖片縮放
在常用的目標檢測算法中,不同的圖片長寬都不相同,因此常用的方式是將原始圖片統一縮放到一個標準尺寸,再送入檢測網絡中。比如Yolo算法中常用416×416,608×608等尺寸,比如對下面800×600的圖像進行變換。
但Yolov5代碼中對此進行了改進,也是Yolov5推理速度能夠很快的一個不錯的trick。作者認為,在項目實際使用時,很多圖片的長寬比不同。因此縮放填充后,兩端的黑邊大小都不同,而如果填充的比較多,則存在信息冗余,影響推理速度。
具體操作的步驟:
1 計算縮放比例
原始縮放尺寸是416×416,都除以原始圖像的尺寸后,可以得到0.52,和0.69兩個縮放系數,選擇小的縮放系數0.52。
2 計算縮放后的尺寸
原始圖片的長寬都乘以最小的縮放系數0.52,寬變成了416,而高變成了312。
3 計算黑邊填充數值
將416-312=104,得到原本需要填充的高度。再采用numpy中np.mod取余數的方式,得到40個像素,再除以2,即得到圖片高度兩端需要填充的數值。
1.2、Backbone的區別
1 Focus結構
Focus結構,在Yolov3&Yolov4中并沒有這個結構,其中比較關鍵是切片操作。比如右圖的切片示意圖,4×4×3的圖像切片后變成3×3×12的特征圖。以Yolov5s的結構為例,原始608×608×3的圖像輸入Focus結構,采用切片操作,先變成304×304×12的特征圖,再經過一次32個卷積核的卷積操作,最終變成304×304×32的特征圖。
需要注意的是:Yolov5s的Focus結構最后使用了32個卷積核,而其他三種結構,使用的數量有所增加,先注意下,后面會講解到四種結構的不同點。
class Focus(nn.Module):
# Focus wh information into c-space
def __init__(self, c1, c2, k=1):
super(Focus, self).__init__()
self.conv = Conv(c1 * 4, c2, k, 1)
def forward(self, x): # x(b,c,w,h) -》 y(b,4c,w/2,h/2)
return self.conv(torch.cat([x[。.., ::2, ::2], x[。.., 1::2, ::2], x[。.., ::2, 1::2], x[。.., 1::2, 1::2]], 1))
2 CSP結構
Yolov5與Yolov4不同點在于,Yolov4中只有主干網絡使用了CSP結構,而Yolov5中設計了兩種CSP結構,以Yolov5s網絡為例,以CSP1_X結構應用于Backbone主干網絡,另一種CSP2_X結構則應用于Neck中。
classConv(nn.Module): #Standardconvolution def__init__(self,c1,c2,k=1,s=1,g=1,act=True):#ch_in,ch_out,kernel,stride,groups super(Conv,self).__init__() self.conv=nn.Conv2d(c1,c2,k,s,k//2,groups=g,bias=False) self.bn=nn.BatchNorm2d(c2) self.act=nn.LeakyReLU(0.1,inplace=True)ifactelsenn.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)) classBottleneckCSP(nn.Module): #CSPBottleneckhttps://github.com/WongKinYiu/CrossStagePartialNetworks def__init__(self,c1,c2,n=1,shortcut=True,g=1,e=0.5):#ch_in,ch_out,number,shortcut,groups,expansion super(BottleneckCSP,self).__init__() c_=int(c2*e)#hiddenchannels self.cv1=Conv(c1,c_,1,1) self.cv2=nn.Conv2d(c1,c_,1,1,bias=False) self.cv3=nn.Conv2d(c_,c_,1,1,bias=False) self.cv4=Conv(c2,c2,1,1) self.bn=nn.BatchNorm2d(2*c_)#appliedtocat(cv2,cv3) self.act=nn.LeakyReLU(0.1,inplace=True) self.m=nn.Sequential(*[Bottleneck(c_,c_,shortcut,g,e=1.0)for_inrange(n)]) defforward(self,x): y1=self.cv3(self.m(self.cv1(x))) y2=self.cv2(x) returnself.cv4(self.act(self.bn(torch.cat((y1,y2),dim=1))))
1.3、Neck的區別
Yolov5現在的Neck和Yolov4中一樣,都采用FPN+PAN的結構,但在Yolov5剛出來時,只使用了FPN結構,后面才增加了PAN結構,此外網絡中其他部分也進行了調整。
Yolov5和Yolov4的不同點在于,Yolov4的Neck中,采用的都是普通的卷積操作。而Yolov5的Neck結構中,采用借鑒CSPNet設計的CSP2結構,加強網絡特征融合的能力。
1.4、輸出端的區別
1 Bounding box損失函數
而Yolov4中采用CIOU_Loss作為目標Bounding box的損失。而Yolov5中采用其中的GIOU_Loss做Bounding box的損失函數。
defcompute_loss(p,targets,model):#predictions,targets,model ft=torch.cuda.FloatTensorifp[0].is_cudaelsetorch.Tensor lcls,lbox,lobj=ft([0]),ft([0]),ft([0]) tcls,tbox,indices,anchors=build_targets(p,targets,model)#targets h=model.hyp#hyperparameters red='mean'#Lossreduction(sumormean) #Definecriteria BCEcls=nn.BCEWithLogitsLoss(pos_weight=ft([h['cls_pw']]),reduction=red) BCEobj=nn.BCEWithLogitsLoss(pos_weight=ft([h['obj_pw']]),reduction=red) #classlabelsmoothinghttps://arxiv.org/pdf/1902.04103.pdfeqn3 cp,cn=smooth_BCE(eps=0.0) #focalloss g=h['fl_gamma']#focallossgamma ifg>0: BCEcls,BCEobj=FocalLoss(BCEcls,g),FocalLoss(BCEobj,g) #peroutput nt=0#targets fori,piinenumerate(p):#layerindex,layerpredictions b,a,gj,gi=indices[i]#image,anchor,gridy,gridx tobj=torch.zeros_like(pi[...,0])#targetobj nb=b.shape[0]#numberoftargets ifnb: nt+=nb#cumulativetargets ps=pi[b,a,gj,gi]#predictionsubsetcorrespondingtotargets #GIoU pxy=ps[:,:2].sigmoid()*2.-0.5 pwh=(ps[:,2:4].sigmoid()*2)**2*anchors[i] pbox=torch.cat((pxy,pwh),1)#predictedbox giou=bbox_iou(pbox.t(),tbox[i],x1y1x2y2=False,GIoU=True)#giou(prediction,target) lbox+=(1.0-giou).sum()ifred=='sum'else(1.0-giou).mean()#giouloss #Obj tobj[b,a,gj,gi]=(1.0-model.gr)+model.gr*giou.detach().clamp(0).type(tobj.dtype)#giouratio #Class ifmodel.nc>1:#clsloss(onlyifmultipleclasses) t=torch.full_like(ps[:,5:],cn)#targets t[range(nb),tcls[i]]=cp lcls+=BCEcls(ps[:,5:],t)#BCE #Appendtargetstotextfile #withopen('targets.txt','a')asfile: #[file.write('%11.5g'*4%tuple(x)+' ')forxintorch.cat((txy[i],twh[i]),1)] lobj+=BCEobj(pi[...,4],tobj)#objloss lbox*=h['giou'] lobj*=h['obj'] lcls*=h['cls'] bs=tobj.shape[0]#batchsize ifred=='sum': g=3.0#lossgain lobj*=g/bs ifnt: lcls*=g/nt/model.nc lbox*=g/nt loss=lbox+lobj+lcls returnloss*bs,torch.cat((lbox,lobj,lcls,loss)).detach()
2 NMS非極大值抑制
Yolov4在DIOU_Loss的基礎上采用DIOU_NMS的方式,而Yolov5中采用加權NMS的方式??梢钥闯?,采用DIOU_NMS,下方中間箭頭的黃色部分,原本被遮擋的摩托車也可以檢出。
在同樣的參數情況下,將NMS中IOU修改成DIOU_NMS。對于一些遮擋重疊的目標,確實會有一些改進。
2、YOLOv5社交距離項目
yolov5檢測要檢測的視頻流中的所有人,然后再計算所有檢測到的人之間的相互“距離”,和現實生活中用“m”這樣的單位衡量距離不一樣的是,在計算機中,簡單的方法是用檢測到的兩個人的質心,也就是檢測到的目標框的中心之間相隔的像素值作為計算機中的“距離”來衡量視頻中的人之間的距離是否超過安全距離。
構建步驟:
使用目標檢測算法檢測視頻流中的所有人,得到位置信息和質心位置;
計算所有檢測到的人質心之間的相互距離;
設置安全距離,計算每個人之間的距離對,檢測兩個人之間的距離是否小于N個像素,小于則處于安全距離,反之則不處于。
項目架構:
detect.py代碼注釋如下:
importargparse fromutils.datasetsimport* fromutils.utilsimport* defdetect(save_img=False): out,source,weights,view_img,save_txt,imgsz= opt.output,opt.source,opt.weights,opt.view_img,opt.save_txt,opt.img_size webcam=source=='0'orsource.startswith('rtsp')orsource.startswith('http')orsource.endswith('.txt') #Initialize device=torch_utils.select_device(opt.device) ifos.path.exists(out): shutil.rmtree(out)#deleteoutputfolder os.makedirs(out)#makenewoutputfolder half=device.type!='cpu'#halfprecisiononlysupportedonCUDA #下載模型 google_utils.attempt_download(weights) #加載權重 model=torch.load(weights,map_location=device)['model'].float() #torch.save(torch.load(weights,map_location=device),weights)#updatemodelifSourceChangeWarning #model.fuse() #設置模型為推理模式 model.to(device).eval() ifhalf: model.half()#toFP16 #Second-stageclassifier classify=False ifclassify: modelc=torch_utils.load_classifier(name='resnet101',n=2)#initialize modelc.load_state_dict(torch.load('weights/resnet101.pt',map_location=device)['model'])#loadweights modelc.to(device).eval() #設置Dataloader vid_path,vid_writer=None,None ifwebcam: view_img=True torch.backends.cudnn.benchmark=True#setTruetospeedupconstantimagesizeinference dataset=LoadStreams(source,img_size=imgsz) else: save_img=True dataset=LoadImages(source,img_size=imgsz) #獲取檢測類別的標簽名稱 names=model.namesifhasattr(model,'names')elsemodel.modules.names #定義顏色 colors=[[random.randint(0,255)for_inrange(3)]for_inrange(len(names))] #開始推理 t0=time.time() #初始化一張全為0的圖片 img=torch.zeros((1,3,imgsz,imgsz),device=device) _=model(img.half()ifhalfelseimg)ifdevice.type!='cpu'elseNone forpath,img,im0s,vid_capindataset: img=torch.from_numpy(img).to(device) img=img.half()ifhalfelseimg.float()#uint8tofp16/32 img/=255.0#0-255to0.0-1.0 ifimg.ndimension()==3: img=img.unsqueeze(0) #預測結果 t1=torch_utils.time_synchronized() pred=model(img,augment=opt.augment)[0] #使用NMS pred=non_max_suppression(pred,opt.conf_thres,opt.iou_thres,fast=True,classes=opt.classes,agnostic=opt.agnostic_nms) t2=torch_utils.time_synchronized() #進行分類 ifclassify: pred=apply_classifier(pred,modelc,img,im0s) people_coords=[] #處理預測得到的檢測目標 fori,detinenumerate(pred): ifwebcam: p,s,im0=path[i],'%g:'%i,im0s[i].copy() else: p,s,im0=path,'',im0s save_path=str(Path(out)/Path(p).name) s+='%gx%g'%img.shape[2:]#printstring gn=torch.tensor(im0.shape)[[1,0,1,0]]#normalizationgainwhwh ifdetisnotNoneandlen(det): #把boxesresize到im0的size det[:,:4]=scale_coords(img.shape[2:],det[:,:4],im0.shape).round() #打印結果 forcindet[:,-1].unique(): n=(det[:,-1]==c).sum()#detectionsperclass s+='%g%ss,'%(n,names[int(c)])#addtostring #書寫結果 for*xyxy,conf,clsindet: ifsave_txt: #xyxy2xywh==>把預測得到的坐標結果[x1,y1,x2,y2]轉換為[x,y,w,h]其中xy1=top-left,xy2=bottom-right xywh=(xyxy2xywh(torch.tensor(xyxy).view(1,4))/gn).view(-1).tolist()#normalizedxywh withopen(save_path[:save_path.rfind('.')]+'.txt','a')asfile: file.write(('%g'*5+' ')%(cls,*xywh))#labelformat ifsave_imgorview_img:#Addbboxtoimage label='%s%.2f'%(names[int(cls)],conf) iflabelisnotNone: if(label.split())[0]=='person': #print(xyxy) people_coords.append(xyxy) #plot_one_box(xyxy,im0,line_thickness=3) plot_dots_on_people(xyxy,im0) #通過people_coords繪制people之間的連接線 #這里主要分為"LowRisk"和"HighRisk" distancing(people_coords,im0,dist_thres_lim=(200,250)) #Printtime(inference+NMS) print('%sDone.(%.3fs)'%(s,t2-t1)) #Streamresults ifview_img: cv2.imshow(p,im0) ifcv2.waitKey(1)==ord('q'):#qtoquit raiseStopIteration #Saveresults(imagewithdetections) ifsave_img: ifdataset.mode=='images': cv2.imwrite(save_path,im0) else: ifvid_path!=save_path:#newvideo vid_path=save_path ifisinstance(vid_writer,cv2.VideoWriter): vid_writer.release()#releasepreviousvideowriter fps=vid_cap.get(cv2.CAP_PROP_FPS) w=int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h=int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) vid_writer=cv2.VideoWriter(save_path,cv2.VideoWriter_fourcc(*opt.fourcc),fps,(w,h)) vid_writer.write(im0) ifsave_txtorsave_img: print('Resultssavedto%s'%os.getcwd()+os.sep+out) ifplatform=='darwin':#MacOS os.system('open'+save_path) print('Done.(%.3fs)'%(time.time()-t0)) if__name__=='__main__': parser=argparse.ArgumentParser() parser.add_argument('--weights',type=str,default='./weights/yolov5s.pt',help='model.ptpath') parser.add_argument('--source',type=str,default='./inference/videos/',help='source')#file/folder,0forwebcam parser.add_argument('--output',type=str,default='./inference/output',help='outputfolder')#outputfolder parser.add_argument('--img-size',type=int,default=640,help='inferencesize(pixels)') parser.add_argument('--conf-thres',type=float,default=0.4,help='objectconfidencethreshold') parser.add_argument('--iou-thres',type=float,default=0.5,help='IOUthresholdforNMS') parser.add_argument('--fourcc',type=str,default='mp4v',help='outputvideocodec(verifyffmpegsupport)') parser.add_argument('--device',default='0',help='cudadevice,i.e.0or0,1,2,3orcpu') parser.add_argument('--view-img',action='store_true',help='displayresults') parser.add_argument('--save-txt',action='store_true',help='saveresultsto*.txt') parser.add_argument('--classes',nargs='+',type=int,help='filterbyclass') parser.add_argument('--agnostic-nms',action='store_true',help='class-agnosticNMS') parser.add_argument('--augment',action='store_true',help='augmentedinference') opt=parser.parse_args() opt.img_size=check_img_size(opt.img_size) print(opt) withtorch.no_grad(): detect()
審核編輯:郭婷
-
gpu
+關注
關注
28文章
4703瀏覽量
128725 -
代碼
+關注
關注
30文章
4753瀏覽量
68368 -
數據集
+關注
關注
4文章
1205瀏覽量
24649
原文標題:項目實踐 | 基于YOLO-V5實現行人社交距離風險提示
文章出處:【微信號:vision263com,微信公眾號:新機器視覺】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論