使用流行的YOLO框架的对象检测实用指南

Introduction

如果我们简单地采用已经设计的框架,执行它并获得理想的结果,我们的生活会变得多么容易? 最小努力,最大奖励。 这不是我们在任何职业中所追求的吗?

能够成为我们机器学习社区的一员,我感到非常幸运,即使是顶尖的科技巨头也拥抱开源技术。 当然,在实施之前理解和掌握概念很重要,但是当顶级行业数据科学家和研究人员为您奠定基础工作时,它总是有用的。

对于像计算机视觉这样的深度学习领域尤其如此。 并非每个人都有从头开始构建DL模型的计算资源。 这就是预定义框架和预定模型派上用场的地方。 在本文中,我们将看一个这样的对象检测框架 - YOLO。 这是一个非常快速和准确的框架,我们很快就会看到。
在这里插入图片描述

到目前为止,我们在详细介绍对象检测的一系列帖子(下面的链接)中,我们已经看到了各种使用的算法,以及我们如何使用R-CNN系列算法检测图像中的对象并预测边界框。 我们还研究了Python中Faster-RCNN的实现。

在这里的第3部分中,我们将了解YOLO的原因,为什么要将它用于其他对象检测算法,以及YOLO使用的不同技术。 一旦我们彻底理解了这个概念,我们就会用Python实现它。 它是获取宝贵知识然后以实际操作方式应用的理想指南。

Table of Contents

  • 什么是YOLO,为什么它有用?
  • YOLO框架如何运作?
  • 如何编码边界框?
  • 联盟和非最大抑制的交叉点
  • 锚箱
  • 结合所有以上的想法
  • 在Python中实现YOLO## What is YOLO and Why is it Useful?

我们在第1部分中看到的R-CNN技术系列主要使用区域来定位图像中的对象。网络不会查看整个图像,只会查看图像中包含对象的可能性较高的部分。

另一方面,YOLO框架(You Only Look Once)以不同的方式处理对象检测。它将整个图像放在一个实例中,并预测这些框的边界框坐标和类概率。使用YOLO的最大优点是速度极快 - 速度极快,每秒可处理45帧。 YOLO也理解广义对象表示。

这是用于对象检测的最佳算法之一,并且已经显示出与R-CNN算法相比类似的性能。在接下来的部分中,我们将了解YOLO算法中使用的不同技术。以下解释的灵感来自于Andrew NG的对象检测课程,该课程帮助我理解了YOLO的工作。

How does the YOLO Framework Function?

现在我们已经掌握了为什么YOLO是一个如此有用的框架,让我们跳进它的实际工作方式。 在本节中,我已经提到了YOLO用于检测给定图像中的对象的步骤。

  • YOLO首先拍摄输入图像:
    在这里插入图片描述
  • 然后框架将输入图像划分为网格(例如3 X 3网格):
    在这里插入图片描述
  • 图像分类和定位应用于每个网格。 然后,YOLO预测对象的边界框及其对应的类概率(当然,如果有的话)。

很简单,不是吗? 让我们分解每一步,以更细致地理解我们刚刚学到的东西。

我们需要将标记数据传递给模型以进行训练。 假设我们已将图像划分为大小为3 X 3的网格,并且总共有3个类,我们希望将对象分类。 让我们说这些课程分别是行人,汽车和摩托车。 因此,对于每个网格单元格,标签y将是一个八维向量:

在这里插入图片描述
这里,

  • pc定义对象是否存在于网格中(这是概率)
  • bx,by,bh,bw如果有对象,则指定边界框
  • c1,c2,c3代表类。 因此,如果对象是汽车,则c2将为1,c1和c3将为0,依此类推

假设我们从上面的例子中选择第一个网格:
在这里插入图片描述
由于此网格中没有对象,因此pc将为零,此网格的y标签将为:
在这里插入图片描述

这里,’?'表示bx,by,bh,bw,c1,c2和c3包含的并不重要,因为网格中没有对象。 让我们采取另一个我们有车的网格(c2 = 1):
在这里插入图片描述
在为此网格编写y标签之前,首先要了解YOLO如何确定网格中是否存在实际对象,这一点很重要。 在上图中,有两个对象(两个车),因此YOLO将获取这两个对象的中点,这些对象将被分配给包含这些对象中点的网格。 与汽车中心左侧网格的y标签将是:
在这里插入图片描述

由于此网格中有一个对象,pc将等于1. bx,by,bh,bw将相对于我们正在处理的特定网格单元计算。 由于汽车是第二类,c2 = 1且c1和c3 = 0.因此,对于9个网格中的每一个,我们将具有八维输出向量。 此输出的形状为3 X 3 X 8。

