【目标检测】yolo系列:从yolov1到yolov5之YOLOv1详解及复现
檢測器通常能夠被分為兩類,一類是two-stage檢測器,最具代表的為faster R-CNN;另一類是one-stage檢測器,包括YOLO,SSD等。一般來說,two-stage檢測器具有高定位和識別準確性,而one-stage則有速度上的優勢。其在結構上的區別就是two-stage檢測器有一個生成region proposal的步驟,然后對其進行預測和分類;而one-stage則是直接對預測框進行回歸和分類預測。
一階段(one-stage)目標檢測算法直接將目標邊界定位問題轉化為回歸問題來處理。對圖像的多層特征圖進行均勻密集采樣,使用CNN網絡提取特征,然后直接進行分類和回歸。
YOLO系列是典型的目標檢測one-stage模型。本系列文章主要介紹從 YOLO v1發展到YOLO v5的改進和主要設計思路,并且會用教程的方式講述每一個YOLO模型。
YOLO v1(2015)
Redmon等人提出的 YOLO(You Only Look Once)算法,將目標檢測作為回歸問題解決,將候選區和檢測兩個階段合二為一。
下面講詳細說明yolo v1。
yolov1解決的需求
- 能夠檢測出圖中的多個大小不同的目標
通俗地講,就是用分類的算法設計了一個檢測器,YOLO的作者的思路如下:分類器輸出是一個one-hot vector,那將檢測器的輸出轉換成(c, x, y, w, h),c表示confidence置信度,把問題轉化成一個回歸問題,直接回歸出Bounding Box的位置。
主要的思路就是用一個(c,x,y,w,h)去負責image某個區域的目標。比如說圖片設置為16個區域,每個區域用1個(c,x,y,w,h)去負責,就可以一次輸出16個框,每個框是1個(c,x,y,w,h)。
所以,現在的模型是
img→cbrp16→cbrp32→cbrp64→cbrp128→...→fc256?fc[5]→c,x,y,w,himg \rightarrow cbrp16 \rightarrow cbrp32 \rightarrow cbrp64 \rightarrow cbrp128 \rightarrow ... \\ \rightarrow fc256-fc[5] \rightarrow c,x,y,w,h img→cbrp16→cbrp32→cbrp64→cbrp128→...→fc256?fc[5]→c,x,y,w,h
- 能夠檢測多類的目標
如果目標特別密集的情況下,只預測一個類別 的目標肯定不夠用,所以將預測出的輸出加上一個類別概率值,即C,則是某個目標框框出來的物體屬于其中一個類別的概率,C是一個one-hot vector,長度為類別的數目。
現在的模型是:
img→cbrp16→cbrp32→cbrp64→cbrp128→...→fc256?fc[5+C]?S?S→[c,x,y,w,h,one?hot]?S?Simg \rightarrow cbrp16 \rightarrow cbrp32 \rightarrow cbrp64 \rightarrow cbrp128 \rightarrow \\ ... \rightarrow fc256-fc[5+C]*S*S \\ \rightarrow [c,x,y,w,h,one-hot]*S*S img→cbrp16→cbrp32→cbrp64→cbrp128→...→fc256?fc[5+C]?S?S→[c,x,y,w,h,one?hot]?S?S
- 檢測小目標
小目標總是檢測不佳,所以我們專門設計神經元去擬合小目標。
對于每個區域,我們用2個五元組(c,x,y,w,h),一個負責回歸大目標,一個負責回歸小目標,同樣添加one-hot vector,one-hot就是[0,1],[1,0]這樣子,來表示目標框框出來的屬于哪一類。
yolo v1的詳細思路
總體思路
yolo v1 將一張圖片分為S × S個網格,每個網格負責預測中心點落在此網格內的目標,每個網格通過卷積,提取成一個特征向量,然后用這個特征向量去預測目標框。每個網格會預測B 個bounding box,C 個類別概率值,每個bounding box預測5個值(x, y , w, h, confidence) ,如下圖所示,一張圖片的預測結果有[S×S×(5?B+C)]個值。
下圖是論文中的圖:
YOLO v1其實就是劃分成了7*7個區域,預測49個目標,分了20個類別。
網絡結構
Yolov1的整個網絡先使用卷積層進行特征提取,再通過全連接層進行分類和回歸。構思來自GoogleNet,網絡由24層卷積+2層全連接層組成。在3×3的卷積后面加上1×1卷積來降低feature map的空間大小,從而較少網絡的參數和計算量。使用PASCAL VOC 數據集來評估模型,所以網絡的最后輸出為7×7×30=1470。
模型訓練
網絡的訓練分為2部分:
(1)預訓練一個分類網絡:使用上述網絡的前20個卷積層+average-pool+全連接層在ImageNet進行訓練,網絡輸入為(224×224×3)。
(2)訓練檢測網絡:移除預訓練網絡中的后兩層,再在預訓練網絡的基礎上+4層卷積層+2層全連接層,將網絡的輸入變為(448×448×3)。
在神經網絡結構確定之后,訓練效果好壞,由Loss函數和優化器決定。Yolo v1使用普通的梯度下降法作為優化器。Yolo v1使用的Loss函數如下:
一個網格中會預測多個bbox,在訓練時,只選擇IOU最大的那個bbox來負責預測目標,其他bbox的Pr(Object)值被設為0,使得bbox的預測更加專業,提高整體的recall。
模型預測
1. 使用非極大抑制生成預測框
在預測的時候,格點與格點并不會沖突,但是在預測一些大物體或者鄰近物體時,會有多個格點預測了同一個物體。此時采用非極大抑制(NMS),一個目標可能有多個預測框,通過NMS可以過濾掉一些多余的預測框,確保一個目標只有一個預測框。NMS是通過類別置信度來篩選的。
2. 推理時將 p?cp*cp?c 作為輸出置信度
在推理時,使用物體的類別預測最大值 p 乘以 預測框的最大值 c ,作為輸出預測物體的置信度。
首先從所有的預測框中找到置信度最大的那個bbox,然后挨個計算其與剩余bbox的IOU,如果其值大于一定閾值(重合度過高),那么就將該剩余bbox剔除;然后對剩余的預測框重復上述過程,直到處理完所有的檢測框
這樣也可以過濾掉一些大部分重疊的矩形框。輸出檢測物體的置信度,同時考慮了矩形框與類別,滿足閾值的輸出更加可信。
yolo v1存在的問題
- yolo v1每個網格只能預測一個類別,讓所有同一個網格預測出來的多個框屬于一個類別,所以對于相鄰目標的預測效果不好。(這個問題從yolo v2后就已經解決)
- 沒有計算背景的geo_loss,只計算了前景的geo_loss,樣本不均衡問題依然存在。
- 小目標預測效果不好
完整代碼如下:
https://github.com/abeardear/pytorch-YOLO-v1
yolov1源碼復現
數據集準備
選用voc2012+2007 數據集,可以提前下載,如需使用自己的數據集,請參考數據集格式。
- 下載voc2012的訓練數據集
- 下載voc2007的測試數據集
- 將所有圖片放在一個文件夾中,
- 對數據集進行預處理。
(1)xml_2_txt.py 將xml標注轉換為txt文件(應該將xml_2_txt.py放在voc數據集的同一文件夾中,或者在 xml_2_txt.py 中更改標注文件的路徑)
(2)dataset預處理數據集,txt描述文件,image_name.jpg x y w h c x y w h c 這樣就是說一張圖片中有兩個目標
依賴庫
- pytorch
- opencv
- visdom
- tqdm
模型訓練驗證
運行 train.py文件,file_root修改成自己訓練集的文件所在位置。
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
from torchvision import models
from torch.autograd import Variablefrom net import vgg16, vgg16_bn
from resnet_yolo import resnet50, resnet18
from yoloLoss import yoloLoss
from dataset import yoloDatasetfrom visualize import Visualizer
import numpy as npuse_gpu = torch.cuda.is_available()file_root = '/home/xzh/data/VOCdevkit/VOC2012/allimgs/'learning_rate = 0.001
num_epochs = 50
batch_size = 24
use_resnet = True
if use_resnet:net = resnet50()
else:net = vgg16_bn()
# net.classifier = nn.Sequential(
# nn.Linear(512 * 7 * 7, 4096),
# nn.ReLU(True),
# nn.Dropout(),
# #nn.Linear(4096, 4096),
# #nn.ReLU(True),
# #nn.Dropout(),
# nn.Linear(4096, 1470),
# )
#net = resnet18(pretrained=True)
#net.fc = nn.Linear(512,1470)
# initial Linear
# for m in net.modules():
# if isinstance(m, nn.Linear):
# m.weight.data.normal_(0, 0.01)
# m.bias.data.zero_()
print(net)
#net.load_state_dict(torch.load('yolo.pth'))
print('load pre-trined model')
if use_resnet:resnet = models.resnet50(pretrained=True)new_state_dict = resnet.state_dict()dd = net.state_dict()for k in new_state_dict.keys():print(k)if k in dd.keys() and not k.startswith('fc'):print('yes')dd[k] = new_state_dict[k]net.load_state_dict(dd)
else:vgg = models.vgg16_bn(pretrained=True)new_state_dict = vgg.state_dict()dd = net.state_dict()for k in new_state_dict.keys():print(k)if k in dd.keys() and k.startswith('features'):print('yes')dd[k] = new_state_dict[k]net.load_state_dict(dd)
if False:net.load_state_dict(torch.load('best.pth'))
print('cuda', torch.cuda.current_device(), torch.cuda.device_count())criterion = yoloLoss(7,2,5,0.5)
if use_gpu:net.cuda()net.train()
# different learning rate
params=[]
params_dict = dict(net.named_parameters())
for key,value in params_dict.items():if key.startswith('features'):params += [{'params':[value],'lr':learning_rate*1}]else:params += [{'params':[value],'lr':learning_rate}]
optimizer = torch.optim.SGD(params, lr=learning_rate, momentum=0.9, weight_decay=5e-4)
# optimizer = torch.optim.Adam(net.parameters(),lr=learning_rate,weight_decay=1e-4)# train_dataset = yoloDataset(root=file_root,list_file=['voc12_trainval.txt','voc07_trainval.txt'],train=True,transform = [transforms.ToTensor()] )
train_dataset = yoloDataset(root=file_root,list_file=['voc2012.txt','voc2007.txt'],train=True,transform = [transforms.ToTensor()] )
train_loader = DataLoader(train_dataset,batch_size=batch_size,shuffle=True,num_workers=4)
# test_dataset = yoloDataset(root=file_root,list_file='voc07_test.txt',train=False,transform = [transforms.ToTensor()] )
test_dataset = yoloDataset(root=file_root,list_file='voc2007test.txt',train=False,transform = [transforms.ToTensor()] )
test_loader = DataLoader(test_dataset,batch_size=batch_size,shuffle=False,num_workers=4)
print('the dataset has %d images' % (len(train_dataset)))
print('the batch_size is %d' % (batch_size))
logfile = open('log.txt', 'w')num_iter = 0
vis = Visualizer(env='xiong')
best_test_loss = np.inffor epoch in range(num_epochs):net.train()# if epoch == 1:# learning_rate = 0.0005# if epoch == 2:# learning_rate = 0.00075# if epoch == 3:# learning_rate = 0.001if epoch == 30:learning_rate=0.0001if epoch == 40:learning_rate=0.00001# optimizer = torch.optim.SGD(net.parameters(),lr=learning_rate*0.1,momentum=0.9,weight_decay=1e-4)for param_group in optimizer.param_groups:param_group['lr'] = learning_rateprint('\n\nStarting epoch %d / %d' % (epoch + 1, num_epochs))print('Learning Rate for this epoch: {}'.format(learning_rate))total_loss = 0.for i,(images,target) in enumerate(train_loader):images = Variable(images)target = Variable(target)if use_gpu:images,target = images.cuda(),target.cuda()pred = net(images)loss = criterion(pred,target)total_loss += loss.data[0]optimizer.zero_grad()loss.backward()optimizer.step()if (i+1) % 5 == 0:print ('Epoch [%d/%d], Iter [%d/%d] Loss: %.4f, average_loss: %.4f' %(epoch+1, num_epochs, i+1, len(train_loader), loss.data[0], total_loss / (i+1)))num_iter += 1vis.plot_train_val(loss_train=total_loss/(i+1))#validationvalidation_loss = 0.0net.eval()for i,(images,target) in enumerate(test_loader):images = Variable(images,volatile=True)target = Variable(target,volatile=True)if use_gpu:images,target = images.cuda(),target.cuda()pred = net(images)loss = criterion(pred,target)validation_loss += loss.data[0]validation_loss /= len(test_loader)vis.plot_train_val(loss_val=validation_loss)if best_test_loss > validation_loss:best_test_loss = validation_lossprint('get best test loss %.5f' % best_test_loss)torch.save(net.state_dict(),'best.pth')logfile.writelines(str(epoch) + '\t' + str(validation_loss) + '\n') logfile.flush() torch.save(net.state_dict(),'yolo.pth')
運行eval_voc.py 進行模型驗證,若模型表現效果不好,可以對train.py中對學習率等進行調整,也可以調整網絡結構,進行模型調優。
模型預測
運行predict.py,將訓練好并調好參數的最優模型傳入其中,進行預測。
結果如圖所示。
總結
以上是生活随笔為你收集整理的【目标检测】yolo系列:从yolov1到yolov5之YOLOv1详解及复现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【目标检测】yolo系列:从yolov1
- 下一篇: 【目标检测】yolo系列:从yolov1