ssd是經典的one-stage目標檢測算法,作者是基於caffe來實現的,這需要加入新的層來完成功能,caffe自定義層可以使用python和c++,faster rcnn既使用了c++定義如smoothl1layer,又使用了python定義,如proposaltargetlayer、roidatalayer等。而ssd完全使用c++來定義層,包括:
1)annotateddatalayer數據讀取層,用於讀取圖像和標簽數據,並且支持數據增強
2)permutelayer用於改變blob的讀取順序
3)priorboxlayer用於生成defalutbox
4)multiboxlosslayer,邊框回歸集成了l2loss、smoothl1,於分類集成了softmax和logistic,並且支持困難樣本挖掘、defaultbox匹配等功能。
name: "mbox_loss"
type: "MultiBoxLoss"
bottom: "mbox_loc"
bottom: "mbox_conf"
bottom: "mbox_priorbox"
bottom: "label"
top: "mbox_loss"
整個網絡比較繁雜,不過相對於fasterrcnn算簡單一些,我們從loss順藤摸瓜,首先查看label的存儲格式,這需要查看annotateddatalayer層是如何對數據進行讀取的,查看網絡定義文件發現數據讀取層的輸入要求是lmdb或leveldb格式,那么問題來了,如何將voc格式的數據制作成lmdb呢?原生caffe好像只支持分類的數據制作,github上說通過./data/VOC0712/create_list.sh和./data/VOC0712/create_data.sh來制作數據,查看create_data.sh文件,里面又調用了$root_dir/scripts/create_annoset.py,繼續查看create_annoset.py 發現最終調用的是/build/tools/convert_annoset進而查看tools/convert_annoset.cpp,里面實現了將分散的文件轉換為lmdb格式,但是依然還沒找到哪里解析了voc格式標注的xml文件,convert_annoset.cpp調用了ReadRichImageToAnnotatedDatum函數,終於在utils/io.cpp中找到了讀取xml標注文件的實現。數據層的輸出有data和label,data的格式是n×3×300×300,而label的格式是n*1×nofboxes*8。每個物體包含了8個信息[item_id, group_label, instance_id, xmin, ymin, xmax, ymax, diff]
,含義是batchsize個圖像中的第item_id幅圖像中的第group_label個類別下的第instance_id個目標的坐標為[xmin, ymin, xmax, ymax]。
loss層的label輸入格式清楚了,接下來解析mbox_priorbox的格式,mbox_priorbox是由多個priorbox層concat起來的,ssd300在6個featuremap上生成了priorbox,例如在conv4_3的featuremap上的priorbox數量為w×h×numprior,其存儲方式為chw,維度為2×1*(w×h×numprior*4),可以看出是3維的,而不是nchw,這是因為一個batch的圖像大小是相同的,為它們生成的priorbox也是完全相同的,所以不需要batch這個維度,另外注意channel維度是2,是存入了對應位置的方差varirance。多層的priorbox數據concat起來時,axis設為2,也就是在w的維度將bloob接起來形成一個如圖所示長條。

接着解析mbox_loc的維度,以大小為38×38的conv4_3的fmap為例,3*3的卷積在fmap上卷積得到bsize×16×38×38的map,注意通道數量是16,在38*38的空間位置上,每個空間位置對應16個數,而這16個數就是對4個priorbox也就是16個點的回歸,但是麻煩的是priorbox是被拉成了1維向量(不考慮variance的話),暫時不考慮bsize的話,那如何將16×38×38的blob拉成與priorbox對應的1維向量呢?直接flatten的話將按照whc從低到高的順序flatten,這是不行的,需要按照cwh的順序flatten才能與priorbox的存儲方式對應得上,於是ssd定義了permutelayer將blob的nchw順序改為nhwc,然后進行flatten即可。這里需要注意flatten的用法,文件指定從1號axis開始flatten,也就是從permute過后hwc維flatten成1維,整體flatten過后的維度為n×(h*w*c)在conv4也就是bsize×23104,四維blob變成了2維,如果對flatten有疑惑可直接查看源碼。各層的二維blob再繼續concat,第1維是bsize,那么肯定在第2維上進行concat,所以mbox_loc和mbox_conf層拼接的axis都設為1。所以mbox_loc層blob的維度最終為bsize×27130。
mbox_conf層的維度與mbox_loc層維度的推算類似,3*3的卷積在fmap上卷積得到bsize×84×38×38的map,其中84=4×21,4是每個位置4個不同的priorbox,21代表了卷積網絡預測出該priorbox屬於21類的概率。后續計算方式參考mbox_loc即可。

