摘要: 本文是目标检测系列文章——YOLO算法,介绍其基本原理及实现细节,并用python实现,方便读者上手体验目标检测的乐趣。python
在以前的文章中,介绍了计算机视觉领域中目标检测的相关方法——RCNN系列算法原理,以及Faster RCNN的实现。这些算法面临的一个问题,不是端到端的模型,几个构件拼凑在一块儿组成整个检测系统,操做起来比较复杂,本文将介绍另一个端到端的方法——YOLO算法,该方法操做简便且仿真速度快,效果也不差。git
YOLO框架(You Only Look Once)与RCNN系列算法不同,是以不一样的方式处理对象检测。它将整个图像放在一个实例中,并预测这些框的边界框坐标和及所属类别几率。使用YOLO算法最大优的点是速度极快,每秒可处理45帧,也可以理解通常的对象表示。github
在本节中,将介绍YOLO用于检测给定图像中的对象的处理步骤。算法
整个过程是否是很清晰,下面逐一详细介绍。首先须要将标记数据传递给模型以进行训练。假设已将图像划分为大小为3 X 3的网格,且总共只有3个类别,分别是行人(c1)、汽车(c2)和摩托车(c3)。所以,对于每一个单元格,标签y将是一个八维向量:网络
其中:session
假设从上面的例子中选择第一个网格:框架
因为此网格中没有对象,所以pc将为零,此网格的y标签将为:dom
?
意味着其它值是什么并不重要,由于网格中没有对象。下面举例另外一个有车的网格(c2=1):ide
在为此网格编写y标签以前,首先要了解YOLO如何肯定网格中是否存在实际对象。大图中有两个物体(两辆车),所以YOLO将取这两个物体的中心点,物体将被分配到包含这些物体中心的网格中。中心点左侧网格的y标签会是这样的:函数
因为此网格中存在对象,所以pc将等于1,bx、by、bh、bw将相对于正在处理的特定网格单元计算。因为检测出的对象是汽车,因此c2=1
,c1和c3均为0。对于9个网格中的每个单元格,都具备八维输出向量。最终的输出形状为3X3X8
。
使用上面的例子(输入图像:100X100X3
,输出:3X3X8
),模型将按以下方式进行训练:
使用经典的CNN网络构建模型,并进行模型训练。在测试阶段,将图像传递给模型,通过一次前向传播就获得输出y。为了简单起见,使用3X3
网格解释这一点,但一般在实际场景中会采用更大的网格(好比19X19
)。
即便一个对象跨越多个网格,它也只会被分配到其中点所在的单个网格。能够经过增长更多网格来减小多个对象出如今同一网格单元中的概率。
如前所述,bx、by、bh和bw是相对于正在处理的网格单元计算而言的。下面经过一个例子来讲明这一点。以包含汽车的右边网格为例:
因为bx、by、bh和bw将仅相对于该网格计算。此网格的y标签将为:
因为这个网格中有一个对象汽车,因此pc=1
、c2=1
。如今,看看如何决定bx、by、bh和bw的取值。在YOLO中,分配给全部网格的坐标都以下图所示:
bx、by是对象相对于该网格的中心点的x和y坐标。在例子中,近似bx=0.4
和by=0.3
:
bh是边界框的高度与相应单元网格的高度之比,在例子中约为0.9:bh=0.9
,bw是边界框的宽度与网格单元的宽度之比,bw=0.5
。此网格的y标签将为:
请注意,bx和by将始终介于0和1之间,由于中心点始终位于网格内,而在边界框的尺寸大于网格尺寸的状况下,bh和bw能够大于1。
这里有一些思考的问题——如何判断预测的边界框是不是一个好结果(或一个坏结果)?单元格之间的交叉点,计算实际边界框和预测的边界框的并集交集。假设汽车的实际和预测边界框以下所示:
其中,红色框是实际的边界框,蓝色框是预测的边界框。如何判断它是不是一个好的预测呢?IoU将计算这两个框的并集交叉区域:
在本例中:
若是IoU大于0.5,就能够说预测足够好。0.5是在这里采起的任意阈值,也能够根据具体问题进行更改。阈值越大,预测就越准确。
还有一种技术能够显着提升YOLO的效果——非极大值抑制。
对象检测算法最多见的问题之一是,它不是一次仅检测出一次对象,而可能得到屡次检测结果。假设:
上图中,汽车不止一次被识别,那么如何断定边界框呢。非极大值抑能够解决这个问题,使得每一个对象只能进行一次检测。下面了解该方法的工做原理。
以上就是非极大值抑制的所有内容,总结一下关于非极大值抑制算法的要点:
在上述内容中,每一个网格只能识别一个对象。可是若是单个网格中有多个对象呢?这就行须要了解 Anchor Boxes的概念。假设将下图按照3X3
网格划分:
获取对象的中心点,并根据其位置将对象分配给相应的网格。在上面的示例中,两个对象的中心点位于同一网格中:
上述方法只会得到两个边界框其中的一个,可是若是使用Anchor Boxes,可能会输出两个边界框!咱们该怎么作呢?首先,预先定义两种不一样的形状,称为Anchor Boxes。对于每一个网格将有两个输出。这里为了易于理解,这里选取两个Anchor Boxes,也能够根据实际状况增长Anchor Boxes的数量:
前8行属于Anchor Boxes1,其他8行属于Anchor Boxes2。基于边界框和框形状的类似性将对象分配给Anchor Boxes。因为Anchor Boxes1的形状相似于人的边界框,后者将被分配给Anchor Boxes1,而且车将被分配给Anchor Boxes2.在这种状况下的输出,将是3X3X16
大小。
所以,对于每一个网格,能够根据Anchor Boxes的数量检测两个或更多个对象。
在本节中,首先介绍如何训练YOLO模型,而后是新的图像进行预测。
训练模型时,输入数据是由图像及其相应的y标签构成。样例以下:
假设每一个网格有两个Anchor Boxes,并划分为3X3
网格,而且有3个不一样的类别。所以,相应的y标签具备3X3X16
的形状。训练过程的完成方式就是将特定形状的图像映射到对应3X3X16
大小的目标。
对于每一个网格,模型将预测·3X3X16·大小的输出。该预测中的16个值将与训练标签的格式相同。前8个值将对应于Anchor Boxes1,其中第一个值将是该网络中对象的几率,2-5的值将是该对象的边界框坐标,最后三个值代表对象属于哪一个类。以此类推。
最后,非极大值抑制方法将应用于预测框以得到每一个对象的单个预测结果。
如下是YOLO算法遵循的确切维度和步骤:
输出的最后两个维度被展平以得到(19,19,425)的输出量:
本节中用于实现YOLO的代码来自Andrew NG的GitHub存储库,须要下载此zip文件,其中包含运行此代码所需的预训练权重。
首先定义一些函数,这些函数将用来选择高于某个阈值的边界框,并对其应用非极大值抑制。首先,导入所需的库:
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 skimage.transform import resize from keras import backend as K from keras.layers import Input, Lambda, Conv2D from keras.models import load_model, Model from yolo_utils import read_classes, read_anchors, generate_colors, preprocess_image, draw_boxes, scale_boxes from yad2k.models.keras_yolo import yolo_head, yolo_boxes_to_corners, preprocess_true_boxes, yolo_loss, yolo_body %matplotlib inline
而后,实现基于几率和阈值过滤边界框的函数:
def yolo_filter_boxes(box_confidence, boxes, box_class_probs, threshold = .6): box_scores = box_confidence*box_class_probs box_classes = K.argmax(box_scores,-1) box_class_scores = K.max(box_scores,-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, boxes, classes
以后,实现计算IoU的函数:
def iou(box1, box2): xi1 = max(box1[0],box2[0]) yi1 = max(box1[1],box2[1]) xi2 = min(box1[2],box2[2]) yi2 = min(box1[3],box2[3]) inter_area = (yi2-yi1)*(xi2-xi1) box1_area = (box1[3]-box1[1])*(box1[2]-box1[0]) box2_area = (box2[3]-box2[1])*(box2[2]-box2[0]) union_area = box1_area+box2_area-inter_area iou = inter_area/union_area return 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
随机初始化下大小为(19,19,5,85)的输出向量:
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))
最后,实现一个将CNN的输出做为输入并返回被抑制的边界框的函数:
def yolo_eval(yolo_outputs, image_shape = (720., 1280.), max_boxes=10, score_threshold=.6, iou_threshold=.5): 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, threshold = score_threshold) boxes = scale_boxes(boxes, image_shape) scores, boxes, classes = yolo_non_max_suppression(scores, boxes, classes, max_boxes, iou_threshold) return scores, boxes, classes
使用yolo_eval函数对以前建立的随机输出向量进行预测:
scores, boxes, classes = yolo_eval(yolo_outputs) with tf.Session() as test_b: print("scores[2] = " + str(scores[2].eval())) print("boxes[2] = " + str(boxes[2].eval())) print("classes[2] = " + str(classes[2].eval()))
score
表示对象在图像中的可能性,boxes
返回检测到的对象的(x1,y1,x2,y2)坐标,classes
表示识别对象所属的类。
如今,在新的图像上使用预训练的YOLO算法,看看其工做效果:
sess = K.get_session() class_names = read_classes("model_data/coco_classes.txt") anchors = read_anchors("model_data/yolo_anchors.txt") yolo_model = load_model("model_data/yolo.h5")
在加载类别信息和预训练模型以后,使用上面定义的函数来获取·yolo_outputs·。
yolo_outputs = yolo_head(yolo_model.output, anchors, len(class_names))
以后,定义一个函数来预测边界框并在图像上标记边界框:
def predict(sess, image_file): image, image_data = preprocess_image("images/" + image_file, model_image_size = (608, 608)) out_scores, out_boxes, out_classes = sess.run([scores, boxes, classes], feed_dict={yolo_model.input: image_data, K.learning_phase(): 0}) print('Found {} boxes for {}'.format(len(out_boxes), image_file)) # Generate colors for drawing bounding boxes. colors = generate_colors(class_names) # Draw bounding boxes on the image file draw_boxes(image, out_scores, out_boxes, out_classes, class_names, colors) # Save the predicted bounding box on the image image.save(os.path.join("out", image_file), quality=90) # Display the results in the notebook output_image = scipy.misc.imread(os.path.join("out", image_file)) plt.figure(figsize=(12,12)) imshow(output_image) return out_scores, out_boxes, out_classes
接下来,将使用预测函数读取图像并进行预测:
img = plt.imread('images/img.jpg') image_shape = float(img.shape[0]), float(img.shape[1]) scores, boxes, classes = yolo_eval(yolo_outputs, image_shape)
最后,输出预测结果:
out_scores, out_boxes, out_classes = predict(sess, "img.jpg")
以上就是YOLO算法的所有内容,更多详细内容能够关注darknet的官网。