<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          目標(biāo)檢測(cè)算法之SSD

          共 15920字,需瀏覽 32分鐘

           ·

          2022-05-23 13:53

          點(diǎn)擊下方卡片,關(guān)注“新機(jī)器視覺(jué)”公眾號(hào)

          重磅干貨,第一時(shí)間送達(dá)

          本文轉(zhuǎn)自|機(jī)器學(xué)習(xí)算法工程師

          前言



          目標(biāo)檢測(cè)近年來(lái)已經(jīng)取得了很重要的進(jìn)展,主流的算法主要分為兩個(gè)類型:(1)two-stage方法,如R-CNN系算法,其主要思路是先通過(guò)啟發(fā)式方法(selective search)或者CNN網(wǎng)絡(luò)(RPN)產(chǎn)生一系列稀疏的候選框,然后對(duì)這些候選框進(jìn)行分類與回歸,two-stage方法的優(yōu)勢(shì)是準(zhǔn)確度高;(2)one-stage方法,如Yolo和SSD,其主要思路是均勻地在圖片的不同位置進(jìn)行密集抽樣,抽樣時(shí)可以采用不同尺度和長(zhǎng)寬比,然后利用CNN提取特征后直接進(jìn)行分類與回歸,整個(gè)過(guò)程只需要一步,所以其優(yōu)勢(shì)是速度快,但是均勻的密集采樣的一個(gè)重要缺點(diǎn)是訓(xùn)練比較困難,這主要是因?yàn)檎龢颖九c負(fù)樣本(背景)極其不均衡(參見(jiàn)Focal Loss,https://arxiv.org/abs/1708.02002),導(dǎo)致模型準(zhǔn)確度稍低。不同算法的性能如圖1所示,可以看到兩類方法在準(zhǔn)確度和速度上的差異。


          圖1 不同檢測(cè)算法的性能對(duì)比

          本文講解的是SSD算法,其英文全名是Single Shot MultiBox Detector,名字取得不錯(cuò),Single shot指明了SSD算法屬于one-stage方法,MultiBox指明了SSD是多框預(yù)測(cè)。在上一篇文章中我們已經(jīng)講了Yolo算法,從圖1也可以看到,SSD算法在準(zhǔn)確度和速度(除了SSD512)上都比Yolo要好很多。圖2給出了不同算法的基本框架圖,對(duì)于Faster R-CNN,其先通過(guò)CNN得到候選框,然后再進(jìn)行分類與回歸,而Yolo與SSD可以一步到位完成檢測(cè)。相比Yolo,SSD采用CNN來(lái)直接進(jìn)行檢測(cè),而不是

          像Yolo那樣在全連接層之后做檢測(cè)。其實(shí)采用卷積直接做檢測(cè)只是SSD相比Yolo的其中一個(gè)不同點(diǎn),另外還有兩個(gè)重要的改變,一是SSD提取了不同尺度的特征圖來(lái)做檢測(cè),大尺度特征圖(較靠前的特征圖)可以用來(lái)檢測(cè)小物體,而小尺度特征圖(較靠后的特征圖)用來(lái)檢測(cè)大物體;二是SSD采用了不同尺度和長(zhǎng)寬比的先驗(yàn)框(Prior boxes, Default boxes,在Faster R-CNN中叫做錨,Anchors)。Yolo算法缺點(diǎn)是難以檢測(cè)小目標(biāo),而且定位不準(zhǔn),但是這幾點(diǎn)重要改進(jìn)使得SSD在一定程度上克服這些缺點(diǎn)。下面我們?cè)敿?xì)講解SDD算法的原理,并最后給出如何用TensorFlow實(shí)現(xiàn)SSD算法。


          圖2 不同算法的基本框架圖

          1

          設(shè)計(jì)理念


          SSD和Yolo一樣都是采用一個(gè)CNN網(wǎng)絡(luò)來(lái)進(jìn)行檢測(cè),但是卻采用了多尺度的特征圖,其基本架構(gòu)如圖3所示。下面將SSD核心設(shè)計(jì)理念總結(jié)為以下三點(diǎn):

          圖3 SSD基本框架

          (1)采用多尺度特征圖用于檢測(cè)

          所謂多尺度采用大小不同的特征圖,CNN網(wǎng)絡(luò)一般前面的特征圖比較大,后面會(huì)逐漸采用stride=2的卷積或者pool來(lái)降低特征圖大小,這正如圖3所示,一個(gè)比較大的特征圖和一個(gè)比較小的特征圖,它們都用來(lái)做檢測(cè)。這樣做的好處是比較大的特征圖來(lái)用來(lái)檢測(cè)相對(duì)較小的目標(biāo),而小的特征圖負(fù)責(zé)檢測(cè)大目標(biāo),如圖4所示,8x8的特征圖可以劃分更多的單元,但是其每個(gè)單元的先驗(yàn)框尺度比較小。

          圖4 不同尺度的特征圖
          (2)采用卷積進(jìn)行檢測(cè)
          與Yolo最后采用全連接層不同,SSD直接采用卷積對(duì)不同的特征圖來(lái)進(jìn)行提取檢測(cè)結(jié)果。對(duì)于形狀為m*n*p,的特征圖,只需要采用3*3*3的特征圖,只需要采用。
          (3)設(shè)置先驗(yàn)框?

          在Yolo中,每個(gè)單元預(yù)測(cè)多個(gè)邊界框,但是其都是相對(duì)這個(gè)單元本身(正方塊),但是真實(shí)目標(biāo)的形狀是多變的,Yolo需要在訓(xùn)練過(guò)程中自適應(yīng)目標(biāo)的形狀。而SSD借鑒了Faster R-CNN中anchor的理念,每個(gè)單元設(shè)置尺度或者長(zhǎng)寬比不同的先驗(yàn)框,預(yù)測(cè)的邊界框(bounding boxes)是以這些先驗(yàn)框?yàn)榛鶞?zhǔn)的,在一定程度上減少訓(xùn)練難度。一般情況下,每個(gè)單元會(huì)設(shè)置多個(gè)先驗(yàn)框,其尺度和長(zhǎng)寬比存在差異,如圖5所示,可以看到每個(gè)單元使用了4個(gè)不同的先驗(yàn)框,圖片中貓和狗分別采用最適合它們形狀的先驗(yàn)框來(lái)進(jìn)行訓(xùn)練,后面會(huì)詳細(xì)講解訓(xùn)練過(guò)程中的先驗(yàn)框匹配原則。

          圖5 SSD的先驗(yàn)框
          SSD的檢測(cè)值也與Yolo不太一樣。對(duì)于每個(gè)單元的每個(gè)先驗(yàn)框,其都輸出一套獨(dú)立的檢測(cè)值,對(duì)應(yīng)一個(gè)邊界框,主要分為兩個(gè)部分。第一部分是各個(gè)類別的置信度或者評(píng)分,值得注意的是SSD將背景也當(dāng)做了一個(gè)特殊的類別,如果檢測(cè)目標(biāo)共有C個(gè)類別,SSD其實(shí)需要預(yù)測(cè)C+1個(gè)置信度值,其中第一個(gè)置信度指的是不含目標(biāo)或者屬于背景的評(píng)分。后面當(dāng)我們說(shuō)C個(gè)個(gè)類別置信度時(shí),請(qǐng)記住里面包含背景那個(gè)特殊的類別,即真實(shí)的檢測(cè)類別只有C-1個(gè)。在預(yù)測(cè)過(guò)程中,置信度最高的那個(gè)類別就是邊界框所屬的類別,特別地,當(dāng)?shù)谝粋€(gè)置信度值最高時(shí),表示邊界框中并不包含目標(biāo)。第二部分就是邊界框的location,包含4個(gè)值(cx,cy,w,h),分別表示邊界框的中心坐標(biāo)以及寬高。但是真實(shí)預(yù)測(cè)值其實(shí)只是邊界框相對(duì)于先驗(yàn)框的轉(zhuǎn)換值(paper里面說(shuō)是offset,但是覺(jué)得transformation更合適,參見(jiàn)R-CNN,https://arxiv.org/abs/1311.2524)。先驗(yàn)框位置用表示,其對(duì)應(yīng)邊界框用表示,那么邊界框的預(yù)測(cè)值l,其實(shí)是b相對(duì)于d的轉(zhuǎn)換值:

          習(xí)慣上,我們稱上面這個(gè)過(guò)程為邊界框的編碼(encode),預(yù)測(cè)時(shí),你需要反向這個(gè)過(guò)程,即進(jìn)行解碼(decode),從預(yù)測(cè)值l中得到邊界框的真實(shí)位置b:

          然而,在SSD的Caffe源碼(https://github.com/weiliu89/caffe/tree/ssd實(shí)現(xiàn)中還有trick,那就是設(shè)置variance超參數(shù)來(lái)調(diào)整檢測(cè)值,通過(guò)bool參數(shù)variance_encoded_in_target來(lái)控制兩種模式,當(dāng)其為True時(shí),表示variance被包含在預(yù)測(cè)值中,就是上面那種情況。但是如果是Fasle(大部分采用這種方式,訓(xùn)練更容易?),就需要手動(dòng)設(shè)置超參數(shù)variance,用來(lái)對(duì)l的4個(gè)值進(jìn)行放縮,此時(shí)邊界框需要這樣解碼:?


          綜上所述,對(duì)于一個(gè)大小m*n的特征圖,共有mn個(gè)單元,每個(gè)單元設(shè)置的先驗(yàn)框數(shù)目記為k,那么每個(gè)單元共需要(c+4)k個(gè)預(yù)測(cè)值,所有的單元共需要(c+4)kmn個(gè)預(yù)測(cè)值,由于SSD采用卷積做檢測(cè),所以就需要(c+4)k個(gè)卷積核完成這個(gè)特征圖的檢測(cè)過(guò)程。

          2

          網(wǎng)絡(luò)結(jié)構(gòu)


          SSD采用VGG16作為基礎(chǔ)模型,然后在VGG16的基礎(chǔ)上新增了卷積層來(lái)獲得更多的特征圖以用于檢測(cè)。SSD的網(wǎng)絡(luò)結(jié)構(gòu)如圖6所示。上面是SSD模型,下面是Yolo模型,可以明顯看到SSD利用了多尺度的特征圖做檢測(cè)。模型的輸入圖片大小是300*300(還可以是512*512,其與前者網(wǎng)絡(luò)結(jié)構(gòu)沒(méi)有差別,只是最后新增一個(gè)卷積層,本文不再討論)。

          圖5 SSD網(wǎng)絡(luò)結(jié)構(gòu)

          采用VGG16做基礎(chǔ)模型,首先VGG16是在ILSVRC CLS-LOC數(shù)據(jù)集預(yù)訓(xùn)練。然后借鑒了DeepLab-LargeFOV,鏈接https://export.arxiv.org/pdf/1606.00915,分別將VGG16的全連接層fc6和fc7轉(zhuǎn)換成3*3卷積層conv6和1*1卷積層conv7,同時(shí)將池化層pool5由原來(lái)的2*2-s2變成3*3-s1(猜想是不想reduce特征圖大小),為了配合這種變化,采用了一種Atrous Algorithm,其實(shí)就是conv6采用擴(kuò)展卷積或帶孔卷積(Dilation Conv,https://arxiv.org/abs/1511.07122),其在不增加參數(shù)與模型復(fù)雜度的條件下指數(shù)級(jí)擴(kuò)大卷積的視野,其使用擴(kuò)張率(dilation rate)參數(shù),來(lái)表示擴(kuò)張的大小,如下圖6所示,(a)是普通的3*3卷積,其視野就是3*3,,(b)是擴(kuò)張率為2,此時(shí)視野變成7*7,(c)擴(kuò)張率為4時(shí),視野擴(kuò)大為15*15,但是視野的特征更稀疏了。Conv6采用3*3大小但dilation rate=6的擴(kuò)展卷積。

          圖6 擴(kuò)展卷積

          然后移除dropout層和fc8層,并新增一系列卷積層,在檢測(cè)數(shù)據(jù)集上做finetuing。

          其中VGG16中的Conv4_3層將作為用于檢測(cè)的第一個(gè)特征圖。conv4_3層特征圖大小是38*38,但是該層比較靠前,其norm較大,所以在其后面增加了一個(gè)L2 Normalization層(參見(jiàn)ParseNet,https://arxiv.org/abs/1506.04579),以保證和后面的檢測(cè)層差異不是很大,這個(gè)和Batch Normalization層不太一樣,其僅僅是對(duì)每個(gè)像素點(diǎn)在channle維度做歸一化,而Batch Normalization層是在[batch_size, width, height]三個(gè)維度上做歸一化。歸一化后一般設(shè)置一個(gè)可訓(xùn)練的放縮變量gamma,使用TF可以這樣簡(jiǎn)單實(shí)現(xiàn):

          # l2norm (not bacth norm, spatial normalization)
          def l2norm(x, scale, trainable=True, scope="L2Normalization"):
          ? ?n_channels
          = x.get_shape().as_list()[-1]
          ? ?l2_norm = tf.nn.l2_normalize(x, [3], epsilon=1e-12)
          ? ?with tf.variable_scope(scope):
          ? ? ? ?gamma = tf.get_variable("gamma", shape=[n_channels, ], dtype=tf.float32,
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?initializer=tf.constant_initializer(scale),
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?trainable=trainable)
          ? ? ? ?return l2_norm * gamma


          從后面新增的卷積層中提取Conv7,Conv8_2,Conv9_2,Conv10_2,Conv11_2作為檢測(cè)所用的特征圖,加上Conv4_3層,共提取了6個(gè)特征圖,其大小分別是(38,38),(19,19),(10,10),(5,5),(3,3,),(1,1),,但是不同特征圖設(shè)置的先驗(yàn)框數(shù)目不同(同一個(gè)特征圖上每個(gè)單元設(shè)置的先驗(yàn)框是相同的,這里的數(shù)目指的是一個(gè)單元的先驗(yàn)框數(shù)目)。先驗(yàn)框的設(shè)置,包括尺度(或者說(shuō)大小)和長(zhǎng)寬比兩個(gè)方面。對(duì)于先驗(yàn)框的尺度,其遵守一個(gè)線性遞增規(guī)則:隨著特征圖大小降低,先驗(yàn)框尺度線性增加:?

          其中m指的特征圖個(gè)數(shù),但卻是5,因?yàn)榈谝粚樱–onv4_3層)是單獨(dú)設(shè)置的,Sk示先驗(yàn)框大小相對(duì)于圖片的比例,而Smin和Smax表示比例的最小值與最大值,paper里面取0.2和0.9。對(duì)于第一個(gè)特征圖,其先驗(yàn)框的尺度比例一般設(shè)置為Smin/2=0.1,那么尺度為300*0.1=30。對(duì)于后面的特征圖,先驗(yàn)框尺度按照上面公式線性增加,但是先將尺度比例先擴(kuò)大100倍,此時(shí)增長(zhǎng)步長(zhǎng)為,這樣各個(gè)特征圖的Sk為20,37,54,71,88,將這些比例除以100,然后再乘以圖片大小,可以得到各個(gè)特征圖的尺度為60,111,162,213,264,這種計(jì)算方式是參考SSD的Caffe源碼。綜上,可以得到各個(gè)特征圖的先驗(yàn)框尺度30,60,111,162,213,264。對(duì)于長(zhǎng)寬比,一般選取,對(duì)于特定的長(zhǎng)寬比,按如下公式計(jì)算先驗(yàn)框的寬度與高度(后面的Sk均指的是先驗(yàn)框?qū)嶋H尺度,而不是尺度比例):?

          默認(rèn)情況下,每個(gè)特征圖會(huì)有一個(gè)且尺度為Sk的先驗(yàn)框,除此之外,還會(huì)設(shè)置一個(gè)尺度為的先驗(yàn)框,這樣每個(gè)特征圖都設(shè)置了兩個(gè)長(zhǎng)寬比為1但大小不同的正方形先驗(yàn)框。注意最后一個(gè)特征圖需要參考一個(gè)虛擬來(lái)計(jì)算。因此,每個(gè)特征圖一共有6個(gè)先驗(yàn)框,但是在實(shí)現(xiàn)時(shí),Conv4_3,Conv10_2和Conv11_2層僅使用4個(gè)先驗(yàn)框,它們不使用長(zhǎng)寬比為3,1/3的先驗(yàn)框。每個(gè)單元的先驗(yàn)框的中心點(diǎn)分布在各個(gè)單元的中心,即,其中為特征圖的大小。

          得到了特征圖之后,需要對(duì)特征圖進(jìn)行卷積得到檢測(cè)結(jié)果,圖7給出了一個(gè)5*5大小的特征圖的檢測(cè)過(guò)程。其中Priorbox是得到先驗(yàn)框,前面已經(jīng)介紹了生成規(guī)則。檢測(cè)值包含兩個(gè)部分:類別置信度和邊界框位置,各采用一次3*3卷積來(lái)進(jìn)行完成。令為該特征圖所采用的先驗(yàn)框數(shù)目,那么類別置信度需要的卷積核數(shù)量為,而邊界框位置需要的卷積核數(shù)量為。由于每個(gè)先驗(yàn)框都會(huì)預(yù)測(cè)一個(gè)邊界框,所以SSD300一共可以預(yù)測(cè)

          個(gè)邊界框,這是一個(gè)相當(dāng)龐大的數(shù)字,所以說(shuō)SSD本質(zhì)上是密集采樣。


          圖7 基于卷積得到檢測(cè)結(jié)果


          3

          訓(xùn)練過(guò)程


          (1)先驗(yàn)框匹配?

          在訓(xùn)練過(guò)程中,首先要確定訓(xùn)練圖片中的ground truth(真實(shí)目標(biāo))與哪個(gè)先驗(yàn)框來(lái)進(jìn)行匹配,與之匹配的先驗(yàn)框所對(duì)應(yīng)的邊界框?qū)⒇?fù)責(zé)預(yù)測(cè)它。在Yolo中,ground truth的中心落在哪個(gè)單元格,該單元格中與其IOU最大的邊界框負(fù)責(zé)預(yù)測(cè)它。但是在SSD中卻完全不一樣,SSD的先驗(yàn)框與ground truth的匹配原則主要有兩點(diǎn)。首先,對(duì)于圖片中每個(gè)ground truth,找到與其IOU最大的先驗(yàn)框,該先驗(yàn)框與其匹配,這樣,可以保證每個(gè)ground truth一定與某個(gè)先驗(yàn)框匹配。通常稱與ground truth匹配的先驗(yàn)框?yàn)檎龢颖荆粗粢粋€(gè)先驗(yàn)框沒(méi)有與任何ground truth進(jìn)行匹配,那么該先驗(yàn)框只能與背景匹配,就是負(fù)樣本。一個(gè)圖片中g(shù)round truth是非常少的, 而先驗(yàn)框卻很多,如果僅按第一個(gè)原則匹配,很多先驗(yàn)框會(huì)是負(fù)樣本,正負(fù)樣本極其不平衡,所以需要第二個(gè)原則。第二個(gè)原則是:對(duì)于剩余的未匹配先驗(yàn)框,若某個(gè)ground truth的IOU大于某個(gè)閾值(一般是0.5),那么該先驗(yàn)框也與這個(gè)ground truth進(jìn)行匹配。這意味著某個(gè)ground truth可能與多個(gè)先驗(yàn)框匹配,這是可以的。但是反過(guò)來(lái)卻不可以,因?yàn)橐粋€(gè)先驗(yàn)框只能匹配一個(gè)ground truth,如果多個(gè)ground truth與某個(gè)先驗(yàn)框IOU大于閾值,那么先驗(yàn)框只與IOU最大的那個(gè)先驗(yàn)框進(jìn)行匹配。第二個(gè)原則一定在第一個(gè)原則之后進(jìn)行,仔細(xì)考慮一下這種情況,如果某個(gè)ground truth所對(duì)應(yīng)最大IOU小于閾值,并且所匹配的先驗(yàn)框卻與另外一個(gè)ground truth的IOU大于閾值,那么該先驗(yàn)框應(yīng)該匹配誰(shuí),答案應(yīng)該是前者,首先要確保某個(gè)ground truth一定有一個(gè)先驗(yàn)框與之匹配。但是,這種情況我覺(jué)得基本上是不存在的。由于先驗(yàn)框很多,某個(gè)ground truth的最大IOU肯定大于閾值,所以可能只實(shí)施第二個(gè)原則既可以了,這里的TensorFlow(https://github.com/xiaohu2015/SSD-Tensorflow/blob/master/nets/ssd_common.py)版本就是只實(shí)施了第二個(gè)原則,但是這里的Pytorch(https://github.com/amdegroot/ssd.pytorch/blob/master/layers/box_utils.py)兩個(gè)原則都實(shí)施了。圖8為一個(gè)匹配示意圖,其中綠色的GT是ground truth,紅色為先驗(yàn)框,F(xiàn)P表示負(fù)樣本,TP表示正樣本。


          圖8 先驗(yàn)框匹配示意圖

          盡管一個(gè)ground truth可以與多個(gè)先驗(yàn)框匹配,但是ground truth相對(duì)先驗(yàn)框還是太少了,所以負(fù)樣本相對(duì)正樣本會(huì)很多。為了保證正負(fù)樣本盡量平衡,SSD采用了hard negative mining,就是對(duì)負(fù)樣本進(jìn)行抽樣,抽樣時(shí)按照置信度誤差(預(yù)測(cè)背景的置信度越小,誤差越大)進(jìn)行降序排列,選取誤差的較大的top-k作為訓(xùn)練的負(fù)樣本,以保證正負(fù)樣本比例接近1:3。

          (2)損失函數(shù)?

          訓(xùn)練樣本確定了,然后就是損失函數(shù)了。損失函數(shù)定義為位置誤差(locatization loss, loc)與置信度誤差(confidence loss, conf)的加權(quán)和:?


          其中N是先驗(yàn)框的正樣本數(shù)量。這里為一個(gè)指示參數(shù),當(dāng)時(shí)表示第i個(gè)先驗(yàn)框與第j個(gè)ground truth匹配,并且ground truth的類別為p。c為類別置信度預(yù)測(cè)值。l為先驗(yàn)框的所對(duì)應(yīng)邊界框的位置預(yù)測(cè)值,而g

          是ground truth的位置參數(shù)。對(duì)于位置誤差,其采用Smooth L1 loss,定義如下:


          由于的存在,所以位置誤差僅針對(duì)正樣本進(jìn)行計(jì)算。值得注意的是,要先對(duì)ground truth的g進(jìn)行編碼得到,因?yàn)轭A(yù)測(cè)值l也是編碼值,若設(shè)置variance_encoded_in_target=True,編碼時(shí)要加上variance:?


          對(duì)于置信度誤差,其采用softmax loss:?

          權(quán)重系數(shù)a通過(guò)交叉驗(yàn)證設(shè)置為1。

          (3)數(shù)據(jù)擴(kuò)增?

          采用數(shù)據(jù)擴(kuò)增(Data Augmentation)可以提升SSD的性能,主要采用的技術(shù)有水平翻轉(zhuǎn)(horizontal flip),隨機(jī)裁剪加顏色扭曲(random crop & color distortion),隨機(jī)采集塊域(Randomly sample a patch)(獲取小目標(biāo)訓(xùn)練樣本),如下圖所示:

          圖9 數(shù)據(jù)擴(kuò)增方案

          其它的訓(xùn)練細(xì)節(jié)如學(xué)習(xí)速率的選擇詳見(jiàn)論文,這里不再贅述。

          4

          預(yù)測(cè)過(guò)程


          預(yù)測(cè)過(guò)程比較簡(jiǎn)單,對(duì)于每個(gè)預(yù)測(cè)框,首先根據(jù)類別置信度確定其類別(置信度最大者)與置信度值,并過(guò)濾掉屬于背景的預(yù)測(cè)框。然后根據(jù)置信度閾值(如0.5)過(guò)濾掉閾值較低的預(yù)測(cè)框。對(duì)于留下的預(yù)測(cè)框進(jìn)行解碼,根據(jù)先驗(yàn)框得到其真實(shí)的位置參數(shù)(解碼后一般還需要做clip,防止預(yù)測(cè)框位置超出圖片)。解碼之后,一般需要根據(jù)置信度進(jìn)行降序排列,然后僅保留top-k(如400)個(gè)預(yù)測(cè)框。最后就是進(jìn)行NMS算法,過(guò)濾掉那些重疊度較大的預(yù)測(cè)框。最后剩余的預(yù)測(cè)框就是檢測(cè)結(jié)果了。

          5

          性能評(píng)估


          首先整體看一下SSD在VOC2007,VOC2012及COCO數(shù)據(jù)集上的性能,如表1所示。相比之下,SSD512的性能會(huì)更好一些。加*的表示使用了image expansion data augmentation(通過(guò)zoom out來(lái)創(chuàng)造小的訓(xùn)練樣本)技巧來(lái)提升SSD在小目標(biāo)上的檢測(cè)效果,所以性能會(huì)有所提升。

          表1 SSD在不同數(shù)據(jù)集上的性能

          SSD與其它檢測(cè)算法的對(duì)比結(jié)果(在VOC2007數(shù)據(jù)集)如表2所示,基本可以看到,SSD與Faster R-CNN有同樣的準(zhǔn)確度,并且與Yolo具有同樣較快地檢測(cè)速度。

          表2 SSD與其它檢測(cè)算法的對(duì)比結(jié)果(在VOC2007數(shù)據(jù)集)?

          文章還對(duì)SSD的各個(gè)trick做了更為細(xì)致的分析,表3為不同的trick組合對(duì)SSD的性能影響,從表中可以得出如下結(jié)論:


          • 數(shù)據(jù)擴(kuò)增技術(shù)很重要,對(duì)于mAP的提升很大;

          • 使用不同長(zhǎng)寬比的先驗(yàn)框可以得到更好的結(jié)果;

          表3 不同的trick組合對(duì)SSD的性能影響?

          同樣的,采用多尺度的特征圖用于檢測(cè)也是至關(guān)重要的,這可以從表4中看出:

          表4 多尺度特征圖對(duì)SSD的影響?

          6

          TensorFlow上的實(shí)現(xiàn)


          SSD在很多框架上都有了開(kāi)源的實(shí)現(xiàn),這里基于balancap的TensorFlow版本(https://github.com/balancap/SSD-Tensorflow)來(lái)實(shí)現(xiàn)SSD的Inference過(guò)程。這里實(shí)現(xiàn)的是SSD300,與paper里面不同的是,這里采用。首先定義SSD的參數(shù):


          self.ssd_params = SSDParams(img_shape=(300, 300), ? # 輸入圖片大小
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?num_classes=21, ? ? # 類別數(shù)+背景
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?no_annotation_label=21,
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?feat_layers=["block4", "block7", "block8", "block9", "block10", "block11"], ? # 要進(jìn)行檢測(cè)的特征圖name
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?feat_shapes=[(38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1)], ?# 特征圖大小
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?anchor_size_bounds=[0.15, 0.90], ?# 特征圖尺度范圍
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?anchor_sizes=[(21., 45.),
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(45., 99.),
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(99., 153.),
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(153., 207.),
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(207., 261.),
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(261., 315.)], ?# 不同特征圖的先驗(yàn)框尺度(第一個(gè)值是s_k,第2個(gè)值是s_k+1
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?anchor_ratios=[[2, .5],
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [2, .5, 3, 1. / 3],
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [2, .5, 3, 1. / 3],
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [2, .5, 3, 1. / 3],
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [2, .5],
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [2, .5]], # 特征圖先驗(yàn)框所采用的長(zhǎng)寬比(每個(gè)特征圖都有2個(gè)正方形先驗(yàn)框)
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?anchor_steps=[8, 16, 32, 64, 100, 300], ?# 特征圖的單元大小
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?anchor_offset=0.5, ? ? ? ? ? ? ? ? ? ? ? # 偏移值,確定先驗(yàn)框中心
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?normalizations=[20, -1, -1, -1, -1, -1], ?# l2 norm
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?prior_scaling=[0.1, 0.1, 0.2, 0.2] ? ? ? # variance
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?)

          然后構(gòu)建整個(gè)網(wǎng)絡(luò),注意對(duì)于stride=2的conv不要使用TF自帶的padding="same",而是手動(dòng)pad,這是為了與Caffe一致:


          def _built_net(self):
          ? ?"""Construct the SSD net"""
          ? ?self.end_points = {} ?# record the detection layers output
          ? ?self._images = tf.placeholder(tf.float32, shape=[None, self.ssd_params.img_shape[0],
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.ssd_params.img_shape[1], 3])
          ? ?with tf.variable_scope("ssd_300_vgg"):
          ? ? ? ?# original vgg layers
          ? ? ? ?# block 1
          ? ? ? ?net = conv2d(self._images, 64, 3, scope="conv1_1")
          ? ? ? ?net = conv2d(net, 64, 3, scope="conv1_2")
          ? ? ? ?self.end_points["block1"] = net
          ? ? ? ?net = max_pool2d(net, 2, scope="pool1")
          ? ? ? ?# block 2
          ? ? ? ?net = conv2d(net, 128, 3, scope="conv2_1")
          ? ? ? ?net = conv2d(net, 128, 3, scope="conv2_2")
          ? ? ? ?self.end_points["block2"] = net
          ? ? ? ?net = max_pool2d(net, 2, scope="pool2")
          ? ? ? ?# block 3
          ? ? ? ?net = conv2d(net, 256, 3, scope="conv3_1")
          ? ? ? ?net = conv2d(net, 256, 3, scope="conv3_2")
          ? ? ? ?net = conv2d(net, 256, 3, scope="conv3_3")
          ? ? ? ?self.end_points["block3"] = net
          ? ? ? ?net = max_pool2d(net, 2, scope="pool3")
          ? ? ? ?# block 4
          ? ? ? ?net = conv2d(net, 512, 3, scope="conv4_1")
          ? ? ? ?net = conv2d(net, 512, 3, scope="conv4_2")
          ? ? ? ?net = conv2d(net, 512, 3, scope="conv4_3")
          ? ? ? ?self.end_points["block4"] = net
          ? ? ? ?net = max_pool2d(net, 2, scope="pool4")
          ? ? ? ?# block 5
          ? ? ? ?net = conv2d(net, 512, 3, scope="conv5_1")
          ? ? ? ?net = conv2d(net, 512, 3, scope="conv5_2")
          ? ? ? ?net = conv2d(net, 512, 3, scope="conv5_3")
          ? ? ? ?self.end_points["block5"] = net
          ? ? ? ?print(net)
          ? ? ? ?net = max_pool2d(net, 3, stride=1, scope="pool5")
          ? ? ? ?print(net)

          ? ? ? ?# additional SSD layers
          ? ? ? ?# block 6: use dilate conv
          ? ? ? ?net = conv2d(net, 1024, 3, dilation_rate=6, scope="conv6")
          ? ? ? ?self.end_points["block6"] = net
          ? ? ? ?#net = dropout(net, is_training=self.is_training)
          ? ? ? ?# block 7
          ? ? ? ?net = conv2d(net, 1024, 1, scope="conv7")
          ? ? ? ?self.end_points["block7"] = net
          ? ? ? ?# block 8
          ? ? ? ?net = conv2d(net, 256, 1, scope="conv8_1x1")
          ? ? ? ?net = conv2d(pad2d(net, 1), 512, 3, stride=2, scope="conv8_3x3",
          ? ? ? ? ? ? ? ? ? ? padding="valid")
          ? ? ? ?self.end_points["block8"] = net
          ? ? ? ?# block 9
          ? ? ? ?net = conv2d(net, 128, 1, scope="conv9_1x1")
          ? ? ? ?net = conv2d(pad2d(net, 1), 256, 3, stride=2, scope="conv9_3x3",
          ? ? ? ? ? ? ? ? ? ? padding="valid")
          ? ? ? ?self.end_points["block9"] = net
          ? ? ? ?# block 10
          ? ? ? ?net = conv2d(net, 128, 1, scope="conv10_1x1")
          ? ? ? ?net = conv2d(net, 256, 3, scope="conv10_3x3", padding="valid")
          ? ? ? ?self.end_points["block10"] = net
          ? ? ? ?# block 11
          ? ? ? ?net = conv2d(net, 128, 1, scope="conv11_1x1")
          ? ? ? ?net = conv2d(net, 256, 3, scope="conv11_3x3", padding="valid")
          ? ? ? ?self.end_points["block11"] = net

          ? ? ? ?# class and location predictions
          ? ? ? ?predictions = []
          ? ? ? ?logits = []
          ? ? ? ?locations = []
          ? ? ? ?for i, layer in enumerate(self.ssd_params.feat_layers):
          ? ? ? ? ? ?cls, loc = ssd_multibox_layer(self.end_points[layer], self.ssd_params.num_classes,
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.ssd_params.anchor_sizes[i],
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.ssd_params.anchor_ratios[i],
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.ssd_params.normalizations[i], scope=layer+"_box")
          ? ? ? ? ? ?predictions.append(tf.nn.softmax(cls))
          ? ? ? ? ? ?logits.append(cls)
          ? ? ? ? ? ?locations.append(loc)
          ? ? ? ?return predictions, logits, locations

          對(duì)于特征圖的檢測(cè),這里單獨(dú)定義了一個(gè)組合層ssd_multibox_layer,其主要是對(duì)特征圖進(jìn)行兩次卷積,分別得到類別置信度與邊界框位置:

          # multibox layer: get class and location predicitions from detection layer
          def ssd_multibox_layer(x, num_classes, sizes, ratios, normalization=-1, scope="multibox"):
          ? ?pre_shape
          = x.get_shape().as_list()[1:-1]
          ? ?pre_shape = [-1] + pre_shape
          ? ?with tf.variable_scope(scope):
          ? ? ? ?# l2 norm
          ? ? ? ?if normalization > 0:
          ? ? ? ? ? ?x = l2norm(x, normalization)
          ? ? ? ? ? ?print(x)
          ? ? ? ?# numbers of anchors
          ? ? ? ?n_anchors = len(sizes) + len(ratios)
          ? ? ? ?# location predictions
          ? ? ? ?loc_pred = conv2d(x, n_anchors*4, 3, activation=None, scope="conv_loc")
          ? ? ? ?loc_pred = tf.reshape(loc_pred, pre_shape + [n_anchors, 4])
          ? ? ? ?# class prediction
          ? ? ? ?cls_pred = conv2d(x, n_anchors*num_classes, 3, activation=None, scope="conv_cls")
          ? ? ? ?cls_pred = tf.reshape(cls_pred, pre_shape + [n_anchors, num_classes])
          ? ? ? ?return cls_pred, loc_pred


          對(duì)于先驗(yàn)框,可以基于numpy生成,定義在ssd_anchors.py文件中,鏈接為https://github.com/xiaohu2015/DeepLearning_tutorials/blob/master/ObjectDetections/SSD/ssd_anchors.py。結(jié)合先驗(yàn)框與檢測(cè)值,對(duì)邊界框進(jìn)行過(guò)濾與解碼:
          classes, scores, bboxes = self._bboxes_select(predictions, locations)
          這里將得到過(guò)濾得到的邊界框,其中classes, scores, bboxes分別表示類別,置信度值以及邊界框位置。
          基于訓(xùn)練好的權(quán)重文件在這里下載https://pan.baidu.com/s/1snhuTsT,這里對(duì)SSD進(jìn)行測(cè)試:
          ssd_net = SSD()
          classes, scores, bboxes = ssd_net.detections()
          images = ssd_net.images()

          sess = tf.Session()
          # Restore SSD model.
          ckpt_filename = './ssd_checkpoints/ssd_vgg_300_weights.ckpt'
          sess.run(tf.global_variables_initializer())
          saver = tf.train.Saver()
          saver.restore(sess, ckpt_filename)

          img = cv2.imread('./demo/dog.jpg')
          img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
          img_prepocessed = preprocess_image(img) ? # 預(yù)處理圖片,主要是歸一化和resize
          rclasses, rscores, rbboxes = sess.run([classes, scores, bboxes],
          ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?feed_dict={images: img_prepocessed})
          rclasses, rscores, rbboxes = process_bboxes(rclasses, rscores, rbboxes) ?# 處理預(yù)測(cè)框,包括clip,sort,nms

          plt_bboxes(img, rclasses, rscores, rbboxes) ?# 繪制檢測(cè)結(jié)果
          詳細(xì)的代碼放在GitHub上了,鏈接為https://github.com/xiaohu2015/DeepLearning_tutorials/tree/master/ObjectDetections/SSD,然后看一下一個(gè)自然圖片的檢測(cè)效果:

          如果你想實(shí)現(xiàn)SSD的train過(guò)程,你可以參考附錄里面的Caffe,TensorFlow以及Pytorch實(shí)現(xiàn)。

          小結(jié)?

          SSD在Yolo的基礎(chǔ)上主要改進(jìn)了三點(diǎn):多尺度特征圖,利用卷積進(jìn)行檢測(cè),設(shè)置先驗(yàn)框。這使得SSD在準(zhǔn)確度上比Yolo更好,而且對(duì)于小目標(biāo)檢測(cè)效果也相對(duì)好一點(diǎn)。由于很多實(shí)現(xiàn)細(xì)節(jié)都包含在源碼里面,文中有描述不準(zhǔn)或者錯(cuò)誤的地方在所難免,歡迎交流指正。

          參考文獻(xiàn)

          1. SSD: Single Shot MultiBox Detector ?鏈接:https://arxiv.org/pdf/1611.10012.pdf

          2. SSD Slide? ??鏈接:http://www.cs.unc.edu/~wliu/papers/ssd_eccv2016_slide.pdf

          3. SSD Caffe? ??鏈接:https://github.com/weiliu89/caffe/tree/ssd

          4. SSD TensorFlow? ??鏈接:https://github.com/balancap/SSD-Tensorflow

          5. SSD Pytorch? ??鏈接:https://github.com/amdegroot/ssd.pytorch

          6. leonardoaraujosantos Artificial Inteligence online book? ??鏈接:https://leonardoaraujosantos.gitbooks.io/artificial-inteligence/content/single-shot-detectors/ssd.html


          本文僅做學(xué)術(shù)分享,如有侵權(quán),請(qǐng)聯(lián)系刪文。

          —THE END—
          瀏覽 53
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩另类在线观看 | 操逼 18岁 | 综合网AV | 中文字幕亚洲无码在线 | sese网站 |