吴恩达第四课第三周分析

#吴恩达课后编程作业 keras使用

#引用块
import argparse
import os
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
import scipy.io
import scipy.misc
import numpy as np
import pandas as pd
import PIL
import tensorflow as tf
from keras import backend as K
from keras.layers import Input,Lambda,Conv2D
from keras.models import load_model,Model

from yad2k.models.keras_yolo import yolo_head,yolo_boxes_to_corners,preprocess_true_boxes,yolo_loss,yolo_body

import yolo_utils

matplotlib inline

#我们导入了Keras的后台,命名为K,这意味着我们将使用Keras框架

#假如你想让YOLO识别80个分类,你可以把分类标签从c1 - c80进行标记,或者把它变为80维的向量(80个数字),在对应位置上写上0或1.视频中我们使用的是后面的方案。因为YOLO的模型训练起来是比较久的,我们将使用预先训练好的权重来进行使用。

YOLO(‘you only look once’)在算法中,只看一次的机制使得它在预测时只需要进行一次前向传播,在使用非最大值抑制后,它与边界框一起输出识别对象。

----------2.1模型细节-----------
第一个你需要知道的事情是:

  • 输入的批量图片的维度为(m,606,608,3)

  • 输出是一个识别分类与边界框的列表。每个边界框由6个数字组成:(px,bx,by,bh,bw,c)。如果你将c放到80维的向量中,那么每个边界框就由85个数字组成。

     我们会使用5个锚框(anchor boxes),所以算法的流程大致是这样子的:图像输入(m,608,608,3) --> DEEP CNN --> 编码(m,19,19,5,85)
    
     我们也使用了5个锚框,19 * 19的单元格,所以每个单元格内有5个锚框的编码信息,锚框的组成是pc + px + py + ph + pw
     为了方便,我们将把最后的两个维度的数据进行展开,所以最后一步的编码由(m,19,19,5,85)变成(m,19,19,425)
     对于每个单元格的每个锚框而言,我们将计算下列元素的乘积,并提取该框包含某一类的概率。
     
     **目前认为这里前面讲的不够准确诶,应该是pc是这个小块每个框有对象的概率,然后c1 - c80应该不全为0,是每一类出现的概率。起码图上是这样画的,当然也有可能将最大的取0**
    
     每个单元格会输出5个锚框,总的来说,观察一次图像(一次向前传播),该模型需要预测:19 x 19 x 5 = 1805个锚框,不同的颜色代表不同的分类。
     在上图中我们只绘制了模型所猜测的高概率的锚框,但锚框依旧太多了,我们希望算法的输出过滤为检测的对象数量更少,要做到这一点,我们要使用非最大抑制。
    
  • 舍弃掉概率低的锚框(意思是格子算出来概率低的我们就不要)

  • 当几个锚框相互重叠并检测同一个物体时,只选择一个锚框。

--------------2.2 分类阈值过滤-----------------
现在我们要为阈值进行过滤,我们要去掉一些预测值低于预设值的锚框。模型共计会有19 x 19 x 5 x 85个数字,每一个锚框由85个数字组成(80个分类 + pc + px + py + ph + pw),将维度为(19,19,5,85)或者(19,19,425),此处取后者。

  • box_confidence : tensor类型,维度为(19 x 19,5,1),包含19x19单元格中每个单元格预测的5个锚框中的所有的锚框的pc(一些对象的置信概率)。 只有Pc
  • boxes : tensor类型,维度为(19x19,5,4),包含了所有的锚框的(px,py,ph,pw)。 每个小块中每个锚框的位置信息
  • box_class_probs:tensor类型,维度为(19 x 19,5,80),包含了所有单元格中所有锚框的所有对象(c1,c2,c3,…,c80)检测的概率

现在我们要实现函数yolo_filter_boxes():

  1. 计算对象的可能性
a = np.random.randn(19x19,5,1) #p_c
b = np.random.randn(19x19,5,4) #c_1 - c_80
c = a*b #计算后的维度会是(19x19,5,80)
  1. c中最后我们仅仅需要(19x19,5,1),即我们仅仅从每个锚框中选取最大概率的类别作为该锚框的判定类别(max),其对应的索引值(即每个锚框中最大值的索引)(argmax),也要保留下来
  2. 此时我们拥有了5个锚框,我们需要将五个锚框中概率低的锚框去掉:根据阈值创建掩码,[0.9,0.3,0.4,0.5,0.1]<0.4,返回的是[False,True,False,False,True],对于我们要保留的锚框,对应的掩码应该为True或者1.
  3. 使用tensorflow来对box_class_scores 、 boxes 、 box_classes进行掩码操作以过滤出我们想要的锚框。

这里强调一下boolean_mask函数(之前这里搞混主要由于这个函数输出维度的有趣):

  1. 若tensor与mask维度相同(以下面函数为例,均为3维),则输出维度为1维张量!!! 不是三维 !!谨记
  2. 若tensor为3维,mask为2维,Mask的维度必须和tensor的维度对齐。比如tensor是3维的,分别是(3,4,2)。则mask的维度可以是3~1维,长度必须是(3,4,2),(3,4)和(3,)。输出为2维向量,比tensor少一维
  3. 若tensor为3维,mask为1维,则输出为3维,和tensor维度一样!!!!
