深度学习目标检测系列:一文弄懂YOLO算法|附Python源码

简介: 本文是目标检测系列文章——YOLO算法,介绍其基本原理及实现细节,并用python实现,方便读者上手体验目标检测的乐趣。

       在之前的文章中,介绍了计算机视觉领域中目标检测的相关方法——RCNN系列算法原理,以及Faster RCNN的实现。这些算法面临的一个问题,不是端到端的模型,几个构件拼凑在一起组成整个检测系统,操作起来比较复杂,本文将介绍另外一个端到端的方法——YOLO算法,该方法操作简便且仿真速度快,效果也不差。

1

YOLO算法是什么?

       YOLO框架(You Only Look Once)与RCNN系列算法不一样,是以不同的方式处理对象检测。它将整个图像放在一个实例中,并预测这些框的边界框坐标和及所属类别概率。使用YOLO算法最大优的点是速度极快,每秒可处理45帧,也能够理解一般的对象表示。

YOLO框架如何运作?

       在本节中,将介绍YOLO用于检测给定图像中的对象的处理步骤。

  • 首先,输入图像:

    2

  • 然后,YOLO将输入图像划分为网格形式(例如3 X 3):

    3

  • 最后,对每个网格应用图像分类和定位处理,获得预测对象的边界框及其对应的类概率。

       整个过程是不是很清晰,下面逐一详细介绍。首先需要将标记数据传递给模型以进行训练。假设已将图像划分为大小为3 X 3的网格,且总共只有3个类别,分别是行人(c1)、汽车(c2)和摩托车(c3)。因此,对于每个单元格,标签y将是一个八维向量:

4


其中:
  • pc定义对象是否存在于网格中(存在的概率);
  • bx、by、bh、bw指定边界框;
  • c1、c2、c3代表类别。如果检测对象是汽车,则c2位置处的值将为1,c1和c3处的值将为0;

       假设从上面的例子中选择第一个网格:

5


       由于此网格中没有对象,因此pc将为零,此网格的y标签将为:

6


        意味着其它值是什么并不重要,因为网格中没有对象。下面举例另一个有车的网格(c2=1):

7


       在为此网格编写y标签之前,首先要了解YOLO如何确定网格中是否存在实际对象。大图中有两个物体(两辆车),因此YOLO将取这两个物体的中心点,物体将被分配到包含这些物体中心的网格中。中心点左侧网格的y标签会是这样的:

8


       由于此网格中存在对象,因此pc将等于1,bx、by、bh、bw将相对于正在处理的特定网格单元计算。由于检测出的对象是汽车,所以 c2=1,c1和c3均为0。对于9个网格中的每一个单元格,都具有八维输出向量。最终的输出形状为 3X3X8
       使用上面的例子(输入图像: 100X100X3,输出: 3X3X8),模型将按如下方式进行训练:

9


       使用经典的CNN网络构建模型,并进行模型训练。在测试阶段,将图像传递给模型,经过一次前向传播就得到输出y。为了简单起见,使用 3X3网格解释这一点,但通常在实际场景中会采用更大的网格(比如 19X19)。
       即使一个对象跨越多个网格,它也只会被分配到其中点所在的单个网格。可以通过增加更多网格来减少多个对象出现在同一网格单元中的几率。

如何编码边界框?

       如前所述,bx、by、bh和bw是相对于正在处理的网格单元计算而言的。下面通过一个例子来说明这一点。以包含汽车的右边网格为例:

10


       由于bx、by、bh和bw将仅相对于该网格计算。此网格的y标签将为:

11


       由于这个网格中有一个对象汽车,所以 pc=1c2=1。现在,看看如何决定bx、by、bh和bw的取值。在YOLO中,分配给所有网格的坐标都如下图所示:

12


       bx、by是对象相对于该网格的中心点的x和y坐标。在例子中,近似 bx=0.4by=0.3

13


       bh是边界框的高度与相应单元网格的高度之比,在例子中约为0.9: bh=0.9,bw是边界框的宽度与网格单元的宽度之比, bw=0.5。此网格的y标签将为:

14


       请注意,bx和by将始终介于0和1之间,因为中心点始终位于网格内,而在边界框的尺寸大于网格尺寸的情况下,bh和bw可以大于1。

非极大值抑制|Non-Max Suppression

       这里有一些思考的问题——如何判断预测的边界框是否是一个好结果(或一个坏结果)?单元格之间的交叉点,计算实际边界框和预测的边界框的并集交集。假设汽车的实际和预测边界框如下所示:

15


       其中,红色框是实际的边界框,蓝色框是预测的边界框。如何判断它是否是一个好的预测呢?IoU将计算这两个框的并集交叉区域:

16

  • IoU =交叉面积/联合的面积;
  • 在本例中:

    • IoU =黄色面积/绿色面积;

       如果IoU大于0.5,就可以说预测足够好。0.5是在这里采取的任意阈值,也可以根据具体问题进行更改。阈值越大,预测就越准确。
       还有一种技术可以显着提高YOLO的效果——非极大值抑制。
       对象检测算法最常见的问题之一是,它不是一次仅检测出一次对象,而可能获得多次检测结果。假设:

