模型结构
Prior box默认框的产生
Prior box 的大小尺寸计算
输出层通道num_output计算。
Priorbox的使用
Permute,Flatten And Concat Layers
matching strategy:选择一系列default boxes
hard negative mining
data augmentation
技巧对比效果结论
training objective:多任务损失函数
论文中(下图)描述为conv4_3,conv7(原先的FC7),conv8_2,conv9_2,conv10_2,以及 pool11,这些 layer 合并起来predict location、confidence。
实际caffe中的ssd 300*300使用conv4_3、fc7、conv6_2、conv7_2、conv8_2、conv9_2进行预测位置、置信度以及priorbox。
对于每一层的一个特征图(m*n尺寸),每个cell对应k个prior box,则该层会有m*n*k个prior box。
SSD默认框从6层卷积层输出的特征图中产生,分别为conv4_3、fc7、conv6_2、conv7_2、conv8_2、conv9_2。这6个特征层产生的特征图的大小分别为38*38、19*19、10*10、5*5、3*3、1*1。每个n*n大小的特征图中有n*n个中心点,每个中心点产生k个默认框,六层中每层的每个中心点产生的k分别为4、6、6、6、4、4。
上图中最后每个类会得到(38*38*4 + 19*19*6 + 10*10*6 + 5*5*6 + 3*3*4 + 1*1*4)= 8732个prior box。
每个尺度的特征图像使用哪些大小和宽高比的默认包围盒。SSD按照如下规则生成prior box:
以feature map上每个点的中点为中心(offset=0.5),生成一些列同心的prior box(然后中心点的坐标会乘以step,相当于从feature map位置映射回原图位置)
在prototxt设置一个aspect ratio,会生成2个长方形,长宽为: 和
其中feature map对应prior box的min_size和max_size由以下公式决定,公式中m是使用feature map的数量(SSD 300中m=6):
第一层feature map对应的min_size=S1,max_size=S2;第二层min_size=S2,max_size=S3;其他类推。
对ssd产生的默认框的大小计算首先要计算参数min_sizes和max_sizes,这些参数具体在ssd_pascal.py中有计算方法。代码如下:
#参数生成先验。 #输入图像的最小尺寸 min_dim = 300 #######维度 # conv4_3 ==> 38 x 38 # fc7 ==> 19 x 19 # conv6_2 ==> 10 x 10 # conv7_2 ==> 5 x 5 # conv8_2 ==> 3 x 3 # conv9_2 ==> 1 x 1 mbox_source_layers = ['conv4_3', 'fc7', 'conv6_2', 'conv7_2', 'conv8_2', 'conv9_2'] #####prior_box来源层,可以更改。 # in percent % ####这里即是论文中所说的Smin=0.2,Smax=0.9的初始值,经过下面的运算即可得到min_sizes,max_sizes。 min_ratio = 20 max_ratio = 90 ####取一个间距步长,即在下面for循环给ratio取值时起一个间距作用。可以用一个具体的数值代替,这里等于17。 ####math.floor()函数表示:求一个最接近它的整数,它的值小于或等于这个浮点数。 step = int(math.floor((max_ratio - min_ratio) / (len(mbox_source_layers) - 2))) min_sizes = [] ###经过以下运算得到min_sizes和max_sizes。 max_sizes = [] ####从min_ratio至max_ratio+1每隔step=17取一个值赋值给ratio。注意xrange函数的作用。 for ratio in xrange(min_ratio, max_ratio + 1, step): #20 37 54 71 88 min_sizes.append(min_dim * ratio / 100.) #[60.0,111.0,162.0,213.0,264.0] max_sizes.append(min_dim * (ratio + step) / 100.) min_sizes = [min_dim * 10 / 100.] + min_sizes #[30.0,60.0,111.0,162.0,213.0,264.0] 前面拼接 max_sizes = [min_dim * 20 / 100.] + max_sizes ###这一步要仔细理解,即计算卷积层产生的prior_box距离原图的步长,先验框中心点的坐标会乘以step,相当于从feature map位置映射回原图位置,比如conv4_3输出特征图大小为38*38,而输入的图片为300*300,所以38*8约等于300,所以映射步长为8。这是针对300*300的训练图片。 steps = [8, 16, 32, 64, 100, 300] #######这里指的是横纵比,六种尺度对应六个产生prior_box的卷积层。具体可查看生成的train.prototxt文件一一对应每层的aspect_ratio参数,此参数在caffe.proto中有定义,关于aspect_ratios如何把其内容传递给了aspect_ratio,在model_libs.py文件中有详细定义。 aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2], [2]]
|
在原文中,Smin=0.2,Smax=0.9,同理依次可以计算出如下表结果:
再用不同aspect ratio,用ar来表示:。因此,默认包围盒的宽度和高度计算公式为:
对于aspect ratio为1时,本文还增加了一个default box(默认产生一大一小两个正方形默认框),所以最终在每个feature map location上,有6个default boxes。
其中SSD 300在conv4_3生成prior box的conv4_3_norm_priorbox层prototxt定义如下:
layer {
name: "conv4_3_norm_mbox_priorbox"
type: "PriorBox"
bottom: "conv4_3_norm"
bottom: "data"
top: "conv4_3_norm_mbox_priorbox"
prior_box_param {
min_size: 30.0 #根据公式计算
max_size: 60.0
aspect_ratio: 2 #包含 1、1、2、1/2四种宽高比的defaultbox(两个正方形,两个长方形)
flip: true #注意如果没有flip参数,aspect_ratio=2只能产生一个纵横比为1:2的默认框
clip: false
variance: 0.1
variance: 0.1
variance: 0.2
variance: 0.2
step: 8 #该层特征图尺寸为38*38,38*8约等于300,用于尺度映射
offset: 0.5
}
}
layer {
name: "fc7_mbox_priorbox"
type: "PriorBox"
bottom: "fc7"
bottom: "data"
top: "fc7_mbox_priorbox"
prior_box_param {
min_size: 60.0 #根据公式计算
max_size: 111.0
aspect_ratio: 2 #包含 1、1、2、1/2、3、1/3六种宽高比的defaultbox
aspect_ratio: 3
flip: true
clip: false
variance: 0.1
variance: 0.1
variance: 0.2
variance: 0.2
step: 16 #该层特征图尺寸为19*19,19*16约等于300,用于尺度映射
}
}
这种默认包围盒在不同的特征层有不同的尺度大小,在同一个特征层又有不同的宽高比,因此可以覆盖输入图像中各种目标大小和宽高比。
如上代码再结合prior_box_layer.cpp产生先验框。
先验框与ground truth框的匹配通过函数CHECK_GT()函数实现,具体在bbox_util.cpp脚本中实现
同时每个prior box要预测c类物体(即c个置信度),每个预测框需要4个offeset参数,则会产生(c+4)*k*m*n个输出结果,因此就需要(c+4)*k个卷积核(其中回归框预测层4*k个,置信度层c*k个)。
layer {
name: "conv4_3_norm_mbox_loc"
type: "Convolution"
bottom: "conv4_3_norm"
top: "conv4_3_norm_mbox_loc"
...
convolution_param {
num_output: 16 #4*4 4个位置参数*4个priorbox
pad: 1
kernel_size: 3
stride: 1
weight_filler {
type: "xavier"
}
}
}
layer {
name: "conv4_3_norm_mbox_conf"
type: "Convolution"
bottom: "conv4_3_norm"
top: "conv4_3_norm_mbox_conf"
...
convolution_param {
num_output: 84 #21*4 21个类*4个priorbox
pad: 1
kernel_size: 3
stride: 1
...
}
}
接下来分析prior box如何使用。这里以conv4_3为例进行分析。
从图可以看到,在conv4_3 feature map网络pipeline分为了3条线路:
还有一个细节就是上面prototxt中的4个variance,这实际上是一种bounding regression中的权重。在图线路(2)中,网络输出[dxmin,dymin,dxmax,dymax],即对应下面代码中bbox;然后利用如下方法进行针对prior box的位置回归:
decode_bbox->set_xmin(prior_bbox.xmin() + prior_variance[0] * bbox.xmin() * prior_width);
decode_bbox->set_ymin(prior_bbox.ymin() + prior_variance[1] * bbox.ymin() * prior_height);
decode_bbox->set_xmax(prior_bbox.xmax() + prior_variance[2] * bbox.xmax() * prior_width);
decode_bbox->set_ymax(prior_bbox.ymax() + prior_variance[3] * bbox.ymax() * prior_height);
上述代码可以在SSD box_utils.cpp的void DecodeBBox()函数见到。
layer {
name: "conv4_3_norm_mbox_conf_perm"
type: "Permute"
bottom: "conv4_3_norm_mbox_conf"
top: "conv4_3_norm_mbox_conf_perm"
permute_param {
order: 0
order: 2
order: 3
order: 1
}
}
Permute是SSD中自带的层,上面conv4_3_norm_mbox_conf_perm的的定义。Permute相当于交换caffe blob中的数据维度。在正常情况下caffe blob的顺序为:
bottom blob = [batch_num, channel, height, width]
经过conv4_3_norm_mbox_conf_perm后的caffe blob为:
top blob = [batch_num, height, width, channel]
对于conv4_3 feature map,conv4_3_norm_priorbox(priorbox层)设置了每个点共有4个prior box。由于SSD 300共有21个分类,所以conv4_3_norm_mbox_conf的channel值为num_priorbox * num_class = 4 * 21 = 84;而每个prior box都要回归出4个位置变换量,所以conv4_3_norm_mbox_loc的caffe blob channel值为4 * 4 = 16。
fc7每个点有6个prior box,其他feature map同理。
经过一系列图7展示的caffe blob shape变化后,最后拼接成mbox_conf和mbox_loc。而mbox_conf后接reshape,再进行softmax(为何在softmax前进行reshape,Faster RCNN有提及)。
最后这些值输出detection_out_layer,获得检测结果.
在训练时,groundtruth boxes 与 default boxes(就是prior boxes) 按照如下方式进行配对:
首先,寻找与每一个ground truth box有最大的jaccard overlap的default box,这样就能保证每一个groundtruth box与唯一的一个default box对应起来。
SSD之后又将剩余还没有配对的default box与任意一个groundtruth box尝试配对,只要两者之间的jaccard overlap大于阈值,就认为match(SSD 300 阈值为0.5,另外所谓的jaccard overlap就是IoU,如图8)。
显然配对到GT的default box就是positive,没有配对到GT的default box就是negative。
J为0说明两个框一点都不重合,为1说明完全重合。
在生成一系列的predictions之后,会产生很多个符合ground truth box的predictions boxes,但同时,不符合ground truth boxes也很多,而且这个negative boxes,远多于positive boxes。这会造成negative boxes、positive boxes之间的不均衡,训练时难以收敛。
将prior box和grount truth box按照IOU(JaccardOverlap)进行匹配,匹配成功则这个prior box就是positive example(正样本),如果匹配不上,就是negative example(负样本),显然这样产生的负样本的数量要远远多于正样本。这里按confidence loss进行排序,选择最高的num_sel个prior box序号集合 D。那么如果Match成功后的正样本序号集合P。那么最后正样本集为 P - Dcap{P},负样本集为 D - Dcap{P}。同时可以通过规范num_sel的数量(是正样本数量的三倍)来控制使得最后正、负样本的比例在 1:3 左右。本文通过实验发现,这样的比例可以更快的优化,训练也更稳定。
1)正样本获得
我们已经在图上画出了prior box,同时也有了ground truth,那么下一步就是将prior box匹配到ground truth上,这是在 src/caffe/utlis/bbox_util.cpp的 FindMatches以及子函数MatchBBox函数里完成的。值得注意的是先是从groudtruth box出发给每个groudtruth box找到了最匹配的prior box放入候选正样本集,然后再从prior box出发为prior box集中寻找与groundtruth box满足IOU>0.5的一个IOU最大的prior box(如果有的话)放入候选正样本集,这样显然就增大了候选正样本集的数量。
2)负样本获得
本文采取,先将每一个物体位置上对应 predictions(prior boxes)loss 进行排序。 实际代码中有三种负样本挖掘方式:
如果选择HARD_EXAMPLE方式(源于论文Training Region-based Object Detectors with Online Hard Example Mining),则默认M = 64,由于无法控制正样本数量,这种方式就有点类似于分类、回归按比重不同交替训练了。
如果选择MAX_NEGATIVE方式,则M = P*neg_pos_ratio,这里当neg_pos_ratio = 3的时候,就是论文中的正负样本比例1:3了。
enum MultiBoxLossParameter_MiningType {
MultiBoxLossParameter_MiningType_NONE = 0,
MultiBoxLossParameter_MiningType_MAX_NEGATIVE = 1,
MultiBoxLossParameter_MiningType_HARD_EXAMPLE = 2};
每一张训练图像,随机的进行如下几种选择:
实验结果证明,对数据集进行扩大能够高精度;使用更多的特征图像能够提高精度;使用更多的包围盒能够提高精度。
SSD中的数据增强的顺序是:
batch_sampler {
sampler {
min_scale: 0.3 #scale是patch随机框和原图的面积比
max_scale: 1
min_aspect_ratio: 0.5 #长宽比
max_aspect_ratio: 2
}
sample_constraint {
min_jaccard_overlap: 0.9 ##随机框和原ground truth的jaccard overlap
}
max_sample: 1
max_trials: 50 #迭代次数
}
SSD训练的目标函数(training objective)源自于MultiBox的目标函数,但是本文将其拓展,使其可以处理多个目标类别。指示变量表示第i个默认包围盒和第个j真实包围盒对于目标类型是否匹配。
根据上面的匹配策略,一定有 ∑i≥1,意味着对于第j个ground truth box,有可能有多个default box i与其相匹配。
在训练时,需要将真实的目标包围盒与默认包围盒匹配起来,即每个默认包围盒是否真的代表了一个目标。损失函数定义为:
其中N为与真实包围盒匹配的默认包围盒的数量。是一个人工设定的参数,用于平衡定位损失和置信度损失,默认为1。如果N=0,损失函数被设置为0。
置信度损失confidence loss(Lconf)是对所有类的置信度的softmax损失,输入为每一类的置信度c,定义为:
定位损失函数Lloc是一个光滑的损失函数(fast rcnn中Smooth L1 loss),定义预测的包围盒和真实的包围盒之间(中心坐标、w、h)的误差。假设默认包围盒的中心点坐标为,宽度和高度分别为和。定位损失函数定义为: