- -------------Z F J
1 直观感受
SSD是端对端的一步走的运行方式,这个方式类似于YOLO,而不同于Faster RCNN,所以速度上是类似于YOLO的速度,而高于Faster RCNN的速度,这个比较高的速度,实时性很强,可以应用到很多的场合。精度方面,SSD是采用不同的特征层进行检测(多尺度),因此就可以容纳很多不同现状、不同大小的问题的轮廓,所以在小物体的检查上面也是比YOLO的效果优异很多。整体精度已经是接近甚至超过Faster RCNN。所以是一个速度和精度的结合。
2 网络构成
2.1 整体结构
SSD(论文链接:https://arxiv.org/abs/1512.02325)的整体网络结构如图1所示,SSD是以高质量的图像分类架构为基础(例如:VGG、googlenet、alexnet等,都是在分类层之前阶段,主要是使用其特征提取功能),而搭建起来的。这里模型的作者选择的基础网络是VGG16,结构如图2,在这个基础上,将fc6和fc7转换为卷积层,将pool5从2×2-s2更改为3×3-s1,但是对pool的改变会引起感受野的改变,为了不让感受野发生变化,就使用了空洞卷积的操作(atrous algorithm)。另外删除了VGG的所有的dropout层和fc8层。在上面的网络后面,加上conv8_2,conv9_2,conv10_2,pool11这些层作为后续检测的提取特征层(另2个提取的特征层是conv4_3,conv7)。上述的外加的4个特征层,对应这结构图中的后面4层。实际上图中是省略了conv8_1等的层,这些层是1*1卷积产生的,用来降维。
注:空洞卷积(atrous algorithm)可以使得感受野变大。atrous相关博客:
https://blog.csdn.net/weixin_41274801/article/details/82960771
2.2 检测架构
每个feature map(特征层)会划分为M*N的原始框(feature maps),每个小格对应有不同长宽比的默认框(default box),每个特征层的每个原始框产生6个默认框(第一个特征层除外,是产生3个)。这样,不同的特征层代表了不同的尺度上的检测,加上不同的长宽比(aspect ratio),可以代表很多不同的形状和不同大小的物体的外接矩形。因此对大小物体的检测都会有比较好的效果,这个是相对于YOLO的优势之一。如图3所示,狗和猫分别对应于不同层的默认框,默认框加上4个偏移值(offsets),可对应到所预测的结果框。
2.2.1 默认框的生成
以feature map上每个点的中点为中心(offset=0.5),生成一系列同心的默认框(然后中心点的坐标会乘以step,相当于从feature map位置映射回原图位置)。在每个特征图上,default box的大小(scale)计算如下:
s就是scale值,其中,最底层特征图的 scale 值为 Smin=0.2,最高层的为Smax=0.95。可得到各层对应的默认框的大小。
default box的aspect ratios 有:{1, 2, 3,1/2,1/3},对于 aspect ratio = 1,额外增加一个default box,该box的尺度为 。每一个default box,中心点就是原始框的中心,宽度、高度计算如下:
在特征图上不同的尺度、不同的长宽比,包含了物体的不同尺寸、形状。如图3,狗的ground truth box与4*4feature map 中的红色 box 吻合,猫的ground truth box于8*8的feature map中的蓝色框吻合,都作为正样本,然后其余的 boxes 都作为负样本。
2.2.2 默认框选取
有了很多默认框(用于作为predictions boxes)之后,需要从若干个默认框选出正样本默认框(positive boxes)和负样本默认框(negative boxes)进行训练。选取正样本主要是通过默认框和ground truth box的重合程度(IOU)来匹配。匹配原则主要有两点。首先,对于图片中每个ground truth,找到与其IOU最大的默认框,该默认框与其匹配,这样,可以保证每个ground truth一定与某个默认框匹配。称与ground truth匹配的默认框为正样本。第二个原则是:对于剩余的未匹配默认框,若某个ground truth的 大于某个阈值(一般是0.5),那么该默认框也与这个ground truth进行匹配,作为正样本。第二个原则一定在第一个原则之后进行,因为要考虑到以下的这种情况,如果某个ground truth所对应最大IOU小于阈值,并且所匹配的默认框却与另外一个ground truth的 大于阈值,那么该默认框应该匹配谁,答案只能是前者,首先要确保某个ground truth一定有一个默认框与之匹配。不过,这种情况我觉得基本上是不存在的。由于默认框很多,某个ground truth的最大IOU肯定大于阈值,这两个原则按顺序一起使用,是作为一个保障。
上述产生了positive boxes,原则上positive boxes之外的默认框都是negative boxes,但是实际上negative boxes的数量会远多于positive boxes,导致两者之间的不均衡。因此SSD的作者采用,先将每一个物体位置上对应 predictions(default boxes)是 negative 的 boxes 进行排序,按照 default boxes 的 confidence 的大小。 选择最高的几个,保证最后 negatives、positives 的比例在3:1。
2.2.3 检测的卷积
网络框架中选取出来进行检查的不同尺度的特征层,是通过一组卷积滤波器来产生预测集合。这个预测需要产生类别的分数(c分类就有c个值)和相对于默认框的偏移值offsets(有4个值)。具体来说,例如p通道的m×n特征层,需要若干个3x3xP的滤波器进行运算,每个滤波器的运算结果就是上述c+4个值中的其中一个。
定义特征层的feature map cell固定有K个不同长宽比的默认框,而每个默认框对于的分类和偏移分量需要c+4个值来表示,那对于一个feature map cell就需要有k*(c+4)个滤波器了。而一个特征层需要s个滤波器,同时输出s个结果,s=N*M*k*(c+4)。输出的结果该预测本质来说也是一次卷积操作,输出的格式是一个1x1xS的层。
3 训练方式
3.1 loss定义
SSD的loss函数是源于MultiBox的目标函数,而SSD在其基础上进行了改造,主要是加入了多分类的目标,同时对矩形框的回归进行损失函数计算的改变。另外SSD的损失函数类似于Fast RCNN中的损失函数,总的损失函数是localization loss (loc) 和 confidence loss (conf) 的加权和,具体如下:
其中:
- 用 Xpij(即式中的x)表示第 i个 default box与类别p的第j个ground truth box 相匹配,否则若不匹配的话,则Xpij 0 = 0 。
- N是与 ground truth box 相匹配的 default boxes 个数
- confidence loss(conf) 是 Softmax Loss,输入为每一类的置信度 c
- 权重项 α,通常设置为 1
- 位置回归则是采用 Smooth L1 loss(是MultiBox的损失函数L2-Norm的简化,虽然没有了“像素级准确”,但效率上提高了很多),L1目标函数为:
3.2 数据增强
数据增强可以使得训练出来的模型更加具有鲁棒性,SSD采用的数据增强策略是随机采用以下的方式进行采样:
-
使用整个原始输入图像
-
采样一个片段,使对象最小的jaccard重叠为0.1,0.3,0.5,0.7或0.9。
-
随机采样一个片段
每个采样片段的大小为原始图像大小的[0.1,1],横宽比在1/2和2之间。如果真实标签框中心在采样片段内,则保留重叠部分。在上述采样步骤之后,将每个采样片大小调整为固定大小,并以0.5的概率水平翻转。其中这种采样具有把图片特征放大的作用,有利于小物体的检测。
3.3训练过程
3.3.1 初始化
网络中的基础网络,使用训练好的VGG16的参数进行初始化,其余的卷积部分采用“xavier”方法进行初始化。另外,对于每个原始框的默认框(前面部分也稍微提过),具体来说,由于conv4_3的大小较大(38×38),因此我们只在其上放置3个默认框:一个0.1比例的框和另外纵横比为1/2和2的框。对于所有其他层,我们设置6个默认框(前面提到过),而且conv4_3使用的L2正则化技术,将特征图中每个位置处的特征范数缩放为20,并在反向传播期间学习比例。
3.3.2 训练策略
优化器:SGD
学习率:0.001
momentum:0.9
weight decay:0.0005
batch:32
按此策略迭代4W次,再把学习率改为0.0001,继续迭代,2W次。
3.3.3 训练结果
结果如表1,SSD300模型比Fast R-CNN更准确。当以更大的500×500输入图像训练SSD,结果更准确,超过了Faster R-CNN 1.9% mAP。
4预测与模型分析
4.1 预测方式
预测方式就是指最终检测所用的方式,即SSD算法步骤,这个预测方式于上述的训练方式大部分是相同的,对预测的算法步骤总结如下(看完训练之后看这里,会感到很熟悉):
1.利用训练好的网络,已经预设好的函数和默认框等参数进行检测,输入300(或500)大小的待测图。
2.抽取Conv4_3、Conv7、Conv8_2、Conv9_2、Conv10_2、Conv11_2层的feature map,然后分别在这些feature map层上面的每一个点构造6个(首层为3个)不同尺度大小的默认框,然后分别进行检测和分类,生成多个默认框。
3. 将不同feature map获得的默认框(这里可以称作为检测框、先验框)结合起来,经过NMS(非极大值抑制)方法来抑制掉一部分重叠或者不正确的结果框(默认框加上偏移值),生成最终的结果集合(即检测结果)。
4.2 模型分析
这里的模型分析主要是总结ssd中哪操作是对模型产生积极影响的,具体如下:
数据增强:Fast和Faster R-CNN使用原始图像和水平翻转(0.5概率)图像训练。SSD使用更广泛的采样策略,类似于YOLO,但它使用了我们没有使用的光度失真。SSD另外指出,用这个抽样策略提高6.7%的mAP。
更多特征图:SSD比较使用conv4_3预测的模型和没有它的模型。论文指出可以看出,通过添加conv4_3进行预测,它有明显更好的结果(72.1%vs68.1%)。这也符合直觉,conv4_3可以捕获对象更好的细粒度,特别是细小的细节。
更多默认框形状:默认情况下,每个位置使用6个默认框。如果删除具有1/3和3宽高比的框,性能下降0.9%。通过进一步移除1/2和2纵横比的框,性能再下降2%。使用多种默认框形状似乎使网络预测任务更容易。
Atrous算法:SSD使用了VGG16的atrous版本,遵循DeepLabLargeFOV。如果我们使用完整的VGG16,保持pool5与2×2-s2,并且不从fc6和fc7的采集参数,添加conv5_3,结果稍差(0.7%),而速度减慢大约50%。
5架构实现
本人跑过SSD的tensorflow框架的代码,从代码资料查找、下载,到训练,再到测试和计算mAP,等过程。以下是一些记录:
训练的模型采用github上SSD的tensorflow版本最多star的一个https://github.com/balancap/SSD-Tensorflow,并参考其称作步骤。
5.1训练
数据来源是voc2007与voc2012,图5中的VOCdevkit文件夹,就是数据集解压好的文件夹了,其余的是训练结果的model和测试结果log。
制作tfrecords文件,运行以下脚本文件即可产生,这个文件需要新建在SSD-Tensorflow-master文件夹下面,另外DATASET_DIR代表数据集的地址,OUTPUT_DIR代表你存放tfrecords的地方。
#Directory where the original dataset is stored
DATASET_DIR=/media/zhangfangjian/data/SSDproject/VOCdevkit/VOC2007/
#Output directory where to store TFRecords files
OUTPUT_DIR=/media/zhangfangjian/data/SSDproject/train_tfrecords
python3 ./tf_convert_data.py \
--dataset_name=pascalvoc \
--dataset_dir=${DATASET_DIR} \
--output_name=voc_2007_train \
--output_dir=${OUTPUT_DIR}
train_ssd_network.py修改第154行的最大训练步数,将None改为比如50000,即训练5W步就会自动停止。制作训练的脚本文件,如下代码,也是新建在SSD-Tensorflow-master文件夹下面,注意,每一行的末尾的\的后面,是不能有空格号的,不然训练的时候会报错。运行一下代码,即可进行训练。
#!/bin/bash
DATASET_DIR=/media/zhangfangjian/data/SSDproject/train_tfrecords/ ###训练集转化成tfrecords存储的路径
TRAIN_DIR=/media/zhangfangjian/data/SSDproject/sgd_3pe34_ssd_peper_model/ ###存储训练结果的路径,包括checkpoint和event,自行指定
#CHECKPOINT_PATH=/media/zhangfangjian/data/SSDproject/SSD-Tensorflow-master/checkpoints/ssd_300_vgg.ckpt
CHECKPOINT_PATH=/media/zhangfangjian/data/SSDproject/SSD-Tensorflow-master/checkpoints/vgg_16.ckpt
python train_ssd_network.py \
--train_dir=${TRAIN_DIR} \
--dataset_dir=${DATASET_DIR} \
--dataset_name=pascalvoc_2007 \
--dataset_split_name=train \
--model_name=ssd_300_vgg \
--checkpoint_path=${CHECKPOINT_PATH} \
--checkpoint_model_scope=vgg_16 \
--checkpoint_exclude_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--trainable_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--save_summaries_secs=160 \
--save_interval_secs=1600 \
--weight_decay=0.0005 \
--optimizer=sgd \
--learning_rate=0.005 \
--learning_rate_decay_factor=0.9 \
--batch_size=16
下图是我训练的结果,loss值一直不太稳定(图6),后来查了Google查了很多,发现很多人有这个问题出现,也没有明确的解决方法。不过在该tensorflow模型的github的评论下面(所有评论都看了一遍),有人提到过可能是ssd_vgg_300.py文件中的batch_size参数有问题,我改成下面之后(图7),同时测试过很多不同的优化器、步长初始化参数、衰减系数等的组合,loss的效果改善很多(图8),不过这个结果也不太好,需要后续继续研究和改进。
5.2 跑demo
跑的demo在notebooks文件夹下,一般是用jupyter来做(如图9)。代码中的ckpt_filename是设置为要做测试的模型的绝对路径(相对路径也行),另外有些博客说过这两行需要注释掉,经过我的尝试,结论是不用注释,也不能注释掉。然后就运行这个jupyter,即可得出结果图(图10)。
5.3 测mAP
步骤是先生成tfrecords文件,然后运行sh文件。具体来说,利用上述训练中生成tfrecords文件的同样的方式(把sh代码中相应的train改为test),生成是要用到的文件。然后新建新的测试脚本(在同样的文件夹下),我的代码如下。
python3 ./eval_ssd_network.py \
--eval_dir=/media/zhangfangjian/data/SSDproject/ssd_eval_log/ \
--dataset_dir=/media/zhangfangjian/data/SSDproject/test_tfrecords/ \
--dataset_name=pascalvoc_2007 \
--dataset_split_name=test \
--model_name=ssd_300_vgg \
--checkpoint_path=/media/zhangfangjian/data/SSDproject/SSD-Tensorflow-master/checkpoints/adam_94_3pe34 \
--batch_size=1
另外需要找出pascalvoc_to_tfrecords.py ,然后更改文件的83行读取方式为’rb’。然后就运行测试的脚本文件。生成了结果文件之后,在相应的文件夹目录下,运行tensorboard,可以得到如图11的结果集合。
5.4 其他框架的SSD
SSD还在很多很多的框架上有实现,例如keras、caffe(作者框架)、Pytorch等等很多。例如我还看过一下keras的SSD模型,记录一下,这里记录的是github上面800+star的一个模型,训练的是kitti数据集,下图就是我下载并解压的数据集,具体操作如下链接。keras的框架训练,主要直接读取voc格式是数据集就可以,不需要像tensorflow那样制作中间的tfrecords文件。
https://blog.csdn.net/Jesse_Mx/article/details/65634482
https://github.com/pierluigiferrari/ssd_keras
https://blog.csdn.net/remanented/article/details/79943418
6训练其他数据
如果想用tensorflow的ssd跑自己的数据,那就要制作自己的数据集,做成类似于VOC格式的数据集,然后在做出tfrecords文件,给ssd训练。
6.1 制作voc数据集
通过labelImg制作每张图片的标签(或者你用现成的标好的图也行),制作2007VOC数据集,具体步骤可以在谷歌上找到很多,例如https://blog.csdn.net/gaohuazhao/article/details/60871886。做好的数据集如下图所示。各个文件夹的作用:Annotations文件夹下有保存每张图片的xml文件。ImageSets有四个xml文件,用来划分训练验证测试集。JPEGImages文件夹下保存的是图片数据。
附分割训练测试验证集的代码:
import os
import random
xmlfilepath=r'/media/comway/data/DialVOC/Annotations'
saveBasePath=r"/media/comway/data/DialVOC"
trainval_percent=0.9
train_percent=0.9
total_xml = os.listdir(xmlfilepath)
num=len(total_xml)
list=range(num)
tv=int(num*trainval_percent)
tr=int(tv*train_percent)
trainval= random.sample(list,tv)
train=random.sample(trainval,tr)
print("train and val size",tv)
print("traub suze",tr)
ftrainval = open(os.path.join(saveBasePath,'ImageSets/Main/trainval.txt'), 'w')
ftest = open(os.path.join(saveBasePath,'ImageSets/Main/test.txt'), 'w')
ftrain = open(os.path.join(saveBasePath,'ImageSets/Main/train.txt'), 'w')
fval = open(os.path.join(saveBasePath,'ImageSets/Main/val.txt'), 'w')
for i in list:
name=total_xml[i][:-4]+'\n'
if i in trainval:
ftrainval.write(name)
if i in train:
ftrain.write(name)
else:
fval.write(name)
else:
ftest.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest .close()
附将数据重命名为六位数据代码:
import os
class BatchRename():
'''
批量重命名文件夹中的图片文件
'''
def __init__(self):
#我的图片文件夹路径horse
self.path = '/media/comway/data/dialData'
def rename(self):
filelist = os.listdir(self.path)
total_num = len(filelist)
i = 1
n = 6
for item in filelist:
if item.endswith('.jpg'):
n = 6 - len(str(i))
src = os.path.join(os.path.abspath(self.path), item)
dst = os.path.join(os.path.abspath(self.path), str(0)*n + str(i) + '.jpg')
try:
os.rename(src, dst)
print 'converting %s to %s ...' % (src, dst)
i = i + 1
except:
continue
print 'total %d to rename & converted %d jpgs' % (total_num, i)
if __name__ == '__main__':
demo = BatchRename()
demo.rename()
6.2 细节修改
打开进入SSD-Tensorflow-master—>datasets—>pascalvoc_common.py 改代码,根据自己情况更改,有几类就改成几类。
VOC_LABELS = {
'none': (0, 'Background'),
'aeroplane': (1, 'Vehicle'),
'bicycle': (2, 'Vehicle'),
'bird': (3, 'Animal'),
'boat': (4, 'Vehicle'),
'bottle': (5, 'Indoor'),
'bus': (6, 'Vehicle'),
'car': (7, 'Vehicle'),
'cat': (8, 'Animal'),
'chair': (9, 'Indoor'),
'cow': (10, 'Animal'),
'diningtable': (11, 'Indoor'),
'dog': (12, 'Animal'),
'horse': (13, 'Animal'),
'motorbike': (14, 'Vehicle'),
'Person': (15, 'Person'),
'pottedplant': (16, 'Indoor'),
'sheep': (17, 'Animal'),
'sofa': (18, 'Indoor'),
'train': (19, 'Vehicle'),
'tvmonitor': (20, 'Indoor'),
}
SSD-Tensorflow-master—>datasets—>pascalvoc_to_tfrecords.py 然后更改文件的83行读取方式为’rb’)
另外就是按照上述的方法生成tfrecords文件,即可开始训练自己的数据。
7目前榜单
附上目前为止,问题检测模型的实时排名,这个排名是voc2012上的排名。http://host.robots.ox.ac.uk:8080/leaderboard/displaylb.php?cls=motorbike&challengeid=11&compid=4&submid=19549
8参考文献
在学习研究ssd的过程中,看了很多论文、博客、源码等,感谢这些资料的作者,现在把影响我较多的一些资料的链接附上:
原论文:https://arxiv.org/abs/1512.02325
模型github:https://github.com/balancap/SSD-Tensorflow
其他博客:
http://www.studyai.com/article/3e454b9e#goto-page-logo
https://blog.csdn.net/u010167269/article/details/52563573
https://blog.csdn.net/c20081052/article/details/80391627
https://blog.csdn.net/WZZ18191171661/article/details/79444217