动手创建 SSD 目标检测框架
参考:单发多框检测(SSD)
本文代码被我放置在 Github:https://github.com/XinetAI/CVX/blob/master/app/gluoncvx/ssd.py
关于 SSD 的训练见:https://github.com/XinetAI/CVX/blob/master/目标检测/训练SSD.ipynb
虽然李沐大神的教程关于 SSD 的讲解很不错,但是大都是函数式的编程,本文我将 SSD 的几个基本组件进行封装,使得 SSD 可以像堆积木一样来进行组织。基网络你可以换成你想要的任意卷积模块,而其余的组件你可以将其当作像 nn.Dense
这样的对象来使用!
先载入一些必备包:
%matplotlib inline
import d2lzh as d2l
from mxnet import autograd, contrib, gluon, image, init, nd
from mxnet.gluon import loss as gloss, nn
import time
基础组件
单发多框检测(single shot multibox detection,SSD)[1] 主要由一个基础网络块和若干个多尺度特征块串联而成。其中基础网络块用来从原始图像中抽取特征,因此一般会选择常用的深度卷积神经网络。大体上可以将 SSD 分为:基网络层,类别预测层,边界框预测层以及高和宽减半块四个个不同的类别。
类别预测层与边界框预测层的设计思路与使用全局平均池化替换全连接层的想法类似。
类别预测层
下面我们来完成类别预测层:
class ClassPredictor(nn.Block):
def __init__(self, num_anchors, num_classes, **kwargs):
super().__init__(**kwargs)
self.num_classes = num_classes # 类别数目
self.num_anchors = num_anchors # 边界框数目
# 类别预测层
self.cls_predictor = nn.Conv2D(
self.num_anchors * (self.num_classes + 1), kernel_size=3, padding=1)
def forward(self, Y):
cls_preds = self.cls_predictor(Y)
return cls_preds
ClassPredictor
生成了与某一个尺度的特征图相同尺度的 num_classes + 1 个类别特征图(包括背景)。
下面我们模拟两个不同尺度的特征图来测试效果:
Y = nd.zeros((2, 8, 20, 20)) # 批量的 20 x 20 的 8 个特征图
cls = ClassPredictor(5, 10) # 实例化
cls.initialize() # 初始化
cls_preds = cls(Y) # 类特征图
Y1 = nd.zeros((2, 16, 10, 10)) # 批量的 10 x 10 的 16 个特征图
cls1 = ClassPredictor(5, 10) # 实例化
cls1.initialize() # 初始化
cls_preds1 = cls1(Y1) # 类特征图2
cls_preds.shape, cls_preds1.shape
((2, 55, 20, 20), (2, 55, 10, 10))
我们需要将它们变形成统一的格式并将多尺度的预测连结,从而让后续计算更简单。
def flatten_pred(pred): # 转换为通道维度在后
return pred.transpose((0, 2, 3, 1)).flatten()
def concat_preds(preds): # 拼接不同尺度的类别预测
return nd.concat(*[flatten_pred(p) for p in preds], dim=1)
concat_preds([cls_preds, cls_preds1]).shape # 拼接多个尺度特征图的类特征图
(2, 27500)
边界框预测层
同样使用一个类完成:
class BBoxPredictor(nn.Block):
def __init__(self, num_anchors, **kwargs):
super().__init__(**kwargs)
self.num_anchors = num_anchors
# 边界框预测层
self.bbox_predictor = nn.Conv2D(
self.num_anchors * 4, kernel_size=3, padding=1)
def forward(self, Y):
bbox_preds = self.bbox_predictor(Y)
return bbox_preds
测试效果:
Y = nd.zeros((2, 8, 20, 20)) # 批量的 20 x 20 的 8 个特征图
bbox = BBoxPredictor(10) # 实例化
bbox.initialize() # 初始化
bbox_preds = bbox(Y) # 边界框特征图
bbox_preds.shape
(2, 40, 20, 20)
ClassPredictor
生成了与某一个尺度的特征图相同尺度的 num_anchors x 4 个边界框坐标的特征图。
高和宽减半块
class DownSampleBlock(nn.Block):
def __init__(self, num_channels, **kwargs):
'''
高和宽减半块
'''
super().__init__(**kwargs)
self.block = nn.Sequential()
with self.block.name_scope():
for _ in range(2):
self.block.add(nn.Conv2D(num_channels, kernel_size=3, padding=1),
nn.BatchNorm(in_channels=num_channels),
nn.Activation('relu'))
self.block.add(nn.MaxPool2D(2))
def forward(self, X):
return self.block(X)
测试效果:
Y = nd.zeros((2, 8, 20, 20)) # 批量的 20 x 20 的 8 个特征图
down_sample = DownSampleBlock(10)
down_sample.initialize()
down_sample(Y).shape
(2, 10, 10, 10)
基网络
为了简洁这里仅仅设计一个简单的基网络:
class BaseNet(nn.Block):
def __init__(self, **kwargs):
'''
基网络
'''
super().__init__(**kwargs)
self.block = nn.Sequential()
with self.block.name_scope():
for num_filters in [16, 32, 64]:
self.block.add(DownSampleBlock(num_filters))
def forward(self, X):
return self.block(X)
测试效果:
Y = nd.zeros((2, 8, 512, 512)) # 批量的 20 x 20 的 8 个特征图
base_net = BaseNet()
base_net.initialize()
base_net(Y).shape
(2, 64, 64, 64)
锚框生成
class AnchorY(nn.Block):
def __init__(self, block, size, ratio, **kwargs):
super().__init__(**kwargs)
self.block = block
self._size = size
self._ratio = ratio
def forward(self, X):
Y = self.block(X)
anchors = contrib.ndarray.MultiBoxPrior(
Y, sizes=self._size, ratios=self._ratio)
return Y, anchors
测试效果:
block = BaseNet()
anchor_gen = AnchorY(block, .4, .7)
anchor_gen.initialize()
X = nd.zeros((2, 8, 256, 256))
Y, anchors = anchor_gen(X)
Y.shape, anchors.shape
((2, 64, 32, 32), (1, 1024, 4))
SSD 组装
class TinySSD(nn.Block):
def __init__(self, sizes, ratios, num_classes, **kwargs):
super().__init__(**kwargs)
sizes, ratios, self.num_classes = sizes, ratios, num_classes
self.num_anchors = len(sizes[0]) + len(ratios[0]) - 1
for i in range(5):
# 即赋值语句self.blk_i = get_blk(i)
setattr(self, 'blk_%d' % i, self.block(i))
setattr(self, 'cls_%d' % i, ClassPredictor(self.num_anchors,
self.num_classes))
setattr(self, 'bbox_%d' % i, BBoxPredictor(self.num_anchors))
setattr(self, 'anchor_%d' % i, AnchorY(
getattr(self, 'blk_%d' % i), sizes[i], ratios[i]))
def block(self, i):
if i == 0:
blk = BaseNet()
elif i == 4:
blk = nn.GlobalMaxPool2D()
else:
blk = DownSampleBlock(128)
return blk
def forward(self, X):
anchors, cls_preds, bbox_preds = [None] * 5, [None] * 5, [None] * 5
for i in range(5):
# getattr(self, 'blk_%d' % i)即访问self.blk_i
Y, anchors[i] = getattr(self, 'anchor_%d' % i)(X)
cls_preds[i] = getattr(self, 'cls_%d' % i)(Y)
bbox_preds[i] = getattr(self, 'bbox_%d' % i)(Y)
X = Y
# reshape函数中的0表示保持批量大小不变
cls_preds = concat_preds(cls_preds).reshape(
(0, -1, self.num_classes + 1))
return nd.concat(*anchors, dim=1), cls_preds, concat_preds(bbox_preds)
测试代码:
sizes = [[0.2, 0.272], [0.37, 0.447], [0.54, 0.619], [0.71, 0.79],
[0.88, 0.961]]
ratios = [[1, 2, 0.5]] * 5
num_classes = 1
X = nd.zeros((32, 3, 256, 256))
net = TinySSD(sizes, ratios, num_classes)
net.initialize()
anchors, cls_preds, bbox_preds = net(X)
print('output anchors:', anchors.shape)
print('output class preds:', cls_preds.shape)
print('output bbox preds:', bbox_preds.shape)
output anchors: (1, 5444, 4)
output class preds: (32, 5444, 2)
output bbox preds: (32, 21776)
网络结构:
net
TinySSD(
(blk_0): BaseNet(
(block): Sequential(
(0): DownSampleBlock(
(block): Sequential(
(0): Conv2D(3 -> 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=16)
(2): Activation(relu)
(3): Conv2D(16 -> 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=16)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
(1): DownSampleBlock(
(block): Sequential(
(0): Conv2D(16 -> 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=32)
(2): Activation(relu)
(3): Conv2D(32 -> 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=32)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
(2): DownSampleBlock(
(block): Sequential(
(0): Conv2D(32 -> 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=64)
(2): Activation(relu)
(3): Conv2D(64 -> 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=64)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
)
)
(cls_0): ClassPredictor(
(cls_predictor): Conv2D(64 -> 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(bbox_0): BBoxPredictor(
(bbox_predictor): Conv2D(64 -> 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(anchor_0): AnchorY(
(block): BaseNet(
(block): Sequential(
(0): DownSampleBlock(
(block): Sequential(
(0): Conv2D(3 -> 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=16)
(2): Activation(relu)
(3): Conv2D(16 -> 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=16)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
(1): DownSampleBlock(
(block): Sequential(
(0): Conv2D(16 -> 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=32)
(2): Activation(relu)
(3): Conv2D(32 -> 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=32)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
(2): DownSampleBlock(
(block): Sequential(
(0): Conv2D(32 -> 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=64)
(2): Activation(relu)
(3): Conv2D(64 -> 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=64)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
)
)
)
(blk_1): DownSampleBlock(
(block): Sequential(
(0): Conv2D(64 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(2): Activation(relu)
(3): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
(cls_1): ClassPredictor(
(cls_predictor): Conv2D(128 -> 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(bbox_1): BBoxPredictor(
(bbox_predictor): Conv2D(128 -> 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(anchor_1): AnchorY(
(block): DownSampleBlock(
(block): Sequential(
(0): Conv2D(64 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(2): Activation(relu)
(3): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
)
(blk_2): DownSampleBlock(
(block): Sequential(
(0): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(2): Activation(relu)
(3): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
(cls_2): ClassPredictor(
(cls_predictor): Conv2D(128 -> 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(bbox_2): BBoxPredictor(
(bbox_predictor): Conv2D(128 -> 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(anchor_2): AnchorY(
(block): DownSampleBlock(
(block): Sequential(
(0): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(2): Activation(relu)
(3): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
)
(blk_3): DownSampleBlock(
(block): Sequential(
(0): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(2): Activation(relu)
(3): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
(cls_3): ClassPredictor(
(cls_predictor): Conv2D(128 -> 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(bbox_3): BBoxPredictor(
(bbox_predictor): Conv2D(128 -> 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(anchor_3): AnchorY(
(block): DownSampleBlock(
(block): Sequential(
(0): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(2): Activation(relu)
(3): Conv2D(128 -> 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=128)
(5): Activation(relu)
(6): MaxPool2D(size=(2, 2), stride=(2, 2), padding=(0, 0), ceil_mode=False)
)
)
)
(blk_4): GlobalMaxPool2D(size=(1, 1), stride=(1, 1), padding=(0, 0), ceil_mode=True)
(cls_4): ClassPredictor(
(cls_predictor): Conv2D(128 -> 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(bbox_4): BBoxPredictor(
(bbox_predictor): Conv2D(128 -> 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
)
(anchor_4): AnchorY(
(block): GlobalMaxPool2D(size=(1, 1), stride=(1, 1), padding=(0, 0), ceil_mode=True)
)
)
下面你便可以使用该网络进行目标检测了。
Liu, W., Anguelov, D., Erhan, D., Szegedy, C., Reed, S., Fu, C. Y., & Berg, A. C. (2016, October). Ssd: Single shot multibox detector. In European conference on computer vision (pp. 21-37). Springer, Cham. ↩︎
动手创建 SSD 目标检测框架的更多相关文章
- TF项目实战(SSD目标检测)-VOC2007
TF项目实战(SSD目标检测)-VOC2007 训练好的模型和代码会公布在网上: 步骤: 1.代码地址:https://github.com/balancap/SSD-Tensorflow 2.解压s ...
- 从零开始实现SSD目标检测(pytorch)(一)
目录 从零开始实现SSD目标检测(pytorch) 第一章 相关概念概述 1.1 检测框表示 1.2 交并比 第二章 基础网络 2.1 基础网络 2.2 附加网络 第三章 先验框设计 3.1 引言 3 ...
- CVPR2020|3D-VID:基于LiDar Video信息的3D目标检测框架
作者:蒋天园 Date:2020-04-18 来源:3D-VID:基于LiDar Video信息的3D目标检测框架|CVPR2020 Brief paper地址:https://arxiv.org/p ...
- caffe SSD目标检测lmdb数据格式制作
一.任务 现在用caffe做目标检测一般需要lmdb格式的数据,而目标检测的数据和目标分类的lmdb格式的制作难度不同.就目标检测来说,例如准备SSD需要的数据,一般需要以下几步: 1.准备图片并标注 ...
- 如何使用 pytorch 实现 SSD 目标检测算法
前言 SSD 的全称是 Single Shot MultiBox Detector,它和 YOLO 一样,是 One-Stage 目标检测算法中的一种.由于是单阶段的算法,不需要产生所谓的候选区域,所 ...
- AI SSD目标检测算法
Single Shot multibox Detector,简称SSD,是一种目标检测算法. Single Shot意味着SSD属于one stage方法,multibox表示多框预测. CNN 多尺 ...
- TF项目实战(基于SSD目标检测)——人脸检测1
SSD实战——人脸检测 Tensorflow 一 .人脸检测的困难: 1. 姿态问题 2.不同种族人, 3.光照 遮挡 带眼睛 4.视角不同 5. 不同尺度 二. 数据集介绍以及转化VOC: 1. F ...
- 使用SSD目标检测c++接口编译问题解决记录
本来SSD做测试的Python接口用起来也是比较方便的,但是如果部署集成的话,肯定要用c++环境,于是动手鼓捣了一下. 编译用的cmake,写的CMakeList.txt,期间碰到一些小问题,简单记录 ...
- 目标检测框架py-faster-rcnn修改anchor_box
众所周知,anchor_box控制了回归框的大小,我们有时候检测的是大物体或小物体时,需要调整回归框的大小的时候,得改一下anchor_box.基于rgb公开的py-faster-rcnn修改anch ...
随机推荐
- Swift 设置某个对象的normal 属性找不到normal 解决方案
normal 等价于 UIControlState(rawValue: 0)
- spark submit参数及调优
park submit参数介绍 你可以通过spark-submit --help或者spark-shell --help来查看这些参数. 使用格式: ./bin/spark-submit \ ...
- 1873: This offer(zzuli)
题目描述 话说WX入职已经有一个多月了,公司boss突然扔给他了一个问题,如果解决不了的话就会被开除掉 - -#,情急之下他只能来请教你了,boss给了他N个不大于100的数,现在wx需要将这N个数通 ...
- ubuntu sublime text 3 安装
#安装GPG wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key add - #确保apt被设置为 ...
- Niagara物联网框架机制一(笔记)
一.介绍: Niagara是Tridium公司研发的设计用于解决设备连接应的软件框架平台技术,应用框架是一个软件工程中的概念,不同于普通的软件,它是用于实现某应用领域通用完备功能的底层服务,使用这种框 ...
- mysql登录报错:ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
在MySQL登录时出现Access denied for user 'root'@'localhost' (using password: YES) 拒绝访问 对于出现拒绝访问root用户的解决方案错 ...
- 插件使用一树形插件---zTree一zTree异步加载
zTree 可以实现异步加载.异步加载可以让初次加载速度快,带来好的用户体验. 异步加载 官方源码中的demo提示了例子.例子是采用php语言. 在java语言中,zTree如何与Servlet结合呢 ...
- Pycharm常用操作方法
1.调整字体大小 2.选择python编译器
- What is base..ctor(); in C#?
I am disassembling some C# applications and I am trying to reconstruct the source code. I am disasse ...
- loss函数学习笔记
一直对机器学习里的loss函数不太懂,这里做点笔记. 符号表示的含义,主要根据Andrew Ng的课程来的,\(m\)个样本,第\(i\)个样本为\(\vec x^{(i)}\),对应ground t ...