def yolo_filter_boxes(box_confidence,boxes,box_class_probs,threshold = 0.6):

	box_scores = box_confidence * box_class_probs
	box_classes = K.argmax(box_scores,axis = -1)
	box_class_scores = K.max(box_scores,axis = -1)

	filtering_mask = (box_class_scores >= threshold)
	scores = tf.boolean_mask(box_class_scores,filtering_mask)
	boxes = tf.boolean_mask(boxes,filtering_mask)
	classes = tf.boolean_mask(box_classes,filtering_mask)

	return scores,#(19x19,None)五个里留下框的概率
			boxes,#(19x19,None,4)留下框的位置及大小
			classes#(19x19,None)五个里留下框的类别

这个函数的作用主要是去除低概率的框,分两步:
先去除每个框中79个小概率,五个框留五个最大值
之后去除五个框没有达到阈值的框。

测试程序:

with tf.Session() as test_a:
	box_confidence = tf.random_normal([19,19,5,1],mean = 1,stddev = 4,seed = 1)
	boxes = tf.random_nomal([19,19,5,4],mean = 1,stddev = 4,seed = 1)
	box_class_probs = tf.random_normal([19,19,5,80],mean = 1,stddev = 4,seed = 1)
	scores,boxes,classes = yolo_filter_boxes(box_confidence,boxes,box_class_probs,threshold = 0.5)
	print('scores[2] = ' + str(scores[2].eval()))
	print('boxes[2] = '+ str(boxes[2].eval()))
	print('classes[2] = ' + str(classes[2].eval()))
	print('scores.shape = ' + str(scores.shape))
	print('boxes.shape = ' + str(boxes.shape))
	print('classes.shape = ' + str(classes.shape))
	
	test_a.close()

-------------2.3非最大值抑制--------------
即使我们通过阈值来过滤了一些得分比较低的分类,但是我们依旧会有很多的锚框被留了下来,第二个过滤器就是i让下图左边变成右边,我们叫它非最大值抑制。
非最大值抑制使用了一个非常重要的功能,叫做交互比(iou) 其实就是 IoU = A∩B / A∪B

要实现交并比函数iou(),要使用左上和右下角来定义方框(x1,y1,x2,y2)而不是使用中点+宽高的方式定义。
要计算矩形的面积我们需要用高度(y2 - y1)乘以(x2 - x1)
我们还需要找到两个锚框的交点的坐标(x1,y1,x2,y2)

def iou(box1,box2): #这里输入是两个框斜对角的坐标。
	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)
	
	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
	

测试码:

box1 = (2,1,4,3)
box2 = (1,2,3,4)

print('iou = ' + str(iou(box1,box2)))  

现在我们要实现非最大值抑制函数,关键步骤如下:

  1. 选择分值高的锚框
  2. 计算与其他框的重叠部分,并删除与iou_threshold相比重叠的框。
  3. 返回第一步,直到不再有比当前选中的框得分更低的框。

我们要实现的函数名为yolo_non_max_suppression(),使用Tensorflow实现,TensorFlow有两个内置函数用于实现非最大抑制(所以你实际上不需要使用你的iou()实现):

