Pytorch搭建SSD目标检测平台
- 學(xué)習(xí)前言
- 什么是SSD目標(biāo)檢測(cè)算法
- 源碼下載
- SSD實(shí)現(xiàn)思路
-
- 一、預(yù)測(cè)部分
-
- 1、主干網(wǎng)絡(luò)介紹
- 2、從特征獲取預(yù)測(cè)結(jié)果
- 3、預(yù)測(cè)結(jié)果的解碼
- 4、在原圖上進(jìn)行繪制
- 二、訓(xùn)練部分
-
- 1、真實(shí)框的處理
- 2、利用處理完的真實(shí)框與對(duì)應(yīng)圖片的預(yù)測(cè)結(jié)果計(jì)算loss
- 訓(xùn)練自己的ssd模型
?
學(xué)習(xí)前言
一起來看看SSD的Pytorch實(shí)現(xiàn)吧,順便訓(xùn)練一下自己的數(shù)據(jù)。
什么是SSD目標(biāo)檢測(cè)算法
SSD是一種非常優(yōu)秀的one-stage目標(biāo)檢測(cè)方法,one-stage算法就是目標(biāo)檢測(cè)和分類是同時(shí)完成的,其主要思路是利用CNN提取特征后,均勻地在圖片的不同位置進(jìn)行密集抽樣,抽樣時(shí)可以采用不同尺度和長(zhǎng)寬比,物體分類與預(yù)測(cè)框的回歸同時(shí)進(jìn)行,整個(gè)過程只需要一步,所以其優(yōu)勢(shì)是速度快。
但是均勻的密集采樣的一個(gè)重要缺點(diǎn)是訓(xùn)練比較困難,這主要是因?yàn)檎龢颖九c負(fù)樣本(背景)極其不均衡(參見Focal Loss),導(dǎo)致模型準(zhǔn)確度稍低。
SSD的英文全名是Single Shot MultiBox Detector,Single shot說明SSD算法屬于one-stage方法,MultiBox說明SSD算法基于多框預(yù)測(cè)。
源碼下載
https://github.com/bubbliiiing/ssd-pytorch
喜歡的可以點(diǎn)個(gè)star噢。
SSD實(shí)現(xiàn)思路
一、預(yù)測(cè)部分
1、主干網(wǎng)絡(luò)介紹
SSD采用的主干網(wǎng)絡(luò)是VGG網(wǎng)絡(luò),關(guān)于VGG的介紹大家可以看我的另外一篇博客https://blog.csdn.net/weixin_44791964/article/details/102779878,這里的VGG網(wǎng)絡(luò)相比普通的VGG網(wǎng)絡(luò)有一定的修改,主要修改的地方就是:
1、將VGG16的FC6和FC7層轉(zhuǎn)化為卷積層。
2、去掉所有的Dropout層和FC8層;
3、新增了Conv6、Conv7、Conv8、Conv9。
如圖所示,輸入的圖片經(jīng)過了改進(jìn)的VGG網(wǎng)絡(luò)(Conv1->fc7)和幾個(gè)另加的卷積層(Conv6->Conv9),進(jìn)行特征提取:
a、輸入一張圖片后,被resize到300x300的shape
b、conv1,經(jīng)過兩次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為64,輸出為(300,300,64),再2X2最大池化,輸出net為(150,150,64)。
c、conv2,經(jīng)過兩次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為128,輸出net為(150,150,128),再2X2最大池化,輸出net為(75,75,128)。
d、conv3,經(jīng)過三次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為256,輸出net為(75,75,256),再2X2最大池化,輸出net為(38,38,256)。
e、conv4,經(jīng)過三次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為512,輸出net為(38,38,512),再2X2最大池化,輸出net為(19,19,512)。
f、conv5,經(jīng)過三次[3,3]卷積網(wǎng)絡(luò),輸出的特征層為512,輸出net為(19,19,512),再步長(zhǎng)為1,卷積核大小為3X3最大池化,輸出net為(19,19,512)。
g、利用卷積代替全連接層,進(jìn)行了一次[3,3]卷積網(wǎng)絡(luò)和一次[1,1]卷積網(wǎng)絡(luò),輸出的特征層為1024,因此輸出的net為(19,19,1024)。(從這里往前都是VGG的結(jié)構(gòu))
h、conv6,經(jīng)過一次[1,1]卷積網(wǎng)絡(luò),調(diào)整通道數(shù),一次步長(zhǎng)為2的[3,3]卷積網(wǎng)絡(luò),輸出的特征層為512,因此輸出的net為(10,10,512)。
i、conv7,經(jīng)過一次[1,1]卷積網(wǎng)絡(luò),調(diào)整通道數(shù),一次步長(zhǎng)為2的[3,3]卷積網(wǎng)絡(luò),輸出的特征層為256,因此輸出的net為(5,5,256)。
j、conv8,經(jīng)過一次[1,1]卷積網(wǎng)絡(luò),調(diào)整通道數(shù),一次padding為valid的[3,3]卷積網(wǎng)絡(luò),輸出的特征層為256,因此輸出的net為(3,3,256)。
k、conv9,經(jīng)過一次[1,1]卷積網(wǎng)絡(luò),調(diào)整通道數(shù),一次padding為valid的[3,3]卷積網(wǎng)絡(luò),輸出的特征層為256,因此輸出的net為(1,1,256)。
實(shí)現(xiàn)代碼:
base = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',512, 512, 512]def vgg(i):layers = []in_channels = ifor v in base:if v == 'M':layers += [nn.MaxPool2d(kernel_size=2, stride=2)]elif v == 'C':layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]else:conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)layers += [conv2d, nn.ReLU(inplace=True)]in_channels = vpool5 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)conv6 = nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)conv7 = nn.Conv2d(1024, 1024, kernel_size=1)layers += [pool5, conv6,nn.ReLU(inplace=True), conv7, nn.ReLU(inplace=True)]return layersdef add_extras(i, batch_norm=False):# Extra layers added to VGG for feature scalinglayers = []in_channels = i# Block 6# 19,19,1024 -> 10,10,512layers += [nn.Conv2d(in_channels, 256, kernel_size=1, stride=1)]layers += [nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1)]# Block 7# 10,10,512 -> 5,5,256layers += [nn.Conv2d(512, 128, kernel_size=1, stride=1)]layers += [nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1)]# Block 8# 5,5,256 -> 3,3,256layers += [nn.Conv2d(256, 128, kernel_size=1, stride=1)]layers += [nn.Conv2d(128, 256, kernel_size=3, stride=1)]# Block 9# 3,3,256 -> 1,1,256layers += [nn.Conv2d(256, 128, kernel_size=1, stride=1)]layers += [nn.Conv2d(128, 256, kernel_size=3, stride=1)]return layers2、從特征獲取預(yù)測(cè)結(jié)果
由上圖我們可以知道,我們分別取conv4的第三次卷積的特征、fc7的特征、conv6的第二次卷積的特征、conv7的第二次卷積的特征、conv8的第二次卷積的特征、conv9的第二次卷積的特征,為了和普通特征層區(qū)分,我們稱之為有效特征層,來獲取預(yù)測(cè)結(jié)果。
對(duì)獲取到的每一個(gè)有效特征層,我們分別對(duì)其進(jìn)行一次num_priors x 4的卷積、一次num_priors x num_classes的卷積、并需要計(jì)算每一個(gè)有效特征層對(duì)應(yīng)的先驗(yàn)框。而num_priors指的是該特征層所擁有的先驗(yàn)框數(shù)量。
其中:
num_priors x 4的卷積 用于預(yù)測(cè) 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)先驗(yàn)框的變化情況。(為什么說是變化情況呢,這是因?yàn)閟sd的預(yù)測(cè)結(jié)果需要結(jié)合先驗(yàn)框獲得預(yù)測(cè)框,預(yù)測(cè)結(jié)果就是先驗(yàn)框的變化情況。)
num_priors x num_classes的卷積 用于預(yù)測(cè) 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)預(yù)測(cè)框對(duì)應(yīng)的種類。
每一個(gè)有效特征層對(duì)應(yīng)的先驗(yàn)框?qū)?yīng)著該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 預(yù)先設(shè)定好的多個(gè)框。
所有的特征層對(duì)應(yīng)的預(yù)測(cè)結(jié)果的shape如下:
實(shí)現(xiàn)代碼為:
3、預(yù)測(cè)結(jié)果的解碼
我們通過對(duì)每一個(gè)特征層的處理,可以獲得三個(gè)內(nèi)容,分別是:
num_priors x 4的卷積 用于預(yù)測(cè) 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)先驗(yàn)框的變化情況。**
num_priors x num_classes的卷積 用于預(yù)測(cè) 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)預(yù)測(cè)框對(duì)應(yīng)的種類。
每一個(gè)有效特征層對(duì)應(yīng)的先驗(yàn)框?qū)?yīng)著該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 預(yù)先設(shè)定好的多個(gè)框。
我們利用 num_priors x 4的卷積 與 每一個(gè)有效特征層對(duì)應(yīng)的先驗(yàn)框 獲得框的真實(shí)位置。
每一個(gè)有效特征層對(duì)應(yīng)的先驗(yàn)框就是,如圖所示的作用:
每一個(gè)有效特征層將整個(gè)圖片分成與其長(zhǎng)寬對(duì)應(yīng)的網(wǎng)格,如conv4-3的特征層就是將整個(gè)圖像分成38x38個(gè)網(wǎng)格;然后從每個(gè)網(wǎng)格中心建立多個(gè)先驗(yàn)框,如conv4-3的特征層就是建立了4個(gè)先驗(yàn)框;對(duì)于conv4-3的特征層來講,整個(gè)圖片被分成38x38個(gè)網(wǎng)格,每個(gè)網(wǎng)格中心對(duì)應(yīng)4個(gè)先驗(yàn)框,一共包含了,38x38x4個(gè),5776個(gè)先驗(yàn)框。
先驗(yàn)框雖然可以代表一定的框的位置信息與框的大小信息,但是其是有限的,無法表示任意情況,因此還需要調(diào)整,ssd利用num_priors x 4的卷積的結(jié)果對(duì)先驗(yàn)框進(jìn)行調(diào)整。
num_priors x 4中的num_priors表示了這個(gè)網(wǎng)格點(diǎn)所包含的先驗(yàn)框數(shù)量,其中的4表示了x_offset、y_offset、h和w的調(diào)整情況。
x_offset與y_offset代表了真實(shí)框距離先驗(yàn)框中心的xy軸偏移情況。
h和w代表了真實(shí)框的寬與高相對(duì)于先驗(yàn)框的變化情況。
SSD解碼過程就是將每個(gè)網(wǎng)格的中心點(diǎn)加上它對(duì)應(yīng)的x_offset和y_offset,加完后的結(jié)果就是預(yù)測(cè)框的中心,然后再利用 先驗(yàn)框和h、w結(jié)合 計(jì)算出預(yù)測(cè)框的長(zhǎng)和寬。這樣就能得到整個(gè)預(yù)測(cè)框的位置了。
當(dāng)然得到最終的預(yù)測(cè)結(jié)構(gòu)后還要進(jìn)行得分排序與非極大抑制篩選這一部分基本上是所有目標(biāo)檢測(cè)通用的部分。
1、取出每一類得分大于self.obj_threshold的框和得分。
2、利用框的位置和得分進(jìn)行非極大抑制。
實(shí)現(xiàn)代碼如下:
4、在原圖上進(jìn)行繪制
通過第三步,我們可以獲得預(yù)測(cè)框在原圖上的位置,而且這些預(yù)測(cè)框都是經(jīng)過篩選的。這些篩選后的框可以直接繪制在圖片上,就可以獲得結(jié)果了。
二、訓(xùn)練部分
1、真實(shí)框的處理
從預(yù)測(cè)部分我們知道,每個(gè)特征層的預(yù)測(cè)結(jié)果,num_priors x 4的卷積 用于預(yù)測(cè) 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)先驗(yàn)框的變化情況。
也就是說,我們直接利用ssd網(wǎng)絡(luò)預(yù)測(cè)到的結(jié)果,并不是預(yù)測(cè)框在圖片上的真實(shí)位置,需要解碼才能得到真實(shí)位置。
而在訓(xùn)練的時(shí)候,我們需要計(jì)算loss函數(shù),這個(gè)loss函數(shù)是相對(duì)于ssd網(wǎng)絡(luò)的預(yù)測(cè)結(jié)果的。我們需要把圖片輸入到當(dāng)前的ssd網(wǎng)絡(luò)中,得到預(yù)測(cè)結(jié)果;同時(shí)還需要把真實(shí)框的信息,進(jìn)行編碼,這個(gè)編碼是把真實(shí)框的位置信息格式轉(zhuǎn)化為ssd預(yù)測(cè)結(jié)果的格式信息。
也就是,我們需要找到 每一張用于訓(xùn)練的圖片的每一個(gè)真實(shí)框?qū)?yīng)的先驗(yàn)框,并求出如果想要得到這樣一個(gè)真實(shí)框,我們的預(yù)測(cè)結(jié)果應(yīng)該是怎么樣的。
從預(yù)測(cè)結(jié)果獲得真實(shí)框的過程被稱作解碼,而從真實(shí)框獲得預(yù)測(cè)結(jié)果的過程就是編碼的過程。
因此我們只需要將解碼過程逆過來就是編碼過程了。
實(shí)現(xiàn)代碼如下:
def encode(matched, priors, variances):g_cxcy = (matched[:, :2] + matched[:, 2:])/2 - priors[:, :2]g_cxcy /= (variances[0] * priors[:, 2:])g_wh = (matched[:, 2:] - matched[:, :2]) / priors[:, 2:]g_wh = torch.log(g_wh) / variances[1]return torch.cat([g_cxcy, g_wh], 1)在訓(xùn)練的時(shí)候我們只需要選擇iou最大的先驗(yàn)框就行了,這個(gè)iou最大的先驗(yàn)框就是我們用來預(yù)測(cè)這個(gè)真實(shí)框所用的先驗(yàn)框。
因此我們還要經(jīng)過一次篩選,將上述代碼獲得的真實(shí)框?qū)?yīng)的所有的iou較大先驗(yàn)框的預(yù)測(cè)結(jié)果中,iou最大的那個(gè)篩選出來。
實(shí)現(xiàn)代碼如下:
def match(threshold, truths, priors, variances, labels, loc_t, conf_t, idx):# 計(jì)算所有的先驗(yàn)框和真實(shí)框的重合程度overlaps = jaccard(truths,point_form(priors))# 所有真實(shí)框和先驗(yàn)框的最好重合程度# [truth_box,1]best_prior_overlap, best_prior_idx = overlaps.max(1, keepdim=True)best_prior_idx.squeeze_(1)best_prior_overlap.squeeze_(1)# 所有先驗(yàn)框和真實(shí)框的最好重合程度# [1,prior]best_truth_overlap, best_truth_idx = overlaps.max(0, keepdim=True)best_truth_idx.squeeze_(0)best_truth_overlap.squeeze_(0)# 找到與真實(shí)框重合程度最好的先驗(yàn)框,用于保證每個(gè)真實(shí)框都要有對(duì)應(yīng)的一個(gè)先驗(yàn)框best_truth_overlap.index_fill_(0, best_prior_idx, 2)# 對(duì)best_truth_idx內(nèi)容進(jìn)行設(shè)置for j in range(best_prior_idx.size(0)):best_truth_idx[best_prior_idx[j]] = j# 找到每個(gè)先驗(yàn)框重合程度最好的真實(shí)框matches = truths[best_truth_idx] # Shape: [num_priors,4]conf = labels[best_truth_idx] + 1 # Shape: [num_priors]# 如果重合程度小于threhold則認(rèn)為是背景conf[best_truth_overlap < threshold] = 0 # label as backgroundloc = encode(matches, priors, variances)loc_t[idx] = loc # [num_priors,4] encoded offsets to learnconf_t[idx] = conf # [num_priors] top class label for each prior2、利用處理完的真實(shí)框與對(duì)應(yīng)圖片的預(yù)測(cè)結(jié)果計(jì)算loss
loss的計(jì)算分為三個(gè)部分:
1、獲取所有正標(biāo)簽的框的預(yù)測(cè)結(jié)果的回歸loss。
2、獲取所有正標(biāo)簽的種類的預(yù)測(cè)結(jié)果的交叉熵loss。
3、獲取一定負(fù)標(biāo)簽的種類的預(yù)測(cè)結(jié)果的交叉熵loss。
由于在ssd的訓(xùn)練過程中,正負(fù)樣本極其不平衡,即 存在對(duì)應(yīng)真實(shí)框的先驗(yàn)框可能只有十來個(gè),但是不存在對(duì)應(yīng)真實(shí)框的負(fù)樣本卻有幾千個(gè),這就會(huì)導(dǎo)致負(fù)樣本的loss值極大,因此我們可以考慮減少負(fù)樣本的選取,對(duì)于ssd的訓(xùn)練來講,常見的情況是取三倍正樣本數(shù)量的負(fù)樣本用于訓(xùn)練。這個(gè)三倍呢,也可以修改,調(diào)整成自己喜歡的數(shù)字。
實(shí)現(xiàn)代碼如下:
class MultiBoxLoss(nn.Module):def __init__(self, num_classes, overlap_thresh, prior_for_matching,bkg_label, neg_mining, neg_pos, neg_overlap, encode_target,use_gpu=True):super(MultiBoxLoss, self).__init__()self.use_gpu = use_gpuself.num_classes = num_classesself.threshold = overlap_threshself.background_label = bkg_labelself.encode_target = encode_targetself.use_prior_for_matching = prior_for_matchingself.do_neg_mining = neg_miningself.negpos_ratio = neg_posself.neg_overlap = neg_overlapself.variance = Config['variance']def forward(self, predictions, targets):# 回歸信息,置信度,先驗(yàn)框loc_data, conf_data, priors = predictions# 計(jì)算出batch_sizenum = loc_data.size(0)# 取出所有的先驗(yàn)框priors = priors[:loc_data.size(1), :]# 先驗(yàn)框的數(shù)量num_priors = (priors.size(0))num_classes = self.num_classes# 創(chuàng)建一個(gè)tensor進(jìn)行處理loc_t = torch.Tensor(num, num_priors, 4)conf_t = torch.LongTensor(num, num_priors)for idx in range(num):# 獲得框truths = targets[idx][:, :-1].data# 獲得標(biāo)簽labels = targets[idx][:, -1].data# 獲得先驗(yàn)框defaults = priors.data# 找到標(biāo)簽對(duì)應(yīng)的先驗(yàn)框match(self.threshold, truths, defaults, self.variance, labels,loc_t, conf_t, idx)if self.use_gpu:loc_t = loc_t.cuda()conf_t = conf_t.cuda()# 轉(zhuǎn)化成Variableloc_t = Variable(loc_t, requires_grad=False)conf_t = Variable(conf_t, requires_grad=False)# 所有conf_t>0的地方,代表內(nèi)部包含物體pos = conf_t > 0# 求和得到每一個(gè)圖片內(nèi)部有多少正樣本num_pos = pos.sum(dim=1, keepdim=True)# 計(jì)算回歸losspos_idx = pos.unsqueeze(pos.dim()).expand_as(loc_data)loc_p = loc_data[pos_idx].view(-1, 4)loc_t = loc_t[pos_idx].view(-1, 4)loss_l = F.smooth_l1_loss(loc_p, loc_t, size_average=False)# 轉(zhuǎn)化形式batch_conf = conf_data.view(-1, self.num_classes)# 你可以把softmax函數(shù)看成一種接受任何數(shù)字并轉(zhuǎn)換為概率分布的非線性方法# 獲得每個(gè)框預(yù)測(cè)到真實(shí)框的類的概率loss_c = log_sum_exp(batch_conf) - batch_conf.gather(1, conf_t.view(-1, 1))loss_c = loss_c.view(num, -1)loss_c[pos] = 0 # 獲得每一張圖新的softmax的結(jié)果_, loss_idx = loss_c.sort(1, descending=True)_, idx_rank = loss_idx.sort(1)# 計(jì)算每一張圖的正樣本數(shù)量num_pos = pos.long().sum(1, keepdim=True)# 限制負(fù)樣本數(shù)量num_neg = torch.clamp(self.negpos_ratio*num_pos, max=pos.size(1)-1)neg = idx_rank < num_neg.expand_as(idx_rank)# 計(jì)算正樣本的loss和負(fù)樣本的losspos_idx = pos.unsqueeze(2).expand_as(conf_data)neg_idx = neg.unsqueeze(2).expand_as(conf_data)conf_p = conf_data[(pos_idx+neg_idx).gt(0)].view(-1, self.num_classes)targets_weighted = conf_t[(pos+neg).gt(0)]loss_c = F.cross_entropy(conf_p, targets_weighted, size_average=False)# Sum of losses: L(x,c,l,g) = (Lconf(x, c) + αLloc(x,l,g)) / NN = num_pos.data.sum()loss_l /= Nloss_c /= Nreturn loss_l, loss_c訓(xùn)練自己的ssd模型
ssd整體的文件夾構(gòu)架如下:
本文使用VOC格式進(jìn)行訓(xùn)練。
訓(xùn)練前將標(biāo)簽文件放在VOCdevkit文件夾下的VOC2007文件夾下的Annotation中。
訓(xùn)練前將圖片文件放在VOCdevkit文件夾下的VOC2007文件夾下的JPEGImages中。
在訓(xùn)練前利用voc2ssd.py文件生成對(duì)應(yīng)的txt。
再運(yùn)行根目錄下的voc_annotation.py,運(yùn)行前需要將classes改成你自己的classes。
- 1
就會(huì)生成對(duì)應(yīng)的2007_train.txt,每一行對(duì)應(yīng)其圖片位置及其真實(shí)框的位置。
在訓(xùn)練前需要修改model_data里面的voc_classes.txt文件,需要將classes改成你自己的classes。
還有config文件下面的Num_Classes,修改成分類的數(shù)量+1。
運(yùn)行train.py即可開始訓(xùn)練。
總結(jié)
以上是生活随笔為你收集整理的Pytorch搭建SSD目标检测平台的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Pytorch搭建Faster R-CN
- 下一篇: Pytorch搭建yolo3目标检测平台