SSD 实时目标检测 SSD是为了实现实时目标检测而设计的。Faster RCNN使用一个区域建议网络 region proposal network (RPN)产生可能包含物体的建议区域(一般训练时2000,测试时600),并且利用这些建议区域进行微调对物体进行分类和定位。Faster RCNN 整个处理过程大约是每秒 7 帧,这远远不能满足实时性。SSD通过取消 RPN 来加速处理过程。为了弥补精度上的损失,SSD采取了一些改进方法如:1. 提取了不同尺度的特征图来做检测,大尺度特征图(较靠前的特征图)可以用来检测小物体,而小尺度特征图(较靠后的特征图)用来检测大物体。这些改进方法允许SSD使用较低分辨率的图像就可以达到Faster RCNN的精度,而且处理速度更快。从下图可以看出,SSD达到了实时处理的速度并且精度还强于Faster RCNN。(精度的评价指标是mAP预测平均精度)。2. 采用了不同尺度和长宽比的先验框(Prior boxes, Default boxes,在 Faster RCNN 中叫做锚,Anchors)。3. 采用CNN来直接进行检测,而不是像 Faster RCNN Yolo 那样在全连接层之后做检测,实现了全卷积4. 将空洞卷积应用于目标检测。这些改进方法使得 SSD 在使用较低分辨率的图像( lower resolution images)就可以达到 Faster RCNN 的精度,而且处理速度更快。从下图可以看出,SSD达到了实时处理的速度并且精度还超过了 Faster RCNN。SSD is a classaware RPN with a lot of bells and whistles.SSD SSD 包含两个部分:提取特征图应用卷积核检测物体ModelmAPTraining CommandTraining logSSD 使用 VGG16 作为特征提取网络 backbone,当然你也可以用其他的 backbone,例如 mobilenet,resnet等等,不过奇怪的是使用 resnet 貌似提升不大。不过其实对于物体较大的简单任务,使用mobilenet还是其他什么的区别不是很大,因此具体选哪个得看自己的任务了。下面是 GluonCV 得到的结果,使用了一些 tricks 得到了超越原论文的表现:cv.mxnet.io/model_zoo/detection.html#ssdssd_300_vgg16_atrous_voc [1]77.6shell scriptlogssd_512_vgg16_atrous_voc [1]79.2shell scriptlogssd_512_resnet50_v1_voc [1]80.1shell scriptlogssd_512_mobilenet1.0_voc [1]75.4shell scriptlog可以看到由 VGG 换为 resnet50提升只有 0.9%,甚至有时候还会下降,这点比较奇怪。而 Faster RCNN 由 VGG16 变为 ResNet101 可以提升 5%。VGG 太大了,由上面的图可以看到,基本没有比 VGG 还大的了。SSD 使用 Conv4_3 的卷积层(VGG 下采样16倍之前的所有网络层)来检测物体。为了方便说明,我们假设图片经过 Conv4_3 变成了一个 8×8 的特征图(它应该是38×38,因为下采样了 8 倍)。对于每个 cell (也称为location),它进行了 4 个预测。 左:原始图片 右:每个 cell 4 个预测。每个预测结果包含一个边界框 bbox 以及这个bbox 属于这 21 个类别的得分(其中一个是背景类别),我们选择其中昀高的得分所属类别作为该bbox的分类类别。Conv4_3 总共会做出 38x38x4 个预测:每个 cell 4 个预测,跟特征图的深度无关。显然,很多预测结果中都不包含物体。SSD 将这些不包含物体的归类为0。 每个预测结果包含一个边界框 bbox 以及这个bbox 属于这 21 个类别的得分(其中一个是背景类别)VGG网络部分源码: # vgg(base['300'], 3) SSD 中的VGG16配置:# base['300'] = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',512, 512, 512]# VGG16 官方的特征提取部分配置:# [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M']# 可以看到去掉了昀后的maxpooling层,并且对于其中一个maxpooling改变了ceil_mode,这在 75 进行maxpooling时会使得# 75 == 37.5 == 38 而不是变成 37# 不太明白为什么要这样,不过应该没太大的影响,可能还是为了尺寸方面考虑# 300 == 150 == 75 == 38 == 19'M':表示使用 kernel size = 2,stride = 2 的 max pooling 'C':表示与‘M’相同的 max pooling,但 ceil_mode = True,需要注意的是这与原始的VGG不同,原始的VGG没有这个选项def vgg(cfg, i, batch_norm=False): (vgg): ModuleList( (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv1_1 (1): ReLU(inplace) (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))Conv1_2 (3): ReLU(inplace) (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv2_1 (6): ReLU(inplace) (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv2_2 (8): ReLU(inplace) (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv3_1 (11): ReLU(inplace) (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv3_2 (13): ReLU(inplace) (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv3_3123456789101112131415161718192021222324252627282930 (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv3_3 (15): ReLU(inplace) (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=True) (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv4_1 (18): ReLU(inplace) (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv4_2 (20): ReLU(inplace) (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv4_3 检测所用的特征图 (22): ReLU(inplace) (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv5_1 (25): ReLU(inplace) (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv5_2 (27): ReLU(inplace) (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) Conv5_3 (29): ReLU(inplace) ############################################################################################### (30): MaxPool2d(kernel_size=3, stride=1, padding=1, dilation=1, ceil_mode=False) pool5 (31): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(6, 6), dilation=(6, 6)) conv6 (32): ReLU(inplace) (33): Conv2d(1024, 1024, kernel_size=(1, 1), stride=(1, 1)) conv7 检测所用的特征图 (34): ReLU(inplace) ) layers = [] in_channels = i for v in cfg: if v == 'M': layers += [nn.MaxPool2d(kernel_size=2, stride=2)] elif v == 'C': # ceil模式shape向上取整 layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)] else: conv2d = nn.Conv2d(in_channels, v, kernel_size=3