声明!
本文参考何宽大佬所写,点击可以到达
目标定位
在图片中,需要定位到我们的目标,采用下图的方式:
这里Y输出有8个量,分别是PC(判断是否是我们的目标)
,bx,by代表目标中心位置
,bh,bw代表目标长宽
,c1,c2,c3分别代表别的目标
,误差函数也有所区别,当pc=1(也就是说图里面有我们要识别的目标,我们在,误差函数相应第一个)
特征点检测
通过卷积得到的结果,这里可以输出不同位置对应的坐标点,可以通过这些点来大致的描绘出轮廓。
目标检测
采用基于滑动窗口的目标检测算法
以上图为例,训练一个带标签的卷积网络,可以实现滑动窗口目标检测,如下图:
- 但是这种方法有个缺点在于计算成本,图片剪切成一个个小块,卷积神经网络需要一个个处理,如果步幅过大,检测不精细,反之,会造成计算成本过高(主要来自于重复计算)
- 这个算法不错,但是对于单个分类任务成本高很多,同时滑动太慢,除非使用超细的粒度(框的大小)或者极小步幅,否则无法准确定位图像。目前有方法可以优化计算成本。
卷积滑动窗口的实现
上面是我们正常的卷积,在后面转化成列向量形式是没办法用卷积的,所以这里可以通过同样维度的卷积核(这里是5*5),进而卷积保持维度相同,看下下面的图:
- 这里我们进行扩充,在第二行,分四个框,我们此时值需要一次卷积(他们之间有公共的计算资源),在我们最后的输出中,不用的小方格,就可以代表我们之前图片切块卷积的信息。下面的图可以体现这一点:
- 这里我们不需要分别对图片进行切片,依次卷积,只需要对整张图片进行卷积,就能得到各个部分的预测结果。
- 这个算法效率变高,但是仍然不能输出最精准的边界框,这里可以使用
YOLO algorithm (you only look once)
,后面会进行介绍
边界预测
YOLO算法 (you only look once)
基本思路,使用图像分类和定位算法,使用以下9个格子中,这里分了9个格子,每个格子对应一个8维的Y的输出标签
跟我们前面一样 ,目标输出为3*3*8
,此时我们的目标即(object)
可以跨越多个小方格,把object
放到中心点所在的方格的位置处。
- 这个算法能让神经网络输出边界框,可以有任意宽高比,并能输出更精确的坐标,不会受到滑动窗法分类器的步长大小限制,同时这只是单个卷积的体现,我们并没有进行
3*3
次(当然如果格子越多计算越多)运算,使用一个卷积网络,共享计算步骤。 - YOLO算法运算速度十分快,可以进行实时识别。
- 这里确定位置,我们把yolo算法框的左上角定为(0,0),右下角为(1,1)
交并比函数(IoU Intersection over union )
顾名思义,就是交集和并集的比,判断对象检测算法是否良好。
这里黄色代表交集交集,绿色代表并集,他们之比>0.5(也可以自己设置更高,但一般没有低的),说明良好
非极大值抑制(Non-max suppression )
-
算法可能对某个对象进行多次检测,通过这种算法保证,只会对对象进行一次检测。
-
顾名思义,就是对于我们预测的不是最大值的进行抑制,这个算法简单来说,就是一个小车可能占了我们不同小格,当然这些小小格子是临近的,这里会有不同的预测概率值。
-
先抑制掉所有小的Pc<=0.6的,剩下的我们抑制选取概率最高的,当成边框,高亮显示。
-
把剩下的和输出边界框有很高重叠面积的和上一步输出边界框有很高交并比的边界框抛弃。
-
如果同时对于三个对象进行检测,输出向量会有三个额外的分量,需要独立进行三次非极大值抑制,对每种类型的输出都进行一次。
Anchor Boxes
- 目前存在的问题是,一个格子只能检测一个对象,如果想一个格子检测多个对象,使用如题的方法
- 先预定几个不同的边框,把预测结果和
anchor box
(这里我们是两个,一般可能更多)联合起来, - 这里我们将数据y,写两次,人的边框更像是
anchor box 1
,而车的边框更像是anchor box 2
,pc便是代表着不同的对象
YOLO完整算法
- 对于完整的YOLO,我们可以看到,y数据的前8行,代表着
anchor box 1
(纵轴比横轴长)的数据,后八行代表anchor box 2
数据(横轴比纵轴长)对于不是的数据Pc=0
其他数据我们不在乎,在绿色框里面,我们检测到车的框横轴比纵轴长,下八行数据Pc=1 ,C2 =1 ,其他为0
- 对于每个小图片,我们都会得到两个框框 ,抛弃掉低概率的框。
- 丢弃完后,对于每种类型,我们都进行一次非极大值抑制,得到最终的预测。
自动驾驶,汽车识别
要解决的问题: 假设现在在做自动驾驶的汽车,首先应该做一个汽车检测系统,为了搜集数据,在汽车前引擎盖上安装了一个照相机,在开车的时候它会每隔几秒拍摄一次前方的道路。
- 想让YOLO识别80个分类,可以把分类标签c cc从1到80进行标记,或者把它变为80维的向量(80个数字),在对应位置填写上0或1。视频中我们使用的是后面的方案。因为YOLO的模型训练起来是比较久的,我们将使用预先训练好的权重来进行使用。
YOLO
YOLO(“you only look once”)因为它的实时高准确率,这就使得它是目前比较流行的算法。在算法中“只看一次(only looks once)”的机制使得它在预测时只需要进行一次前向传播,在使用非最大值抑制后,它与边界框一起输出识别对象。
yolo 模型细节
- 我们会使用5个锚框(anchor boxes),所以算法大致流程是这样的:图像输入(m,608,608,3) ⇒ DEEP CNN ⇒ 编码(m,19,19,5,85)。
为了方便,我们将把最后的两个维度的数据进行展开,所以最后一步的编码由(m,19,19,5,85)变为了(m,19,19,425)。
对于每个单元格的每个锚框而言,我们将计算下列元素的乘积,并提取该框包含某一类的概率。
这里有张YOLO预测图的可视化预测: - 对于每个19x19的单元格,找寻最大的可能性值,在5个锚框和不同的类之间取最大值
- 根据单元格预测的最可能的对象来使用添加颜色的方式来标记单元格。
- 该可视化不是YOLO算法本身进行预测的核心部分,这只是一种可视化算法中间结果的比较好的方法。
- 另一种可视化YOLO输出的方法是绘制它输出的边界框,这样做会导致可视化是这样的:
- 在上图中们只绘制了模型所猜测的高概率的锚框,但锚框依旧是太多了。我们希望将算法的输出过滤为检测到的对象数量更少,要做到这一点,我们将使用非最大抑制:
- 舍弃掉低概率的锚框(意思是格子算出来的概率比较低我们就不要)
- 当几个锚框相互重叠并检测同一个物体时,只选择一个锚框。
分类阈值过滤
现在我们要实现函数yolo_filter_boxes()
,步骤如下:
- 根据图4来计算对象的可能性:
a = np.random.randn(19x19,5,1) #p_c
b = np.random.randn(19x19,5,80) #c_1 ~ c_80
c = a * b #计算后的维度将会是(19x19,5,80)
- 对于每个锚框,需要找到:
对分类的预测的概率拥有最大值的锚框的索引(中文文档),需要注意的是我们需要选择的轴,我们可以试着使用axis=-1
。
对应的最大值的锚框(查看中文文档),需要注意的是我们需要选择的轴,我们可以试着使用axis=-1
- 根据阈值来创建掩码,比如执行下列操作:[0.9, 0.3, 0.4, 0.5, 0.1] < 0.4,返回的是[False, True, False, False, True],对于我们要保留的锚框,对应的掩码应该为True或者
- 使用TensorFlow来对
box_class_scores、boxes、box_classes
进行掩码操作以过滤出我们想要的锚框。
def yolo_filter_boxes(box_confidence, boxes, box_class_probs, threshold=0.6):
"""
通过阈值来过滤对象和分类的置信度。
参数:
box_confidence - tensor类型,维度为(19,19,5,1),包含19x19单元格中每个单元格预测的5个锚框中的所有的锚框的pc (一些对象的置信概率)。
boxes - tensor类型,维度为(19,19,5,4),包含了所有的锚框的(px,py,ph,pw )。
box_class_probs - tensor类型,维度为(19,19,5,80),包含了所有单元格中所有锚框的所有对象( c1,c2,c3,···,c80 )检测的概率。
threshold - 实数,阈值,如果分类预测的概率高于它,那个分么这类概率预测的就会被保留。
返回:
scores - tensor 类型,维度为(None,),包含了保留了的锚框的分类概率。
boxes - tensor 类型,维度为(None,4),包含了保留了的锚框的(b_x, b_y, b_h, b_w)
classess - tensor 类型,维度为(None,),包含了保留了的锚框的索引
注意:"None"是因为你不知道所选框的确切数量,因为它取决于阈值。
比如:如果有10个锚框,scores的实际输出大小将是(10,)
"""
# 第一步:计算锚框的得分
box_scores = box_confidence * box_class_probs
# 计算后的维度将会是(19x19,5,80),box_confidence维度为(19,19,5,1),5是5个锚框,1为Pc=0或者Pc=1,
# 代表这个方块有没有我们要检测的。0时候我们就不在乎其他值
# 第二步:找到最大值的锚框的索引以及对应的最大值的锚框的分数(即索引代表我们属于哪种类型的C,分数代表这种类型出现的概率)
box_classes = K.argmax(box_scores, axis=-1)
box_class_scores = K.max(box_scores, axis=-1)
# 第三步:根据阈值创建掩码(过滤掉低概率的)
filtering_mask = (box_class_scores >= threshold)
# 对scores, boxes 以及 classes使用掩码
scores = tf.boolean_mask(box_class_scores, filtering_mask)
boxes = tf.boolean_mask(boxes, filtering_mask)
classes = tf.boolean_mask(box_classes, filtering_mask)
# tf.boolean_mask将输入的数组挑出想要的数据输出,在原来的一堆中,挑出高概率的。
return scores, boxes, classes
非最大值抑制
- 即使是我们通过阈值来过滤了一些得分较低的分类,但是我们依旧会有很多的锚框被留了下来,第二个过滤器就是让下图左边变为右边,我们叫它非最大值抑制( non-maximum suppression (NMS))
非最大值抑制使用了一个非常重要的功能,叫做交并比(Intersection over Union (IoU))
实现交并比函数iou(),伪代码如下:
def iou(box1, box2):
"""
实现两个锚框的交并比的计算
参数:
box1 - 第一个锚框,元组类型,(x1, y1, x2, y2)
box2 - 第二个锚框,元组类型,(x1, y1, x2, y2)
返回:
iou - 实数,交并比。
"""
# 计算相交的区域的面积
xi1 = np.maximum(box1[0], box2[0])
yi1 = np.maximum(box1[1], box2[1])
xi2 = np.minimum(box1[2], box2[2])
yi2 = np.minimum(box1[3], box2[3])
inter_area = (xi1 - xi2) * (yi1 - yi2)
# 计算并集,公式为:Union(A,B) = A + B - Inter(A,B)
box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
union_area = box1_area + box2_area - inter_area
# 计算交并比
iou = inter_area / union_area
return iou
实现非最大值抑制函数,关键步骤如下:
- 选择分值高的锚框
- 计算与其他框的重叠部分,并删除与iou_threshold相比重叠的框。
- 返回第一步,直到不再有比当前选中的框得分更低的框。
这将删除与选定框有较大重叠的其他所有锚框,只有得分最高的锚框仍然存在
我们要实现的函数名为yolo_non_max_suppression()
,使用TensorFlow实现,TensorFlow有两个内置函数用于实现非最大抑制。
def yolo_non_max_suppression(scores, boxes, classes, max_boxes=10, iou_threshold=0.5):
"""
为锚框实现非最大值抑制( Non-max suppression (NMS))
参数:
scores - tensor类型,维度为(None,),yolo_filter_boxes()的输出
boxes - tensor类型,维度为(None,4),yolo_filter_boxes()的输出,已缩放到图像大小(见下文)
classes - tensor类型,维度为(None,),yolo_filter_boxes()的输出
max_boxes - 整数,预测的锚框数量的最大值
iou_threshold - 实数,交并比阈值。
返回:
scores - tensor类型,维度为(,None),每个锚框的预测的可能值
boxes - tensor类型,维度为(4,None),预测的锚框的坐标
classes - tensor类型,维度为(,None),每个锚框的预测的分类
注意:"None"是明显小于max_boxes的,这个函数也会改变scores、boxes、classes的维度,这会为下一步操作提供方便。
"""
max_boxes_tensor = K.variable(max_boxes, dtype="int32") # 用于tf.image.non_max_suppression()
tf.Session().run(tf.variables_initializer([max_boxes_tensor])) # 初始化变量max_boxes_tensor
# 使用使用tf.image.non_max_suppression()来获取与我们保留的框相对应的索引列表
nms_indices = tf.image.non_max_suppression(boxes, scores, max_boxes, iou_threshold)
# 使用K.gather()来选择保留的锚框
scores = K.gather(scores, nms_indices)
boxes = K.gather(boxes, nms_indices)
classes = K.gather(classes, nms_indices)
return scores, boxes, classes
对所有框进行过滤
- 现在要实现一个CNN(19x19x5x85)输出的函数,并使用刚刚实现的函数对所有框进行过滤。
- 我们要实现的函数名为
yolo_eval()
,它采用YOLO编码的输出,并使用分数阈值和NMS来过滤这些框。。有几种表示锚框的方式,例如通过它们的角或通过它们的中点和高度/宽度。YOLO使用以下功能在几种这样的格式之间进行转换: boxes = yolo_boxes_to_corners(box_xy, box_wh)
它将yolo锚框坐标(x,y,w,h)转换为角的坐标(x1,y1,x2,y2)以适应yolo_filter_boxes()的输入。boxes = yolo_utils.scale_boxes(boxes, image_shape)
YOLO的网络经过训练可以在608x608图像上运行。如果要在不同大小的图像上测试此数据(例如,汽车检测数据集具有720x1280图像),则此步骤会重新缩放这些框,以便在原始的720x1280图像上绘制它们。
def yolo_eval(yolo_outputs, image_shape=(720., 1280.),
max_boxes=10, score_threshold=0.6, iou_threshold=0.5):
"""
将YOLO编码的输出(很多锚框)转换为预测框以及它们的分数,框坐标和类。
参数:
yolo_outputs - 编码模型的输出(对于维度为(608,608,3)的图片),包含4个tensors类型的变量:
box_confidence : tensor类型,维度为(None, 19, 19, 5, 1)
box_xy : tensor类型,维度为(None, 19, 19, 5, 2)
box_wh : tensor类型,维度为(None, 19, 19, 5, 2)
box_class_probs: tensor类型,维度为(None, 19, 19, 5, 80)
image_shape - tensor类型,维度为(2,),包含了输入的图像的维度,这里是(608.,608.)
max_boxes - 整数,预测的锚框数量的最大值
score_threshold - 实数,可能性阈值。
iou_threshold - 实数,交并比阈值。
返回:
scores - tensor类型,维度为(,None),每个锚框的预测的可能值
boxes - tensor类型,维度为(4,None),预测的锚框的坐标
classes - tensor类型,维度为(,None),每个锚框的预测的分类
"""
# 获取YOLO模型的输出
box_confidence, box_xy, box_wh, box_class_probs = yolo_outputs
# 中心点转换为边角
boxes = yolo_boxes_to_corners(box_xy, box_wh)
# 可信度分值过滤(过滤是否有无对象)
scores, boxes, classes = yolo_filter_boxes(box_confidence, boxes, box_class_probs, score_threshold)
# 缩放锚框,以适应原始图像
boxes = yolo_utils.scale_boxes(boxes, image_shape)
# 使用非最大值抑制
scores, boxes, classes = yolo_non_max_suppression(scores, boxes, classes, max_boxes, iou_threshold)
return scores, boxes, classes
测试已经训练好了的YOLO模型
- 建一个会话来启动计算图:
sess = K.get_session()
- 定义分类、锚框与图像维度
分类80个类别,使用5个锚框。收集了两个文件“coco_classes.txt”和“yolo_anchors.txt”中关于80个类和5个锚框的信息。 我们将这些数据加载到模型中。
class_names = yolo_utils.read_classes("model_data/coco_classes.txt")
anchors = yolo_utils.read_anchors("model_data/yolo_anchors.txt")
image_shape = (720.,1280.)
- 加载已经训练好了的模型
训练YOLO模型需要很长时间,并且需要一个相当大的标签边界框数据集,用于大范围的目标类。我们将加载存储在“yolov2.h5”中的现有预训练Keras YOLO模型。(这些权值来自官方YOLO网站,并使用Allan Zelener编写的函数进行转换,从技术上讲,这些参数来自“YOLOv2”模型。) (也就是官方提供的)
yolo_model = load_model("model_data/yolov2.h5")
查看YOLO模型的权重, 以下是模型包含的图层的摘要:
yolo_model.summary()
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) (None, 608, 608, 3) 0
__________________________________________________________________________________________________
conv2d_1 (Conv2D) (None, 608, 608, 32) 864 input_1[0][0]
__________________________________________________________________________________________________
batch_normalization_1 (BatchNor (None, 608, 608, 32) 128 conv2d_1[0][0]
__________________________________________________________________________________________________
leaky_re_lu_1 (LeakyReLU) (None, 608, 608, 32) 0 batch_normalization_1[0][0]
__________________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D) (None, 304, 304, 32) 0 leaky_re_lu_1[0][0]
__________________________________________________________________________________________________
conv2d_2 (Conv2D) (None, 304, 304, 64) 18432 max_pooling2d_1[0][0]
__________________________________________________________________________________________________
batch_normalization_2 (BatchNor (None, 304, 304, 64) 256 conv2d_2[0][0]
__________________________________________________________________________________________________
leaky_re_lu_2 (LeakyReLU) (None, 304, 304, 64) 0 batch_normalization_2[0][0]
__________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D) (None, 152, 152, 64) 0 leaky_re_lu_2[0][0]
__________________________________________________________________________________________________
conv2d_3 (Conv2D) (None, 152, 152, 128 73728 max_pooling2d_2[0][0]
__________________________________________________________________________________________________
batch_normalization_3 (BatchNor (None, 152, 152, 128 512 conv2d_3[0][0]
__________________________________________________________________________________________________
leaky_re_lu_3 (LeakyReLU) (None, 152, 152, 128 0 batch_normalization_3[0][0]
__________________________________________________________________________________________________
conv2d_4 (Conv2D) (None, 152, 152, 64) 8192 leaky_re_lu_3[0][0]
__________________________________________________________________________________________________
batch_normalization_4 (BatchNor (None, 152, 152, 64) 256 conv2d_4[0][0]
__________________________________________________________________________________________________
leaky_re_lu_4 (LeakyReLU) (None, 152, 152, 64) 0 batch_normalization_4[0][0]
__________________________________________________________________________________________________
conv2d_5 (Conv2D) (None, 152, 152, 128 73728 leaky_re_lu_4[0][0]
__________________________________________________________________________________________________
batch_normalization_5 (BatchNor (None, 152, 152, 128 512 conv2d_5[0][0]
__________________________________________________________________________________________________
leaky_re_lu_5 (LeakyReLU) (None, 152, 152, 128 0 batch_normalization_5[0][0]
__________________________________________________________________________________________________
max_pooling2d_3 (MaxPooling2D) (None, 76, 76, 128) 0 leaky_re_lu_5[0][0]
__________________________________________________________________________________________________
conv2d_6 (Conv2D) (None, 76, 76, 256) 294912 max_pooling2d_3[0][0]
__________________________________________________________________________________________________
batch_normalization_6 (BatchNor (None, 76, 76, 256) 1024 conv2d_6[0][0]
__________________________________________________________________________________________________
leaky_re_lu_6 (LeakyReLU) (None, 76, 76, 256) 0 batch_normalization_6[0][0]
__________________________________________________________________________________________________
conv2d_7 (Conv2D) (None, 76, 76, 128) 32768 leaky_re_lu_6[0][0]
__________________________________________________________________________________________________
batch_normalization_7 (BatchNor (None, 76, 76, 128) 512 conv2d_7[0][0]
__________________________________________________________________________________________________
leaky_re_lu_7 (LeakyReLU) (None, 76, 76, 128) 0 batch_normalization_7[0][0]
__________________________________________________________________________________________________
conv2d_8 (Conv2D) (None, 76, 76, 256) 294912 leaky_re_lu_7[0][0]
__________________________________________________________________________________________________
batch_normalization_8 (BatchNor (None, 76, 76, 256) 1024 conv2d_8[0][0]
__________________________________________________________________________________________________
leaky_re_lu_8 (LeakyReLU) (None, 76, 76, 256) 0 batch_normalization_8[0][0]
__________________________________________________________________________________________________
max_pooling2d_4 (MaxPooling2D) (None, 38, 38, 256) 0 leaky_re_lu_8[0][0]
__________________________________________________________________________________________________
conv2d_9 (Conv2D) (None, 38, 38, 512) 1179648 max_pooling2d_4[0][0]
__________________________________________________________________________________________________
batch_normalization_9 (BatchNor (None, 38, 38, 512) 2048 conv2d_9[0][0]
__________________________________________________________________________________________________
leaky_re_lu_9 (LeakyReLU) (None, 38, 38, 512) 0 batch_normalization_9[0][0]
__________________________________________________________________________________________________
conv2d_10 (Conv2D) (None, 38, 38, 256) 131072 leaky_re_lu_9[0][0]
__________________________________________________________________________________________________
batch_normalization_10 (BatchNo (None, 38, 38, 256) 1024 conv2d_10[0][0]
__________________________________________________________________________________________________
leaky_re_lu_10 (LeakyReLU) (None, 38, 38, 256) 0 batch_normalization_10[0][0]
__________________________________________________________________________________________________
conv2d_11 (Conv2D) (None, 38, 38, 512) 1179648 leaky_re_lu_10[0][0]
__________________________________________________________________________________________________
batch_normalization_11 (BatchNo (None, 38, 38, 512) 2048 conv2d_11[0][0]
__________________________________________________________________________________________________
leaky_re_lu_11 (LeakyReLU) (None, 38, 38, 512) 0 batch_normalization_11[0][0]
__________________________________________________________________________________________________
conv2d_12 (Conv2D) (None, 38, 38, 256) 131072 leaky_re_lu_11[0][0]
__________________________________________________________________________________________________
batch_normalization_12 (BatchNo (None, 38, 38, 256) 1024 conv2d_12[0][0]
__________________________________________________________________________________________________
leaky_re_lu_12 (LeakyReLU) (None, 38, 38, 256) 0 batch_normalization_12[0][0]
__________________________________________________________________________________________________
conv2d_13 (Conv2D) (None, 38, 38, 512) 1179648 leaky_re_lu_12[0][0]
__________________________________________________________________________________________________
batch_normalization_13 (BatchNo (None, 38, 38, 512) 2048 conv2d_13[0][0]
__________________________________________________________________________________________________
leaky_re_lu_13 (LeakyReLU) (None, 38, 38, 512) 0 batch_normalization_13[0][0]
__________________________________________________________________________________________________
max_pooling2d_5 (MaxPooling2D) (None, 19, 19, 512) 0 leaky_re_lu_13[0][0]
__________________________________________________________________________________________________
conv2d_14 (Conv2D) (None, 19, 19, 1024) 4718592 max_pooling2d_5[0][0]
__________________________________________________________________________________________________
batch_normalization_14 (BatchNo (None, 19, 19, 1024) 4096 conv2d_14[0][0]
__________________________________________________________________________________________________
leaky_re_lu_14 (LeakyReLU) (None, 19, 19, 1024) 0 batch_normalization_14[0][0]
__________________________________________________________________________________________________
conv2d_15 (Conv2D) (None, 19, 19, 512) 524288 leaky_re_lu_14[0][0]
__________________________________________________________________________________________________
batch_normalization_15 (BatchNo (None, 19, 19, 512) 2048 conv2d_15[0][0]
__________________________________________________________________________________________________
leaky_re_lu_15 (LeakyReLU) (None, 19, 19, 512) 0 batch_normalization_15[0][0]
__________________________________________________________________________________________________
conv2d_16 (Conv2D) (None, 19, 19, 1024) 4718592 leaky_re_lu_15[0][0]
__________________________________________________________________________________________________
batch_normalization_16 (BatchNo (None, 19, 19, 1024) 4096 conv2d_16[0][0]
__________________________________________________________________________________________________
leaky_re_lu_16 (LeakyReLU) (None, 19, 19, 1024) 0 batch_normalization_16[0][0]
__________________________________________________________________________________________________
conv2d_17 (Conv2D) (None, 19, 19, 512) 524288 leaky_re_lu_16[0][0]
__________________________________________________________________________________________________
batch_normalization_17 (BatchNo (None, 19, 19, 512) 2048 conv2d_17[0][0]
__________________________________________________________________________________________________
leaky_re_lu_17 (LeakyReLU) (None, 19, 19, 512) 0 batch_normalization_17[0][0]
__________________________________________________________________________________________________
conv2d_18 (Conv2D) (None, 19, 19, 1024) 4718592 leaky_re_lu_17[0][0]
__________________________________________________________________________________________________
batch_normalization_18 (BatchNo (None, 19, 19, 1024) 4096 conv2d_18[0][0]
__________________________________________________________________________________________________
leaky_re_lu_18 (LeakyReLU) (None, 19, 19, 1024) 0 batch_normalization_18[0][0]
__________________________________________________________________________________________________
conv2d_19 (Conv2D) (None, 19, 19, 1024) 9437184 leaky_re_lu_18[0][0]
__________________________________________________________________________________________________
batch_normalization_19 (BatchNo (None, 19, 19, 1024) 4096 conv2d_19[0][0]
__________________________________________________________________________________________________
conv2d_21 (Conv2D) (None, 38, 38, 64) 32768 leaky_re_lu_13[0][0]
__________________________________________________________________________________________________
leaky_re_lu_19 (LeakyReLU) (None, 19, 19, 1024) 0 batch_normalization_19[0][0]
__________________________________________________________________________________________________
batch_normalization_21 (BatchNo (None, 38, 38, 64) 256 conv2d_21[0][0]
__________________________________________________________________________________________________
conv2d_20 (Conv2D) (None, 19, 19, 1024) 9437184 leaky_re_lu_19[0][0]
__________________________________________________________________________________________________
leaky_re_lu_21 (LeakyReLU) (None, 38, 38, 64) 0 batch_normalization_21[0][0]
__________________________________________________________________________________________________
batch_normalization_20 (BatchNo (None, 19, 19, 1024) 4096 conv2d_20[0][0]
__________________________________________________________________________________________________
space_to_depth_x2 (Lambda) (None, 19, 19, 256) 0 leaky_re_lu_21[0][0]
__________________________________________________________________________________________________
leaky_re_lu_20 (LeakyReLU) (None, 19, 19, 1024) 0 batch_normalization_20[0][0]
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (None, 19, 19, 1280) 0 space_to_depth_x2[0][0]
leaky_re_lu_20[0][0]
__________________________________________________________________________________________________
conv2d_22 (Conv2D) (None, 19, 19, 1024) 11796480 concatenate_1[0][0]
__________________________________________________________________________________________________
batch_normalization_22 (BatchNo (None, 19, 19, 1024) 4096 conv2d_22[0][0]
__________________________________________________________________________________________________
leaky_re_lu_22 (LeakyReLU) (None, 19, 19, 1024) 0 batch_normalization_22[0][0]
__________________________________________________________________________________________________
conv2d_23 (Conv2D) (None, 19, 19, 425) 435625 leaky_re_lu_22[0][0]
==================================================================================================
Total params: 50,983,561
Trainable params: 50,962,889
Non-trainable params: 20,672
__________________________________________________________________________________________________
该模型将预处理的一批输入图像(shape:(m,608,608,3))转换为tensor类型,维度为(m,19,19,5,85)。
- 将模型的输出转换为边界框
yolo_model的输出是一个(m,19,19,5,85)的tensor变量,它需要进行处理和转换。
yolo_outputs = yolo_head(yolo_model.output, anchors, len(class_names))
yolo_outputs
添加进了计算图中,这4个tensor变量已准备好用作yolo_eval
函数的输入。
- 在实际图像中运行计算图
yolo_model.input
是yolo_model
的输入,yolo_model.output
是yolo_model
的输出。
yolo_model.output
会让yolo_head
进行处理,这个函数最后输出yolo_outputs
yolo_outputs
会让一个过滤函数yolo_eval
进行处理,然后输出预测:scores、 boxes、 classes
现在我们要实现predict()
函数,使用它来对图像进行预测,需要运行TensorFlow的Session会话,然后在计算图上计算scores、 boxes、 classes
,下面的代码可以帮预处理图像:
`image, image_data = yolo_utils.preprocess_image("images/" + image_file, model_image_size = (608, 608))`
image:用于绘制框的图像的Python(PIL)表示,这里不需要.
image_data:图像的numpy数组,这将是CNN的输入。
def predict(sess, image_file, is_show_info=True, is_plot=True):
"""
运行存储在sess的计算图以预测image_file的边界框,打印出预测的图与信息。
参数:
sess - 包含了YOLO计算图的TensorFlow/Keras的会话。
image_file - 存储在images文件夹下的图片名称
返回:
out_scores - tensor类型,维度为(None,),锚框的预测的可能值。
out_boxes - tensor类型,维度为(None,4),包含了锚框位置信息。
out_classes - tensor类型,维度为(None,),锚框的预测的分类索引。
"""
#图像预处理
image, image_data = yolo_utils.preprocess_image("images/" + image_file, model_image_size = (608, 608))
#运行会话并在feed_dict中选择正确的占位符.
out_scores, out_boxes, out_classes = sess.run([scores, boxes, classes], feed_dict = {yolo_model.input:image_data, K.learning_phase(): 0})
#打印预测信息
if is_show_info:
print("在" + str(image_file) + "中找到了" + str(len(out_boxes)) + "个锚框。")
#指定要绘制的边界框的颜色
colors = yolo_utils.generate_colors(class_names)
#在图中绘制边界框
yolo_utils.draw_boxes(image, out_scores, out_boxes, out_classes, class_names, colors)
#保存已经绘制了边界框的图
image.save(os.path.join("out", image_file), quality=100)
#打印出已经绘制了边界框的图
if is_plot:
output_image = scipy.misc.imread(os.path.join("out", image_file))
plt.imshow(output_image)
return out_scores, out_boxes, out_classes
实际测试
out_scores, out_boxes, out_classes = predict(sess, "test.jpg")
在test.jpg中找到了7个锚框。
car 0.60 (925, 285) (1045, 374)
car 0.66 (706, 279) (786, 350)
bus 0.67 (5, 266) (220, 407)
car 0.70 (947, 324) (1280, 705)
car 0.74 (159, 303) (346, 440)
car 0.80 (761, 282) (942, 412)
car 0.89 (367, 300) (745, 648)
- 批量绘制图
for i in range(1,121):
#计算需要在前面填充几个0
num_fill = int( len("0000") - len(str(1))) + 1
#对索引进行填充
filename = str(i).zfill(num_fill) + ".jpg"
print("当前文件:" + str(filename))
#开始绘制,不打印信息,不绘制图
out_scores, out_boxes, out_classes = predict(sess, filename,is_show_info=False,is_plot=False)
print("绘制完成!")