所以现在我们有一个输入图像,它是相应的目标矢量。 使用上面的例子(输入图像 - 100 X 100 X 3,输出 - 3 X 3 X 8),我们的模型将按如下方式进行训练:
在这里插入图片描述

我们将运行前向和后向传播来训练我们的模型。 在测试阶段,我们将图像传递给模型并向前传播直到我们得到输出y。 为了简单起见,我在这里使用3 X 3网格解释了这一点,但通常在现实场景中我们采用更大的网格(可能是19 X 19)。

即使一个对象跨越多个网格,它也只会被分配到其中点所在的单个网格。 我们可以通过增加更多的网格(例如,19 X 19)来减少多个对象出现在同一网格单元中的几率。

How to Encode Bounding Boxes?

正如我之前提到的,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。

在下一节中,我们将研究更多可能有助于我们使该算法的性能更好的想法。

Union 和非最大抑制的交叉点

这里有一些思考的问题 - 我们如何判断预测的边界框是否给我们一个好结果(或一个坏结果)? 这就是联盟上的交叉点出现的情况。 它计算实际边界框和预测的绑定框的并集交集。 考虑汽车的实际和预测边界框,如下所示:
在这里插入图片描述
这里,红色框是实际的边界框,蓝色框是预测的框。 我们如何判断它是否是一个好的预测? IoU或Union的交叉点将计算这两个框的并集交叉区域。 那个区域将是:
在这里插入图片描述

IoU =交叉区域/联合区域,即

IoU =黄色框的面积/绿色框的面积

如果IoU大于0.5,我们可以说预测足够好。 0.5是我们在这里采取的任意阈值,但可以根据您的具体问题进行更改。 直觉上,您增加阈值越多,预测就越好。

还有一种技术可以显着提高YOLO的输出 - 非最大抑制。

对象检测算法最常见的问题之一是,它不是仅检测一次对象,而是可以多次检测到它。 考虑下面的图像:

在这里插入图片描述
在这里,汽车被识别不止一次。 非最大抑制技术可以清除它,这样我们每个对象只能进行一次检测。 让我们看看这种方法是如何工作的。

1.它首先查看与每次检测相关的概率并采用最大的概率。 在上图中,0.9是最高概率,因此将首先选择概率为0.9的方框:
在这里插入图片描述
2.现在,它会查看图像中的所有其他框。 具有当前盒子的高IoU的盒子被抑制。 因此,在我们的示例中,具有0.6和0.7概率的框将被抑制:在这里插入图片描述
3.在框被抑制后,它会从概率最高的所有框中选择下一个框,在我们的例子中为0.8:
在这里插入图片描述
4.再次,它会看到这个盒子的IoU与其余的盒子并用高IoU压缩盒子:在这里插入图片描述

5.我们重复这些步骤,直到选中或压缩所有方框,然后我们得到最后的边界框:在这里插入图片描述
这就是Non-Max Suppression的全部内容。 我们以最大概率获取框并抑制具有非最大概率的近距离框。 让我们快速总结一下我们在本节中看到的关于非最大抑制算法的要点:

  • 丢弃概率小于或等于预定阈值(例如0.5)的所有方框
  • 对于剩余的盒子:
    • 选择概率最高的方框并将其作为输出预测
    • 使用上述步骤中的输出框丢弃任何其他IoU大于阈值的盒子
  • 重复步骤2,直到所有框都被视为输出预测或被丢弃
    我们可以使用另一种方法来改进YOLO算法的执行 - 让我们检查一下!

Anchor Boxes

我们已经看到每个网格只能识别一个对象。 但是如果单个网格中有多个对象呢? 实际情况往往是如此。 这引导我们了解锚箱的概念。 考虑以下图像,分为3 X 3网格:
在这里插入图片描述
还记得我们如何将对象分配给网格? 我们取对象的中点并根据其位置将对象分配给相应的网格。 在上面的示例中,两个对象的中点位于同一网格中。 这是对象的实际边界框的方式:
在这里插入图片描述

我们只会获得两个盒子中的一个,无论是汽车还是人。 但是如果我们使用锚箱,我们可能会输出两个箱子! 我们该怎么做呢? 首先,我们预先定义了两种不同的形状,称为锚盒或锚盒形状。 现在,对于每个网格,我们将有两个输出,而不是一个输出。 我们总是可以增加锚箱的数量。 我在这里采取了两个使这个概念易于理解:
在这里插入图片描述
这是没有锚框的YOLO的y标签如下所示:
在这里插入图片描述

