这几天准备对物体检测Mobilenet-SSD做一些了解以及在自己的数据集上进行训练,MobileNet-SSD网络的一部分是轻量级网络Mobilenet的引入,这一部分在网络中加入了可分离卷积部分,大大减小了模型的计算量,同时也缩小了模型大小,这一部分在后续的博客中会进行完善,在Mobilenet-SSD中,我们可以将其Mobilenet理解为轻量级的特征提取网络部分;另外还有重要的一部分----SSD框架在这里进行详细的说明:SSD的主要贡献,一方面在于从多个不同尺度的特征图获取特征信息进而预测目标的位置和类别,使网络同时对输入图片上的大小物体都比较敏感;另一方面在于其训练技巧值得借鉴,这在论文中有一定的阐述,更加详细的训练技巧可以结合作者开源的代码学习。
这里给出SSD论文下载地址:https://arxiv.org/pdf/1512.02325.pdf
下图给出SSD的整体框架图:
SSD中是用预训练好的分类网络作为特征提取器(论文里使用的是VGG16)
VGG16原模型如下图所示:
由于SSD论文里输入是 300×300300×300,我们重写一下VGG16模型的各层输出大小:
感受野
卷积是对特定小区域的特征提取,比如一张 300×300300×300 的原图经过一定卷积操作之后得到 8×88×8 的特征图,特征图上的每个“像素点”其实对应原图的一个“感受野”,在这里一个“感受野”的大小为 3008×30083008×3008 也即 37.5×37.537.5×37.5 (实际上不可能有.5.5,是3737还是3838要看卷积过程是否padding)。
换句话说,特征图左上角的一个感受野其实是原图左上角一个 37.5×37.537.5×37.5 大小区域的一个特征抽取(或者说是这个区域的一个抽象化,融合了这个区域的信息)。
缺省框
论文里称为default box,而源码里称为prior box.
在SSD中,为每个向Detection贡献特征的特征图上的每个感受野分配若干不同大小和长宽比(aspect ratio)的绑定框(Bounding Box),比如在作者开源的源码中,这些绑定框是这样生成的——
参考SSD - PriorBoxLayer源码 | github
(其中,Dmin、xoffset、yoffset、AR、DmaxDmin、xoffset、yoffset、AR、Dmax 都是用户指定的超参数,而且 DmaxDmax 是可选的)
事实上网络输出的预测框位置参数就是这每一个缺省框的中心点偏移量和长、宽偏移量(每个框共计4个参数)!
对于一个方框其实有两种不同的表示方法,都是四元组,即(中心点x坐标,中心点y坐标,长度,宽度)或(左上角x坐标,左下角x坐标,右下角x坐标,右下角y坐标)
DminDmin 和 DmaxDmax 的设置建议
论文中也给出了超参数 DminDmin 和 DmaxDmax 的设置建议——
scale=BoxSizeImageSizescale=BoxSizeImageSize
写成数学公式为:
sk=smin+smax−sminm−1(k−1),k∈[1,m]sk=smin+smax−sminm−1(k−1),k∈[1,m]
其中,
sksk,是第k个特征图的 minsizeminsize 参数;
sminsmin,是设计好的最小归一化尺寸,即前述 scaleminscalemin;
smaxsmax,是设计好的最大归一化尺寸,即前述 scalemaxscalemax;
mm,是特征图的总数;
注意:上述“特征图”指的是对Detection有贡献的特征图,序号从浅层到深层递增
回过头再看看网络结构,注意从不同尺度融合特征信息的那一部分,用的都是一个 3×33×3 的卷积核,输出通道要么是 num_class + 4 的四倍要么是六倍,这里 num_class 就是最终分类的数量,数值 44 其实指的是预测框的位置参数的数量,而四倍或六倍指的是特征图上每个感受野对应的绑定框数量。
按照论文,浅层的分辨率比较高,所以使用了 AR={1,2,3}AR={1,2,3} 的分辨率组合,对于 ARi=2ARi=2 和 ARi=3ARi=3 分别会生成两个绑定框,而 ARi=1ARi=1 除了本身之外还会额外产生一个归一化尺寸为 s′k=sksk+1−−−−−√sk′=sksk+1 的绑定框;而浅层分辨率较低,2或3的长宽比其实没太大区别或必要,所以只取了 AR={1,2}AR={1,2}。
源码prototxt参数示例:
layer {
name: "conv11_mbox_priorbox"
type: "PriorBox"
bottom: "conv11" # bottom[0]:特征向量
bottom: "data" # bottom[1]:原始输入(主要提供宽、高参数)
top: "conv11_mbox_priorbox"
prior_box_param {
min_size: 30.0 # 最小的基本Box大小(可根据论文计算)
aspect_ratio: 2.0 # 所要用的长宽比(除了1.0)
flip: true # 是否以0.5的概率翻转
clip: false # 是否截断Box(若截断表示不允许Box超出图片范围)
variance: 0.1 # xmin的偏差因子
variance: 0.1 # ymin的偏差因子
variance: 0.2 # xmax的偏差因子
variance: 0.2 # ymax的偏差因子
offset: 0.5 # Box中心相对于感受野中心的x、y偏移量
}
}
框配对(Matching)
前边讲到了感受野和缺省框,我们现在看看这些缺省框是如何跟实际标注的真实框(Ground Truth, GT)配对起来的。
介绍一个重叠率计算公式,jaccard重叠率:
J(A,B)=|A∩B||A∪B|=|A∩B||A|+|B|−|A∩B|J(A,B)=|A∩B||A∪B|=|A∩B||A|+|B|−|A∩B|
对于每个GT,
损失函数(MultiBoxLoss)
损失函数由位置损失 LlocLloc 和分类损失 LconfLconf 加权求和获得;
完整公式如下:
L(x,c,l,g)=1N(Lconf(x,c)+αLloc(x,l,g))L(x,c,l,g)=1N(Lconf(x,c)+αLloc(x,l,g))
其中,
NN,是匹配正样本的总量(如果N=0,则令L=0);
xx、cc,分别是分类的指示量和置信度;
ll、gg,分别是预测框和GT框;
αα,是位置损失的权重。
位置损失是预测框l和真实框g之间的Smooth L1损失,
Lloc=∑i∈PosN∑m∈cx,cy,w,hxkijsmoothL1(lmi−g^mj)Lloc=∑i∈PosN∑m∈cx,cy,w,hxijksmoothL1(lim−g^jm)
g^cxj=gcxj−dcxidwi,g^cyj=gcyj−dcyidhi,g^wj=log(gwjdwi),g^hj=log(ghjdhi)g^jcx=gjcx−dicxdiw,g^jcy=gjcy−dicydih,g^jw=log(gjwdiw),g^jh=log(gjhdih)
其中,
xkijxijk,是指示量,当第i个匹配框和分类p的第j个真实框配对时值为1,否则为0;
cxcx、cycy、ww、hh,分别是框的中心点x坐标、中心点y坐标,宽度、高度;
dd,是绑定框(网络本身预设的绑定框);
ll,是预测框(网络输出的加上预测偏移量的框);
gg,是GT框(数据集标注的真实框)。
分类损失是分类置信度之间的softmax损失,
Lconf(x,c)=−∑i∈PosNxpijlog(c^pi)−∑i∈Neglog(c^0i)Lconf(x,c)=−∑i∈PosNxijplog(c^ip)−∑i∈Neglog(c^i0)
c^pi=exp(cpi)∑pexp(cpi)c^ip=exp(cip)∑pexp(cip)
源码prototxt参数示例:
layer {
name: "mbox_loss"
type: "MultiBoxLoss"
bottom: "mbox_loc" # 用于预测位置的特征向量
bottom: "mbox_conf" # 用于预测分类的特征向量
bottom: "mbox_priorbox" # 若干PriorBox的输出连接
bottom: "label" # 训练用的标签
top: "mbox_loss"
include {
phase: TRAIN
}
propagate_down: true # bottom[0] : mbox_loc,需要训练(反向传播)
propagate_down: true # bottom[1] : mbox_conf,需要训练
propagate_down: false # bottom[2] : mbox_priorbox,不需要训练
propagate_down: false # bottom[3] : label,不需要训练
# 损失计算参数组
loss_param {
normalization: VALID # 损失的归一化方式
# FULL:除以batch_size
# VALID:除以有效的数量(排除ignore_label参数指定的标签)
# NONE
}
# multibox损失参数组
multibox_loss_param {
loc_loss_type: SMOOTH_L1 # 位置预测的损失函数
conf_loss_type: SOFTMAX # 分类预测的损失函数
loc_weight: 1.0 # loc_loss的权重,论文中的alpha
num_classes: 12 # 输出类别
share_location: true # 是否让所有的预测框共享参数
match_type: PER_PREDICTION #
overlap_threshold: 0.5 # 重叠阈值(训练时超过该阈值的Box作为正样本)
use_prior_for_matching: true # 是否使用先验框(即前边是否有PriorBox)
background_label_id: 0 # 背景(background)标签的id值(与labelmap.prototxt文件匹配)
use_difficult_gt: true # 是否使用difficult的Ground Truth(?)
neg_pos_ratio: 3.0 # 负样本:正样本 的比例
neg_overlap: 0.5 # 负样本阈值(低于该阈值的Box作为负样本)
code_type: CENTER_SIZE # bounding box的编码方式
ignore_cross_boundary_bbox: false #
mining_type: MAX_NEGATIVE # 挖掘类型
# NONE:什么都不用,会发生正负样本步均衡
# MAX_NEGATIVE:为负样本排序,选择分类得分最高的一批作为训练的负样本
# HARD_EXAMPLE:选择基于“在线硬示例挖掘的基于训练区域的对象探测器”的硬实例(?)
}
}
数据增强(Data Augmentation)
目标检测任务和图片分类任务不同,
基本的数据增强,每张图片在输入会等概率地选取以下一种变换:
随机裁剪是有条件的,限制最低归一化尺寸(论文中为0.1),以及长宽比(论文中为[1/2,2][1/2,2]);
实际操作中是先生成一系列满足条件的框,在从中挑选若干框来裁剪图像输入训练。
仔细思考一下会发现,上述的数据增强方式只能对某个目标进行放大(zoom in)操作,这会导致小目标数据集缺失。
于是论文中在执行上述数据增强前又增加了一步缩小(zoom out)的增强措施——
据论文介绍,该额外操作为mAP提升了两到三个百分点。
对于画布,从源码data_transform.cpp/ExpandImage()函数(第489行) | github上看,是用空格符初始化了一个字符串缓冲区来作为画布,这似乎意味着是用RGB(32,32,32)的固定颜色填充了画布(空格符的assic码为32);
这种做法有些残暴,或许用原图的平均值、或者一个随机值来作固定颜色填充效果会更好一些;
甚至,用实际图片作为背景图可能会取得更好的效果,具体结论还有待实验。
源码prototxt参数示例:
layer {
name: "data"
type: "AnnotatedData"
top: "data"
top: "label"
include {
phase: TRAIN
}
# 图像转换参数组
transform_param {
scale: 0.007843 # 归一化,1/127.5
mirror: true # 0.5的概率镜像翻转
mean_value: 127.5 # 去均值(R通道)
mean_value: 127.5 # 去均值(G通道)
mean_value: 127.5 # 去均值(B通道)
# 缩放参数组
resize_param {
prob: 1.0 # 缩放操作的概率
resize_mode: WARP # 缩放模式
# WARP:放大或缩小以适应(width, height)
# FIT_SMALL_SIZE:
# FIT_LARGE_SIZE_AND_PAD:
height: 300 # 缩放后的高
width: 300 # 缩放后的宽
# 插值模式同opencv
interp_mode: LINEAR # 线性
interp_mode: AREA # 像素区域重采样
interp_mode: NEAREST # 最近邻
interp_mode: CUBIC # 三次样条
interp_mode: LANCZOS4 # Lanczos
}
emit_constraint {
emit_type: CENTER
}
# 色彩扭曲参数组
distort_param {
brightness_prob: 0.5
brightness_delta: 32.0
contrast_prob: 0.5
contrast_lower: 0.5
contrast_upper: 1.5
hue_prob: 0.5
hue_delta: 18.0
saturation_prob: 0.5
saturation_lower: 0.5
saturation_upper: 1.5
random_order_prob: 0.0
}
# 图像扩展参数组(zoom-out)
expand_param {
prob: 0.5 # 概率
max_expand_ratio: 4.0 # 扩大倍数(4x4)
}
}
# 数据源参数组
data_param {
source: "trainval_lmdb/" # 源文件
batch_size: 24
backend: LMDB # 数据源类型
}
# AnnotatedData参数组
annotated_data_param {
# 采样器1:原图
batch_sampler {
max_sample: 1
max_trials: 1
}
# 采样器2:随机抠图(overlap=0.1)
batch_sampler {
sampler {
min_scale: 0.15 # 最小尺寸(原图的0.15倍)
max_scale: 1.0 # 最大尺寸
min_aspect_ratio: 0.5 # 最小长宽比
max_aspect_ratio: 2.0 # 最大长宽比
}
sample_constraint {
min_jaccard_overlap: 0.1 # 最小JACCARD_OVERLAP(重叠率)
}
max_sample: 1 # 最大采样数量
max_trials: 50 # 最大尝试数量(产生50个随机框,然后再筛选出1个输出)
}
# [略]采样器3:随机抠图(overlap=0.3)
# [略]采样器4:随机抠图(overlap=0.5)
# [略]采样器5:随机抠图(overlap=0.7)
# [略]采样器6:随机抠图(overlap=0.9)
# [略]采样器7:随机抠图(overlap=1.0)
label_map_file: "labelmap.prototxt" # 标签映射文件
}
}