論文題目:SSD: Single Shot MultiBox Detector
論文地址:https://arxiv.org/pdf/1512.02325.pdf
大神那年盛夏pytorch復現的SSD算法代碼:https://github.com/acm5656/ssd_pytorch
本篇轉載於大神蕁cecilia的SSD算法思想和結構詳解[3]
目標檢測大方向分主要分為二段式和一段式,前面筆者的博客里說到的RCNN、SSP-Net、Fast RCNN、Faster RCNN軍屬於二段式,均是利用選擇性搜索算法(SS)或者利用backbone CNN獲取特征圖然后通過region proposal network獲取anchor box,然后再分類和回歸,“二段”即先得到region proposal,然后再分類回歸。
而一段式則是如YOLO、SSD,其主要思路就是均勻地在input image上不同位置進行密集抽樣,抽樣時可以采用不同尺度和長寬比,然后利用backbone CNN提取特征后直接進行分類和回歸,整個過程只需要一步,所以優勢在於速度快,但是均勻密集采樣的一個重要缺點就是訓練比較困難,這主要是因為正樣本和負樣本極度不均衡,導致模型准確度比較低。
SSD算法(Single Shot MultiBox Detector),顧名思義,Single shot指明了SSD算法屬於one-stage方法,MultiBox指明了SSD是多框預測。對於Faster R-CNN,先通過CNN得到候選框,然后進行分類和回歸,而YOLO和SSD可以一步完成檢測,YOLO的解讀可以參考我之前的一篇文章https://www.cnblogs.com/nanmi/p/12785770.html;相對於YOLO,SSD采用CNN來直接進行檢測,而不是像YOLO那樣采用全連接層后做檢測。相對比於YOLO而言,還有其他兩個特點地改變:
第一,SSD提取了不同尺度的特征圖來做檢測,大尺度特征圖可以用來檢測小物體,而小特征圖用來檢測大物體;筆者到這里就笑了,這不是和EfficientNet很相似嗎,有特征融合那味兒了。
第二,SSD采用了不同尺度和長寬比的先驗框,在faster r-cnn中稱為Anchors。YOLO算法缺點是難以檢測小物體,而且定位不准,但是對於這幾點,SSD在一定程度上克服這些缺點。
從上面的圖可以看出SSD算法前半部分是vgg-16的架構,作者在vgg-16的層次上,將vgg-16后邊兩層的全連接層(fc6,fc7)變換為了卷積層,conv7之后的層則是作者自己添加的識別層。
SSD僅在輸入圖像上運行一個卷積網絡並計算特征映射。作者在這個特征映射上運行一個3×3大小的卷積核來預測邊界框和分類概率。SSD也使用類似於Faster-RCNN的各種寬高比的anchor boxes ,並學習偏移而不是學習box。為了處理這個尺度,SSD在多個卷積層之后預測邊界框。 由於每個卷積層以不同的比例操作,因此能夠檢測各種比例的目標。
SSD的核心:
- 使用應用於要素圖的小卷積濾波器來預測固定的默認邊界框的類別得分和框偏移。
- 從不同尺度的特征圖中快速檢測不同尺度的准確度,並通過縱橫比明確區分預測。
- 即使在低分辨率輸入圖像上,這些設計特征也可實現簡單的端到端訓練和高精度,從而進一步提高速度與精度之間的權衡。
- 實驗包括對PASCAL VOC,COCO和ILSVRC評估的不同輸入尺寸的模型進行定時和精度分析,並與一系列最新的最新方法進行比較。
SSD和YOLO一樣都是采用一個CNN網絡來進行檢測,但是SSD卻采用了多尺度的特征圖,其基本架構圖如下:
采用多尺度特征圖用於檢測
所謂多尺度采用大小不同的特征圖,CNN網絡一般前面的特征圖比較大,后面逐漸采用stride=2的卷積或者pooling層來降低特征圖大小。一個比較大的特征圖和比較小的特征圖都用來做檢測,這樣做的好處就是比較大的特征圖用來檢測相對比較小的的目標,而小的特征圖負責用來檢測大的物體,如圖所示:(8x8的特征圖可以划分更多的單元,但是其每個單元的先驗框尺度比較小。)
采用卷積做檢測
YOLO最后采用的是全連接層,SSD直接采用卷積對不同的特征圖進行提取檢測結果,對於形狀為m×n×p的特征圖,只需要采用3×3×p這樣小的卷積核得到檢測值。
設置先驗框
類比於Faster RCNN的先驗anchors,在YOLO中,每個單元預測多個邊界框,但是都是相對於這個單元本身而言,但是真實目標的形狀時多變的,YOLO需要在訓練過程中自適應目標的形狀。而SSD借鑒了Faster R-CNN中anchors box的原理,每個單元設置尺度或者長寬比不同的先驗框,預測的邊界框都是以先驗框為基准,在一定程度上減少了訓練難度。一般情況下,每個單元會設置多個先驗框,其尺度和長寬比存在差異。每個單元使用了4個不同的先驗框。
對於每個單元的每個先驗框,其都輸出一套獨立的檢測值,對應一個邊界框,主要分為兩個部分。第一部分是各個類別的置信度或者評分,值得注意的是SSD將背景也當做了一個特殊的類別,如果檢測目標共有 c 個類別,SSD其實需要預測 c+1 個類別的置信度值,其中第一個置信度指的是不含目標或者屬於背景的評分。后面當我們說 c個類別置信度時,請記住里面包含背景那個特殊的類別,即真實的檢測類別只有 c-1個。在預測過程中,置信度最高的那個類別就是邊界框所屬的類別,特別地,當第一個置信度值最高時,表示邊界框中並不包含目標。第二部分就是邊界框的location,包含4個值 $(c x, c y, w, h)$,分別表示邊界框的中心坐標以及寬高。但是真實預測值其實只是邊界框相對於先驗框anchors的轉換值或者叫偏置值(論文里面說是offset)。先驗框位置用 $d=\left(d^{c x}, d^{c y}, d^{w}, d^{h}\right)$表示,其對應邊界框用 $b=\left(b^{c x}, b^{c y}, b^{w}, b^{h}\right)$表示,那么邊界框的預測值 $l$ 其實是 $b$相對於$d$的轉換值:
$l^{c x}=\left(b^{c x}-d^{c x}\right) / d^{w}, l^{c y}=\left(b^{c y}-d^{c y}\right) / d^{h}$
$l^{w}=\log \left(b^{w} / d^{w}\right), l^{h}=\log \left(b^{h} / d^{h}\right)$
習慣上,我們稱上面這個過程為邊界框的編碼(encode),預測時,你需要反向這個過程,即進行解碼(decode),從預測值 $l$ 中得到邊界框的真實位置 $b$ :
$b^{c x}=d^{w} l^{c x}+d^{c x}, b^{c y}=d^{y} l^{c y}+d^{c y}$
$b^{w}=d^{w} \exp \left(l^{w}\right), b^{h}=d^{h} \exp \left(l^{h}\right)$
然而,在SSD的Caffe源碼實現中還有trick,那就是設置variance超參數來調整檢測值,通過bool參數variance_encoded_in_target來控制兩種模式,當其為True時,表示variance被包含在預測值中,就是上面那種情況。但是如果是False(大部分采用這種方式,訓練可能更容易),就需要手動設置超參數variance,用來對$l$的4個值進行放縮,此時邊界框需要這樣解碼:
$b^{c x}=d^{w}\left(\right.$variance $\left.[0] * l^{c x}\right)+d^{c x}, b^{c y}=d^{y}\left(\right.$variance $\left.[1] * l^{c y}\right)+d^{c y}$
$b^{w}=d^{w} \exp \left(\right.$variance $\left.[2] * l^{w}\right), b^{h}=d^{h} \exp \left(\right.$variance $\left.[3] * l^{h}\right)$
綜上所述,對於一個大小m×n的特征圖,共有mn個單元,每個單元設置的先驗框數目記為 k ,那么每個單元共需要(c+4)×k個預測值,所有的單元共需要(c+4)×kmn個預測值,由於SSD采用卷積做檢測,所以就需要(c+4)×k個卷積核完成這個特征圖的檢測過程。
采用VGG16做基礎模型,首先VGG16是在ILSVRC CLS-LOC數據集預訓練。然后借鑒了DeepLab-LargeFOV,分別將VGG16的全連接層fc6和fc7轉換成3×3卷積層 conv6和1×1卷積層conv7,同時將池化層pool5由原來的stride=2的2×2變成stride=1的 3×3(猜想是不想reduce特征圖大小),為了配合這種變化,采用了一種Atrous Algorithm,其實就是conv6采用空洞卷積或擴展卷積或帶孔卷積(Dilation Conv),其在不增加參數與模型復雜度的條件下指數級擴大卷積的視野,其使用擴張率(dilation rate)參數,來表示擴張的大小,如下圖6所示,(a)是普通的 3×3 卷積,其視野就是 3×3 ,(b)是擴張率為2,此時視野變成 7×7 ,(c)擴張率為4時,視野擴大為 15×15 ,但是視野的特征更稀疏了。Conv6采用 3×3 大小但dilation rate=6的擴展卷積。
然后移除dropout層和fc8層,並新增一系列卷積層,在檢測數據集上做finetuing。
其中VGG16中的Conv4_3層將作為用於檢測的第一個特征圖。conv4_3層特征圖大小是 38×38,但是該層比較靠前,其norm較大,所以在其后面增加了一個L2 Normalization層,以保證和后面的檢測層差異不是很大,這個和Batch Normalization層不太一樣,其僅僅是對每個像素點在channle維度做歸一化,而Batch Normalization層是在[batch_size, width, height]三個維度上做歸一化。歸一化后一般設置一個可訓練的放縮變量gamma。
從后面新增的卷積層中提取Conv7,Conv8_2,Conv9_2,Conv10_2,Conv11_2作為檢測所用的特征圖,加上Conv4_3層,共提取了6個特征圖,其大小分別是(38,38),(19,19),(10,10),(5,5),(3,3),(1,1),但是不同特征圖設置的先驗框數目不同(同一個特征圖上每個單元設置的先驗框是相同的,這里的數目指的是一個單元的先驗框數目)。先驗框的設置,包括尺度(或者說大小)和長寬比兩個方面。對於先驗框的尺度,其遵守一個線性遞增規則:隨着特征圖大小降低,先驗框尺度線性增加:
$s_{k}=s_{\min }+\frac{s_{\max }-s_{\min }}{m-1}(k-1), k \in[1, m]$
其中m指的特征圖個數,但卻是5,因為第一層(Conv4_3層)是單獨設置的, $\boldsymbol{s}_{k}$表示先驗框大小相對於圖片的比例,而 $s_{\min }$和$s_{\max }$表示比例的最小值與最大值,paper里面取0.2和0.9。對於第一個特征圖,其先驗框的尺度比例一般設置為 $s_{\min }$的一半0.1 ,那么尺度為 300×0.1=30。對於后面的特征圖,先驗框尺度按照上面公式線性增加,但是先將尺度比例先擴大100倍,此時增長步長為 $\left\lfloor\frac{\left\lfloor s_{\max } \times 100\right\rfloor-\left\lfloor s_{\min } \times 100\right\rfloor}{m-1}\right\rfloor=17$,這樣各個特征圖的 $\boldsymbol{s}_{k}$為20,37,54,71,88 ,將這些比例除以100,然后再乘以圖片大小,可以得到各個特征圖的尺度為60,111,162,213,264 ,這種計算方式是參考SSD的Caffe源碼。綜上,可以得到各個特征圖的先驗框尺度30,60,111,162,213,264 。對於長寬比,一般選取 $a_{r} \in\left\{1,2,3, \frac{1}{2}, \frac{1}{3}\right\}$,對於特定的長寬比,按如下公式計算先驗框的寬度與高度(后面的$s_{k}$均指的是先驗框實際尺度,而不是尺度比例):
$w_{k}^{a}=s_{k} \sqrt{a_{r}}, h_{k}^{a}=s_{k} / \sqrt{a_{r}}$
默認情況下,每個特征圖會有一個 $a_{r}$=1且尺度為 $\boldsymbol{s}_{k}$的先驗框,除此之外,還會設置一個尺度為 $s_{k}^{\prime}=\sqrt{s_{k} s_{k+1}}$且 $a_{r}$=1的先驗框,這樣每個特征圖都設置了兩個長寬比為1但大小不同的正方形先驗框。注意最后一個特征圖需要參考一個虛擬$s_{m+1}$=300×105/100=315來計算 $s_{m}^{\prime}$。因此,每個特征圖一共有6個先驗框 $\left\{1,2,3, \frac{1}{2}, \frac{1}{3}, 1^{\prime}\right\}$,但是在實現時,Conv4_3,Conv10_2和Conv11_2層僅使用4個先驗框,它們不使用長寬比為3,$\frac{1}{3}$的先驗框。每個單元的先驗框的中心點分布在各個單元的中心,即($\frac{i+0.5} {\left|f_{k}\right|}$, $\frac{j+0.5} {\left|f_{k}\right|}$),i,j屬於(0, $\left|f_{k}\right|$),其中 $\left|f_{k}\right|$為特征圖的大小。
得到了特征圖之后,需要對特征圖進行卷積得到檢測結果,圖7給出了一個 5×5大小的特征圖的檢測過程。其中Priorbox是得到先驗框,前面已經介紹了生成規則。檢測值包含兩個部分:類別置信度和邊界框位置,各采用一次 3×3卷積來進行完成。令 $n_{k}$為該特征圖所采用的先驗框數目,那么類別置信度需要的卷積核數量為$n_{k}$×c,而邊界框位置需要的卷積核數量為$n_{k}$×4。由於每個先驗框都會預測一個邊界框,所以SSD300一共可以預測 38×38×4+19×19×6+10×10×6+5×5×6+3×3×4+1×1×4=8732個邊界框,這是一個相當龐大的數字,所以說SSD本質上是密集采樣。
1. 訓練
先驗框匹配
在訓練過程中,首先要確定訓練圖片中的ground truth(真實目標)與哪個先驗框來進行匹配,與之匹配的先驗框所對應的邊界框將負責預測它。在Yolo中,ground truth的中心落在哪個單元格,該單元格中與其IOU最大的邊界框負責預測它。但是在SSD中卻完全不一樣,SSD的先驗框與ground truth的匹配原則主要有兩點。首先,對於圖片中每個ground truth,找到與其IOU最大的先驗框,該先驗框與其匹配,這樣,可以保證每個ground truth一定與某個先驗框匹配。通常稱與ground truth匹配的先驗框為正樣本(其實應該是先驗框對應的預測box,不過由於是一一對應的就這樣稱呼了),反之,若一個先驗框沒有與任何ground truth進行匹配,那么該先驗框只能與背景匹配,就是負樣本。一個圖片中ground truth是非常少的, 而先驗框卻很多,如果僅按第一個原則匹配,很多先驗框會是負樣本,正負樣本極其不平衡,所以需要第二個原則。第二個原則是:對於剩余的未匹配先驗框,若某個ground truth的 IOU大於某個閾值(一般是0.5),那么該先驗框也與這個ground truth進行匹配。這意味着某個ground truth可能與多個先驗框匹配,這是可以的。但是反過來卻不可以,因為一個先驗框只能匹配一個ground truth,如果多個ground truth與某個先驗框 IOU大於閾值,那么先驗框只與IOU最大的那個先驗框進行匹配。第二個原則一定在第一個原則之后進行,仔細考慮一下這種情況,如果某個ground truth所對應最大 IOU小於閾值,並且所匹配的先驗框卻與另外一個ground truth的 IOU大於閾值,那么該先驗框應該匹配誰,答案應該是前者,首先要確保某個ground truth一定有一個先驗框與之匹配。但是,這種情況我覺得基本上是不存在的。由於先驗框很多,某個ground truth的最大 IOU肯定大於閾值,所以可能只實施第二個原則既可以了,這里的TensorFlow版本就是只實施了第二個原則,但是這里的Pytorch兩個原則都實施了。圖8為一個匹配示意圖,其中綠色的GT是ground truth,紅色為先驗框,FP表示負樣本,TP表示正樣本。
損失函數
訓練樣本確定了,然后就是損失函數了。損失函數定義為位置誤差(locatization loss, loc)與置信度誤差(confidence loss, conf)的加權和:
$L(x, c, l, g)=\frac{1}{N}\left(L_{c o n f}(x, c)+\alpha L_{l o c}(x, l, g)\right)$
其中 $N$是先驗框的正樣本數量。這里 $x_{i j}^{p} \in\{1,0\}$為一個指示參數,當 $x_{i j}^{p}=1$時表示第 $i$個先驗框與第 $j$ 個ground truth匹配,並且ground truth的類別為 $p$ 。 $c$ 為類別置信度預測值。 $l$為先驗框的所對應邊界框的位置預測值,而 $g$ 是ground truth的位置參數。對於位置誤差,其采用Smooth L1 loss,定義如下:
$L_{l o c}(x, l, g)=\sum_{i \in \operatorname{Pos} m \in\{c x, c y, w, h\}}^{N} x_{i j}^{k} \operatorname{smooth}_{\mathrm{L} 1}\left(l_{i}^{m}-\hat{g}_{j}^{m}\right)$
$\hat{g}_{j}^{c x}=\left(g_{j}^{c x}-d_{i}^{c x}\right) / d_{i}^{w} \quad \hat{g}_{j}^{c y}=\left(g_{j}^{c y}-d_{i}^{c y}\right) / d_{i}^{h}$
$\hat{g}_{j}^{w}=\log \left(\frac{g_{j}^{w}}{d_{i}^{w}}\right) \quad \hat{g}_{j}^{h}=\log \left(\frac{g_{j}^{h}}{d_{i}^{h}}\right)$
$\operatorname{smooth}_{L_{1}}(x)=\left\{\begin{array}{ll}0.5 x^{2} & \text { if }|x|<1 \\ |x|-0.5 & \text { otherwise }\end{array}\right.$
由於 $x_{i j}^{p}$ 的存在,所以位置誤差僅針對正樣本進行計算。值得注意的是,要先對ground truth的 $g$ 進行編碼得到 $\hat{g}$ ,因為預測值$l$也是編碼值,若設置variance_encoded_in_target=True,編碼時要加上variance:
$\hat{g}_{j}^{c x}=\left(g_{j}^{c x}-d_{i}^{c x}\right) / d_{i}^{w} /$variance$[0], \hat{g}_{j}^{c y}=\left(g_{j}^{c y}-d_{i}^{c y}\right) / d_{i}^{h} /$variance[1]
$\hat{g}_{j}^{w}=\log \left(g_{j}^{w} / d_{i}^{w}\right) /$variance$[2], \hat{g}_{j}^{h}=\log \left(g_{j}^{h} / d_{i}^{h}\right) /$variance[3]
對於置信度誤差,其采用softmax loss:
$L_{\text {conf}}(x, c)=-\sum_{i \in \text {Pos}}^{N} x_{i j}^{p} \log \left(\hat{c}_{i}^{p}\right)-\sum_{i \in N e g} \log \left(\hat{c}_{i}^{0}\right) \quad$ where $\quad \hat{c}_{i}^{p}=\frac{\exp \left(c_{i}^{p}\right)}{\sum_{p} \exp \left(c_{i}^{p}\right)}$
權重系數 $\alpha$ 通過交叉驗證設置為1。
數據擴增
采用數據擴增(Data Augmentation)可以提升SSD的性能,主要采用的技術有水平翻轉(horizontal flip),隨機裁剪加顏色扭曲(random crop & color distortion),隨機采集塊域(Randomly sample a patch)(獲取小目標訓練樣本),如下圖所示:
預測過程
預測過程比較簡單,對於每個預測框,首先根據類別置信度確定其類別(置信度最大者)與置信度值,並過濾掉屬於背景的預測框。然后根據置信度閾值(如0.5)過濾掉閾值較低的預測框。對於留下的預測框進行解碼,根據先驗框得到其真實的位置參數(解碼后一般還需要做clip,防止預測框位置超出圖片)。解碼之后,一般需要根據置信度進行降序排列,然后僅保留top-k(如400)個預測框。最后就是進行NMS算法,過濾掉那些重疊度較大的預測框。最后剩余的預測框就是檢測結果了。
Reference
[1] https://www.cnblogs.com/cmai/p/10076050.html
[2] https://blog.csdn.net/weixin_39749553/article/details/88086486
[3] https://www.cnblogs.com/cecilia-2019/p/11342791.html
[4] http://www.360doc.com/content/20/0104/21/99071_884171814.shtml