如果我们有2个锚箱,您认为y标签会是什么? 我想让你花点时间思考一下,然后再进一步阅读。 得到它了? y标签将是:在这里插入图片描述

前8行属于锚框1,其余8属于锚框2.基于边界框和锚框形状的相似性将对象分配给锚框。 由于锚箱1的形状类似于人的边界框,后者将被分配给锚箱1,并且车将被分配给锚箱2.在这种情况下的输出,而不是3 X 3 X 8 (使用3 X 3网格和3个类别)将是3 X 3 X 16(因为我们使用2个锚点)。

因此,对于每个网格,我们可以根据锚点的数量检测两个或更多个对象。 让我们结合到目前为止所涵盖的所有想法,并将它们整合到YOLO框架中。

Combining the Ideas

在本节中,我们将首先看到YOLO模型是如何训练的,然后是如何对新的和以前看不见的图像进行预测。

Training

训练我们模型的输入显然是图像及其相应的y标签。 让我们看一个图像并制作它的y标签:
在这里插入图片描述

考虑我们使用每个网格有两个锚点的3 X 3网格的场景,并且有3个不同的对象类。 因此,相应的y标签将具有3 X 3 X 16的形状。现在,假设我们每个网格使用5个锚箱并且类的数量已经增加到5.因此目标将是3 X 3 X 10 X 5 = 3 X 3 X 50.这就是训练过程的完成方式 - 拍摄特定形状的图像并将其映射到3 X 3 X 16目标(这可能会根据网格大小,锚箱数量和 类数量)。

Testing

新图像将被划分为我们在训练期间选择的相同数量的网格。对于每个网格,模型将预测形状3 X 3 X 16的输出(假设这是训练时间内目标的形状)。此预测中的16个值将与训练标签的格式相同。前8个值将对应于锚框1,其中第一个值将是该网格中对象的概率。值2-5将是该对象的边界框坐标,最后三个值将告诉我们该对象属于哪个类。接下来的8个值将用于锚箱2并且具有相同的格式,即,首先是概率,然后是边界框坐标,最后是类。

最后,非最大抑制技术将应用于预测的框以获得每个对象的单个预测。

这使我们理解YOLO算法如何工作的理论方面的结束,从训练模型开始,然后为对象生成预测框。以下是YOLO算法遵循的确切维度和步骤:

  • 获取形状的输入图像(608,608,3)
  • 将此图像传递给卷积神经网络(CNN),该网络返回(19,19,5,85)维输出
  • 上面输出的最后两个维度被展平以获得(19,19,425)的输出量:
    • 这里,19 X 19网格的每个单元返回425个数字
    • 425 = 5 * 85,其中5是每个网格的锚箱数
    • 85 = 5 + 80,其中5是(pc,bx,by,bh,bw),80是我们想要检测的类数
  • 最后,我们进行IoU和非最大抑制以避免选择重叠框

Implementing YOLO in Python

是时候启动我们的Jupyter笔记本(或您首选的IDE),最后以代码的形式实现我们的学习! 这是我们到目前为止所积累的,所以让我们开球。

我们将在本节中看到的用于实现YOLO的代码来自Andrew NG的GitHub存储库关于深度学习。 您还需要下载此zip文件,其中包含运行此代码所需的预训练重量。

让我们首先定义一些函数,这些函数将帮助我们选择高于某个阈值的框,找到IoU,并对它们应用非最大抑制。 然而,在其他所有事情之前,我们将首先导入所需的库:

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

我们现在具有计算IoU并执行非最大抑制的功能。 我们得到CNN形状的输出(19,19,5,85)。 因此,我们将创建一个随机的形状体积(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()))

在这里插入图片描述

“得分”表示对象在卷中的可能性。 '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是一种先进的物体检测算法,具有极快的速度和准确性
  • 我们将输入图像发送到输出19 X 19 X 5 X 85尺寸体积的CNN。
  • 这里,网格大小为19 X 19,每个网格包含5个框
  • 我们使用Non-Max Suppression过滤所有框,仅保留准确的框,并消除重叠框

YOLO是我最喜欢的框架之一,我相信一旦你在自己的机器上实现代码,你就会明白为什么。 使用流行的计算机视觉算法,这是一个很好的方法。 如果您对本指南有任何疑问或反馈,请在下面的评论部分与我联系。

猜你喜欢

转载自blog.csdn.net/weixin_41697507/article/details/89438030