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
即可
。