3.6 YOLO(You only look once)
学习目标
- 目标
- 知道YOLO的网络结构
- 知道单元格的意义
- 知道YOLO的损失
- 应用
- 无
在正式介绍YOLO之前,我们来看一张图:
可以看出YOLO的最大特点是速度快。
3.6.1 YOLO
3.6.1.1 结构
一个网络搞定一切,GoogleNet + 4个卷积+2个全连接层
3.6.1.2 流程理解
- 1、原始图片resize到448x448,经过前面卷积网络之后,将图片输出成了一个7 7 30的结构
以图示的方式演示
2、默认7 7个单元格,这里用3 3的单元格图演示
3、每个单元格预测两个bbox框
4、进行NMS筛选,筛选概率以及IoU
3.6.2 单元格(grid cell)
最后网络输出的7 7 30的特征图怎么理解?7 * 7=49个像素值,理解成49个单元格,每个单元格可以代表原图的一个方块。单元格需要做的两件事:
-
1.每个单元格负责预测一个物体类别,并且直接预测物体的概率值
-
2.每个单元格预测两个(默认)bbox位置,两个bbox置信度(confidence) 7 7 2=98个bbox
- 30=(4+1+4+1+20), 4个坐标信息,1个置信度(confidence)代表一个bbox的结果, 20代表 20类的预测概率结果
3.6.2.1 网格输出筛选
一个网格会预测两个Bbox,在训练时我们只有一个Bbox专门负责(一个Object 一个Bbox)
怎么进行筛选?
- 通过置信度大小比较
每个bounding box都对应一个confidence score
- 如果grid cell里面没有object,confidence就是0
- 如果有,则confidence score等于 预测的box和ground truth的IOU乘积
注:所以如何判断一个grid cell中是否包含object呢?如果一个object的ground truth的中心点坐标在一个grid cell中,那么这个grid cell就是包含这个object,也就是说这个object的预测就由该grid cell负责。
这个概率可以理解为不属于任何一个bbox,而是属于这个单元格所预测的类别。
- 不同于faster rcnn中的anchors,yolo的框坐标是由网络得出,而faster-rcnn是人为设定一个值,然后利用RPN网络对其优化到一个更准的坐标和是否背景类别
3.6.3 非最大抑制(NMS)
每个Bbox的Class-Specific Confidence Score以后,设置阈值,滤掉概率的低的bbox,对每个类别过滤IoU,就得到最终的检测结果
3.6.4 训练
- 预测框对应的目标值标记
- confidence:格子内是否有目标
- 20类概率:标记每个单元格的目标类别
怎么理解这个过程?同样以分类那种形式来对应,假设以一个单元格的预测值为结果,如下图
- 损失
- 三部分损失 bbox损失+confidence损失+classfication损失
3.6.5 与Faster R-CNN比较
Faster R-CNN利用RPN网络与真实值调整了候选区域,然后再进行候选区域和卷积特征结果映射的特征向量的处理来通过与真实值优化网络预测结果。而这两步在YOLO当中合并成了一个步骤,直接网络输出预测结果进行优化。
所以经常也会称之为YOLO算法为直接回归法代表。YOLO的特点就是快
3.6.6 YOLO总结
- 优点
- 速度快
- 缺点
- 准确率会打折扣
- YOLO对相互靠的很近的物体(挨在一起且中点都落在同一个格子上的情况),还有很小的群体检测效果不好,这是因为一个网格中只预测了两个框
3.6.7 总结
- YOLO的网络结构
- 输出结果的意义
- 7 7 30(用于20类分类)
========================================
3.7 SSD(Single Shot MultiBox Detector)
学习目标
- 目标
- 知道SSD的结构
- 说明Detector & classifier的作用
- 说明SSD的优点
- 应用
- 无
3.7.1 SSD
3.7.1.1 简介
SSD算法源于2016年发表的算法论文,论文网址:https://arxiv.org/abs/1512.02325
SSD的特点在于:
-
SSD结合了YOLO中的回归思想和Faster-RCNN中的Anchor机制,使用全图各个位置的多尺度区域进行回归,既保持了YOLO速度快的特性,也保证了窗口预测的跟Faster-RCNN一样比较精准。
-
SSD的核心是在不同尺度的特征特征图上采用卷积核来预测一系列Default Bounding Boxes的类别、坐标偏移。
3.7.1.2 结构
以VGG-16为基础,使用VGG的前五个卷积,后面增加从CONV6开始的5个卷积结构,输入图片要求300*300。
3.7.1.3 流程
SSD中引入了Defalut Box,实际上与Faster R-CNN的anchor box机制类似,就是预设一些目标预选框,不同的是在不同尺度feature map所有特征点上使用PriorBox层
3.7.1.4 Detector & classifier
Detector & classifier的三个部分:
-
1.PriorBox层:生成default boxes,默认候选框
-
2.Conv3 x 3:生成localization, 4个位置偏移
-
3.Conv3 x 3:confidence,21个类别置信度(要区分出背景)
3.7.1.4.1 PriorBox层-default boxes
default boxex类似于RPN当中的滑动窗口生成的候选框,SSD中也是对特征图中的每一个像素生成若干个框。
- 特点分析:
- priorbox:相当于faster rcnn里的anchors,预设一些box,网络根据box,通过分类和回归给出被检测到物体的类别和位置。每个window都会被分类,并回归到一个更准的位置和尺寸上
- 各个feature map层经过priorBox层生成prior box
根据输入的不同aspect ratio 和 scale 以及 num_prior来返回特定的default box,default box 的数目是feature map的height x width x num_prior。
以下为了解内容,记住几个参数即可:
prior_box:打印出来的形状为:
Tensor("concat_2:0", shape=(?, 7308, 8), dtype=float32)
variance: bounding regression中的权重。网络输出[dxmin,dymin,dxmax,dymax],即对应利用如下方法进行针对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);
layer {
name: "conv6_2_mbox_priorbox"
type: "PriorBox"
bottom: "conv6_2"
bottom: "data"
top: "conv6_2_mbox_priorbox"
prior_box_param {
min_size: 111.0
max_size: 162.0
aspect_ratio: 2.0
aspect_ratio: 3.0
flip: true
clip: false
variance: 0.10000000149
variance: 0.10000000149
variance: 0.20000000298
variance: 0.20000000298
step: 32.0
offset: 0.5
}
3.7.1.4.2 localization与confidence
这两者的意义如下,主要作用用来过滤,训练
模型中打印出最后的三个部分结果:
Tensor("Reshape_42:0", shape=(?, 7308, 4), dtype=float32) Tensor("truediv:0", shape=(?, 7308, 21), dtype=float32) Tensor("concat_2:0", shape=(?, 7308, 8), dtype=float32)
问题:SSD中的多个Detector & classifier有什么作用?
SSD的核心是在不同尺度的特征图上来进行Detector & classifier 容易使得SSD观察到更小的物体
3.7.2 训练与测试流程
3.7.2.1 train流程
- 输入->输出->结果与ground truth标记样本回归损失计算->反向传播, 更新权值
1. 样本标记:
先将prior box与ground truth box做匹配进行标记正负样本,每次并不训练8732张计算好的default boxes, 先进行置信度筛选,并且训练指定的正样本和负样本, 如下规则
-
正样本
- 1.与GT重合最高的boxes, 其输出对应label设为对应物体.
- 2.物体GT与anchor iou满足大于0.5
-
负样本:其它的样本标记为负样本
在训练时, default boxes按照正负样本控制positive:negative=1:3
3. 损失
网络输出预测的predict box与ground truth回归变换之间的损失计算, 置信度是采用 Softmax Loss(Faster R-CNN是log loss),位置回归则是采用 Smooth L1 loss (与Faster R-CNN一样)
3.7.2.2 test流程
- 输入->输出->nms->输出
3.7.3 比较
从图中看出SSD算法有较高的准确率和性能,兼顾了速度和精度
3.7.4 总结
- SSD的结构
- Detector & classifier的组成部分以及作用
- SSD的训练样本标记
- GT与default boxes的格式转换过程
=============================================
SSD网络接口实现
4.3 案例:SSD进行物体检测
学习目标
- 目标
- 无
- 应用
- 应用keras SSD进行物体检测案例
4.3.1 案例效果
我们使用已经训练过的模型进行加载之后,总共基础训练时有动物、载具等等共20个物体类别的训练集。一下是对没有训练过的图像的检测结果
4.3.2 案例需求
使用开源的SSD网络结构进行检测的是的代码编写,由于开源代码使用 keras 编写,没有tf.keras版本,需要下载 keras-1.2.2 包
pip install keras==1.2.2
- 使用SSD网络模型,输入图片数据,处理图片数据
- 得到预测的类别和预测的位置
- 在图片中显示出来
4.3.3 步骤分析以及代码
- 代码结构:
- ckpt:模型参数保存目录
- image:测试图片
- nets:模型网络路径
- utils:公共组件(模型工具,BBox处理)
- 定义好类别数量以及输出
class SSDTrain(object):
def __init__(self):
self.classes_name = ['Aeroplane', 'Bicycle', 'Bird', 'Boat', 'Bottle',
'Bus', 'Car', 'Cat', 'Chair', 'Cow', 'Diningtable',
'Dog', 'Horse', 'Motorbike', 'Person', 'Pottedplant',
'Sheep', 'Sofa', 'Train', 'Tvmonitor']
self.classes_nums = len(self.classes_name) + 1
self.input_shape = (300, 300, 3)
模型预测流程
- SSD300模型输入以及加载参数
- 读取多个本地路径测试图片,preprocess_input以及保存图像像素值(显示需要)
- 模型预测结果,得到priorbox
-
进行非最大抑制算法处理
-
SSD300模型输入以及加载参数
-
by_name:按照每一层名字进行填充参数
-
If `by_name` is True, weights are loaded into layers only if they share the same name. This is useful for fine-tuning or transfer-learning models where some of the layers have changed.
-
model = SSD300(self.input_shape, num_classes=self.classes_nums)
model.load_weights('./ckpt/weights_SSD300.hdf5', by_name=True)
- 读取多个本地路径测试图片,preprocess_input以及保存图像像素值(显示需要)
需要使用
from keras.applications.imagenet_utils import preprocess_input
from keras.preprocessing.image import load_img, img_to_array
from scipy.misc import imread
import os
from nets.ssd_net import SSD300
from utils.ssd_utils import BBoxUtility
代码:
# 循环读取图片进行多个图片输出检测
feature = []
images = []
for pic_name in os.listdir("./image/"):
img_path = os.path.join("./image/", pic_name)
print(img_path)
# 读取图片
# 转换成数组
# 模型输入
img = load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1]))
img = img_to_array(img)
feature.append(img)
images.append(imread(img_path))
# 处理图片数据,ndarray数组输入
inputs = preprocess_input(np.array(feature))
- 模型预测结果,得到priorbox
# 预测
preds = model.predict(inputs, batch_size=1, verbose=1)
- 进行非最大抑制算法处理
# 定义BBox工具
bbox_util = BBoxUtility(self.classes_nums)
# 使用非最大抑制算法过滤
results = bbox_util.detection_out(preds)
print(results[0].shape, results[1].shape)
图片的检测结果显示
需要下载图像显示库
pip install matplotlib
- 对结果进行标记
def tag_picture(self, images, results):
"""
对图片预测结果画图显示
:param images:
:param results:
:return:
"""
for i, img in enumerate(images):
# 解析输出结果,每张图片的标签,置信度和位置
pre_label = results[i][:, 0]
pre_conf = results[i][:, 1]
pre_xmin = results[i][:, 2]
pre_ymin = results[i][:, 3]
pre_xmax = results[i][:, 4]
pre_ymax = results[i][:, 5]
print("label:{}, probability:{}, xmin:{}, ymin:{}, xmax:{}, ymax:{}".
format(pre_label, pre_conf, pre_xmin, pre_ymin, pre_xmax, pre_ymax))
# 过滤置信度低的结果
top_indices = [i for i, conf in enumerate(pre_conf) if conf >= 0.6]
top_conf = pre_conf[top_indices]
top_label_indices = pre_label[top_indices].tolist()
top_xmin = pre_xmin[top_indices]
top_ymin = pre_ymin[top_indices]
top_xmax = pre_xmax[top_indices]
top_ymax = pre_ymax[top_indices]
# 定义21中颜色,显示图片
# currentAxis增加图中文本显示和标记显示
colors = plt.cm.hsv(np.linspace(0, 1, 21)).tolist()
plt.imshow(img / 255.)
currentAxis = plt.gca()
for i in range(top_conf.shape[0]):
xmin = int(round(top_xmin[i] * img.shape[1]))
ymin = int(round(top_ymin[i] * img.shape[0]))
xmax = int(round(top_xmax[i] * img.shape[1]))
ymax = int(round(top_ymax[i] * img.shape[0]))
# 获取该图片预测概率,名称,定义显示颜色
score = top_conf[i]
label = int(top_label_indices[i])
label_name = self.classes_name[label - 1]
display_txt = '{:0.2f}, {}'.format(score, label_name)
coords = (xmin, ymin), xmax - xmin + 1, ymax - ymin + 1
color = colors[label]
# 显示方框
currentAxis.add_patch(plt.Rectangle(*coords, fill=False, edgecolor=color, linewidth=2))
# 左上角显示概率以及名称
currentAxis.text(xmin, ymin, display_txt, bbox={'facecolor': color, 'alpha': 0.5})
plt.show()
================================================