17


       上图中,汽车不止一次被识别,那么如何判定边界框呢。非极大值抑可以解决这个问题,使得每个对象只能进行一次检测。下面了解该方法的工作原理。
  • 1.它首先查看与每次检测相关的概率并取最大的概率。在上图中,0.9是最高概率,因此首先选择概率为0.9的方框:

    18

  • 2.现在,它会查看图像中的所有其他框。与当前边界框较高的IoU的边界框将被抑制。因此,在示例中,0.6和0.7概率的边界框将被抑制:

    19

  • 3.在部分边界框被抑制后,它会从概率最高的所有边界框中选择下一个,在例子中为0.8的边界框:

    20

  • 4.再次计算与该边界框相连边界框的IoU,去掉较高IoU值的边界框:

    21

  • 5.重复这些步骤,得到最后的边界框:

    22

       以上就是非极大值抑制的全部内容,总结一下关于非极大值抑制算法的要点:

  • 丢弃概率小于或等于预定阈值(例如0.5)的所有方框;
  • 对于剩余的边界框:
  • 选择具有最高概率的边界框并将其作为输出预测;
  • 计算相关联的边界框的IoU值,舍去IoU大于阈值的边界框;
  • 重复步骤2,直到所有边界框都被视为输出预测或被舍弃;

Anchor Boxes

       在上述内容中,每个网格只能识别一个对象。但是如果单个网格中有多个对象呢?这就行需要了解 Anchor Boxes的概念。假设将下图按照3X3网格划分:

23


       获取对象的中心点,并根据其位置将对象分配给相应的网格。在上面的示例中,两个对象的中心点位于同一网格中:

24


       上述方法只会获得两个边界框其中的一个,但是如果使用Anchor Boxes,可能会输出两个边界框!我们该怎么做呢?首先,预先定义两种不同的形状,称为Anchor Boxes。对于每个网格将有两个输出。这里为了易于理解,这里选取两个Anchor Boxes,也可以根据实际情况增加Anchor Boxes的数量:

25

  • 没有Anchor Boxes的YOLO输出标签如下所示:

26

  • 有Anchor Boxes的YOLO输出标签如下所示:

27


       前8行属于Anchor Boxes1,其余8行属于Anchor Boxes2。基于边界框和框形状的相似性将对象分配给Anchor Boxes。由于Anchor Boxes1的形状类似于人的边界框,后者将被分配给Anchor Boxes1,并且车将被分配给Anchor Boxes2.在这种情况下的输出,将是 3X3X16大小。
       因此,对于每个网格,可以根据Anchor Boxes的数量检测两个或更多个对象。

结合思想

       在本节中,首先介绍如何训练YOLO模型,然后是新的图像进行预测。

训练

       训练模型时,输入数据是由图像及其相应的y标签构成。样例如下:

28


       假设每个网格有两个Anchor Boxes,并划分为 3X3网格,并且有3个不同的类别。因此,相应的y标签具有 3X3X16的形状。训练过程的完成方式就是将特定形状的图像映射到对应 3X3X16大小的目标。

测试

       对于每个网格,模型将预测·3X3X16·大小的输出。该预测中的16个值将与训练标签的格式相同。前8个值将对应于Anchor Boxes1,其中第一个值将是该网络中对象的概率,2-5的值将是该对象的边界框坐标,最后三个值表明对象属于哪个类。以此类推。
       最后,非极大值抑制方法将应用于预测框以获得每个对象的单个预测结果。
       以下是YOLO算法遵循的确切维度和步骤:

  • 准备对应的图像(608,608,3);
  • 将图像传递给卷积神经网络(CNN),该网络返回(19,19,5,85)维输出;
  • 输出的最后两个维度被展平以获得(19,19,425)的输出量:

    • 19×19网格的每个单元返回425个数字;
    • 425=5 * 85,其中5是每个网格的Anchor Boxes数量;
    • 85= 5+80,其中5表示(pc、bx、by、bh、bw),80是检测的类别数;
  • 最后,使用IoU和非极大值抑制去除重叠框;

YOLO算法实现

       本节中用于实现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()))

29


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")

31


以上就是YOLO算法的全部内容,更多详细内容可以关注 darknet的官网

作者信息

PULKIT SHARMA,机器学习和深度学习
本文由阿里云云栖社区组织翻译。
文章原标题《A Practical Guide to Object Detection using the Popular YOLO Framework – Part III (with Python codes)》,译者:海棠,审校:Uncle_LLD。
文章为简译,更为详细的内容,请查看原文