def yolo_non_max_suppression(scores,boxes,classes,max_boxes = 10,iou_threshold = 0.5):
	max_boxes_tensor = K.variable(max_boxes,dtype = 'int32)
	K.get_session().run(tf.variables_initializer([max_boxes_tensor]))

	nms_indices = tf.image.non_max_suppression(boxes,scores,max_boxes,iou_threshold)
	
	scores = K.gather(scores,nms_indices)
	boxes = K.gather(boxes,nms_indices)
	classes = K.gather(classes,nms_indices)

	return scores,boxes,classes

测试码

with tf.Session() as test_b:
	scores = tf.random_normal([54,],mean = 1,stdeev = 4,seed =1 )
	boxes = tf.random_normal([54,4],mean = 1,stddev = 4,seed = 1)
	classes = tf.random_noraml([54,],mean = 1,stddev = 4,seed = 1)

	print('scores[2] = ' + str(scores[2].eval()))
	print('boxes[2] = ' + str(boxes[2].eval()))
	print("classes[2] = " + str(classes[2].eval()))
    print("scores.shape = " + str(scores.eval().shape))
    print("boxes.shape = " + str(boxes.eval().shape))
    print("classes.shape = " + str(classes.eval().shape))
	test_b.close()

---------------2.4对所有框进行过滤----------------
现在我们要实现一个CNN(19x19x5x85)输出的函数,并使用刚刚实现的函数对所有框进行过滤。
我们要实现的函数名为yolo_eval(),它采用YOLO编码的输出,并使用分数阈值和NMS来过滤这些框,你必须知道最后一个实现的细节。有几种表示锚框的方式,例如通过它们的角或通过它们的中点和高度/宽度。

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)#这个函数的作用就是将其他像素的图片转换为本函数所需要的像素的图片。
def yolo_eval(yolo_outputs,image_shape = (720.,1280.),max_boxes = 10,score_threshold = 0.6,iou_threshold = 0.5):#这个函数就是将之前预测出来的结果,由于是几个一维张量(包含类别,分数,位置信息)一同表示,所以现在要整合他们,把他们作为我们常用的(pc,x,y,w,h,c1,...,c80)的类型
	'''
	将yolo编码的输出(很多锚框)转换为预测框以及它们的分数,框坐标和类。
	参数:
	yolo_outputs  - 编码模型的输出(对于维度为(608,608,3)的图片),包含4个tensor类型的变量:
	box_confidence :tensor类型,维度为(None,19,19,5,1)
	box_xy		   :tensor类型,维度为(None,19,19,5,2)
	box_hw		   :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
	

#测试码

with tf.Session() as test_c:
	yolo_outputs = (tf.random_normal([19,19,5,1],mean = 1,stddev = 4,seed = 1),
	tf.random_normal([19,19,5,2],mean = 1,stddev = 4,seed = 1),
	tf.random_normal([19,19,5,2],mean = 1,stddev = 4,seed = 1),
	tf.random_normal([19,19,5,80],mean = 1,stddev = 4,seed = 1))
	scores,boxes,classes = yolo_eval(yolo_outputs)

	print('scores[2] = ' + str(scores[2].eval))
	print("boxes[2] = " + str(boxes[2].eval()))
    print("classes[2] = " + str(classes[2].eval()))
    print("scores.shape = " + str(scores.eval().shape))
    print("boxes.shape = " + str(boxes.eval().shape))
    print("classes.shape = " + str(classes.eval().shape))

	test_c.close()

对YOLO的总结:

  • 输入图像为(608,608,3)

  • 输入的图像要先通过一个CNN模型,返回一个(19,19,5,85)的数据

  • 在对最后两维降维之后,输出的维度变为了(19,19,425):
    每个 19x19的单元格拥有425个数字。
    425 = 5x85,即每个单元格拥有5个锚框,每个锚框由5个基本信息+80个分类预测构成。
    85 = 5+80,其中5个基本信息是(pc,px,py,ph,pw),剩下80就是80个分类的预测。

  • 然后我们会根据以下规则选择锚框:
    预测分数阈值:丢弃分数低于阈值的分类的锚框。
    非最大值抑制:计算交并比,并避免选择重叠框。

  • 最后给出YOLO的最终输出。

----------------------3 测试已经训练好的YOLO模型----------------------
在这部分,我们将使用一个预先训练好的模型并在汽车检测数据集上进行测试。像往常一样,首先创建一个会话来启动计算图。

sess = K.get_session

-------3.1定义分类、锚框与图像维度------

class_names = yolo_utils.read_classes('model_data/coco_classes.txt')
anchors = yolo_utils.read_anchors('model_data_anchors.txt')
image_shape = (720.,1280.)

------3.2加载已经训练好的模型-----
yolo_model = load_model(‘model_data/yolov2.h5’)
yolo_model.summary()

------3.3将模型的输出转换为边界框-----
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函数的输入。
------3.4过滤锚框-----
yolo_outputs已经正确的格式为我们提供了yolo_model的所有预测框,我们现在已准备好执行过滤并仅选择最佳的锚框。现在让我们调用之前实现的yolo_eval()
------3.5在实际图像中运行计算图-----
我们之前已经创建了一个用于会话的sess,这里有一些回顾:

#1.yolo_model.input是yolo_model的输入,yolo_model.output是yolo_model的输出 ##这一步是进行CNN

#2.yolo_model.output会让yolo_head进行处理,这个函数最后输出yolo_outputs  ##这一步输出可以进入yolo_eval的格式

#3.yolo_outputs会让一个过滤函数yolo_eval进行处理,然后输出预测:scores、boxes、classes

#现在我们要实现predict()函数,使用它来对图像进行预测,我们需要运行tf的Session会话,然后在计算图上计算scores、boxes、classes,下面的代码可以帮你预处理图像
image,image_data = yolo_utils.preprocess_image('image/' + 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')

------------3.6批量绘制图------------

for i in range(1,121):

	#计算需要在前面填充几个0
	num_fill = int(len('0000') - len(str(i))) + 1
		
	#Python zfill() 方法返回指定长度的字符串,原字符串右对齐,前面填充0。
	fillname = str(i).zfill(num_fill) + '.jpg'
	print('当前文件:' + str(fillname))

	out_scores,out_boxes,out_classes = predict(sess,filename,is_show_info = False,is_plot = True)

print('绘制完成!')
发布了31 篇原创文章 · 获赞 0 · 访问量 677

猜你喜欢

转载自blog.csdn.net/ballzy/article/details/105442076