相关文章
|
5天前
|
机器学习/深度学习 运维 算法
【Python机器学习专栏】异常检测算法在Python中的实践
【4月更文挑战第30天】本文介绍了异常检测的重要性和在不同领域的应用,如欺诈检测和网络安全。文章概述了四种常见异常检测算法:基于统计、距离、密度和模型的方法。在Python实践中,使用scikit-learn库展示了如何实现这些算法,包括正态分布拟合、K-means聚类、局部异常因子(LOF)和孤立森林(Isolation Forest)。通过计算概率密度、距离、LOF值和数据点的平均路径长度来识别异常值。
|
5天前
|
机器学习/深度学习 数据可视化 算法
【Python机器学习专栏】t-SNE算法在数据可视化中的应用
【4月更文挑战第30天】t-SNE算法是用于高维数据可视化的非线性降维技术,通过最小化Kullback-Leibler散度在低维空间保持数据点间关系。其特点包括:高维到二维/三维映射、保留局部结构、无需预定义簇数量,但计算成本高。Python中可使用`scikit-learn`的`TSNE`类实现,结合`matplotlib`进行可视化。尽管计算昂贵,t-SNE在揭示复杂数据集结构上极具价值。
|
5天前
|
机器学习/深度学习 算法 数据挖掘
【Python机器学习专栏】关联规则学习:Apriori算法详解
【4月更文挑战第30天】Apriori算法是一种用于关联规则学习的经典算法,尤其适用于购物篮分析,以发现商品间的购买关联。该算法基于支持度和置信度指标,通过迭代生成频繁项集并提取满足阈值的规则。Python中可借助mlxtend库实现Apriori,例如处理购物篮数据,设置支持度和置信度阈值,找出相关规则。
|
5天前
|
机器学习/深度学习 算法 数据挖掘
【Python机器学习专栏】层次聚类算法的原理与应用
【4月更文挑战第30天】层次聚类是数据挖掘中的聚类技术,无需预设簇数量,能生成数据的层次结构。分为凝聚(自下而上)和分裂(自上而下)两类,常用凝聚层次聚类有最短/最长距离、群集平均和Ward方法。优点是自动确定簇数、提供层次结构,适合小到中型数据集;缺点是计算成本高、过程不可逆且对异常值敏感。在Python中可使用`scipy.cluster.hierarchy`进行实现。尽管有局限,层次聚类仍是各领域强大的分析工具。
|
5天前
|
机器学习/深度学习 算法 数据挖掘
【Python 机器学习专栏】K-means 聚类算法在 Python 中的实现
【4月更文挑战第30天】K-means 是一种常见的聚类算法,用于将数据集划分为 K 个簇。其基本流程包括初始化簇中心、分配数据点、更新簇中心并重复此过程直到收敛。在 Python 中实现 K-means 包括数据准备、定义距离函数、初始化、迭代和输出结果。虽然算法简单高效,但它需要预先设定 K 值,且对初始点选择敏感,可能陷入局部最优。广泛应用在市场分析、图像分割等场景。理解原理与实现对应用聚类分析至关重要。
|
5天前
|
机器学习/深度学习 算法 前端开发
【Python机器学习专栏】集成学习算法的原理与应用
【4月更文挑战第30天】集成学习通过组合多个基学习器提升预测准确性,广泛应用于分类、回归等问题。主要步骤包括生成基学习器、训练和结合预测结果。算法类型有Bagging(如随机森林)、Boosting(如AdaBoost)和Stacking。Python中可使用scikit-learn实现,如示例代码展示的随机森林分类。集成学习能降低模型方差,缓解过拟合,提高预测性能。
|
5天前
|
机器学习/深度学习 算法 Python
【Python 机器学习专栏】随机森林算法的性能与调优
【4月更文挑战第30天】随机森林是一种集成学习方法,通过构建多棵决策树并投票或平均预测结果,具有高准确性、抗过拟合、处理高维数据的能力。关键性能因素包括树的数量、深度、特征选择和样本大小。调优方法包括调整树的数量、深度,选择关键特征和参数优化。Python 示例展示了使用 GridSearchCV 进行调优。随机森林广泛应用于分类、回归和特征选择问题,是机器学习中的重要工具。
|
5天前
|
机器学习/深度学习 算法 数据可视化
【Python机器学习专栏】决策树算法的实现与解释
【4月更文挑战第30天】本文探讨了决策树算法,一种流行的监督学习方法,用于分类和回归。文章阐述了决策树的基本原理,其中内部节点代表特征判断,分支表示判断结果,叶节点代表类别。信息增益等标准用于衡量特征重要性。通过Python的scikit-learn库展示了构建鸢尾花数据集分类器的示例,包括训练、预测、评估和可视化决策树。最后,讨论了模型解释和特征重要性评估在优化中的作用。
|
6天前
|
机器学习/深度学习 算法 数据挖掘
机器学习--K近邻算法,以及python中通过Scikit-learn库实现K近邻算法API使用技巧
机器学习--K近邻算法,以及python中通过Scikit-learn库实现K近邻算法API使用技巧
|
6天前
|
机器学习/深度学习 算法 数据挖掘
【视频】支持向量机算法原理和Python用户流失数据挖掘SVM实例(下)
【视频】支持向量机算法原理和Python用户流失数据挖掘SVM实例(下)
14 0