深度學習目標檢測系列:一文弄懂YOLO算法|附Python源碼

摘要: 本文是目標檢測系列文章——YOLO算法,介紹其基本原理及實現細節,並用python實現,方便讀者上手體驗目標檢測的樂趣。python

       在以前的文章中,介紹了計算機視覺領域中目標檢測的相關方法——RCNN系列算法原理,以及Faster RCNN的實現。這些算法面臨的一個問題,不是端到端的模型,幾個構件拼湊在一塊兒組成整個檢測系統,操做起來比較複雜,本文將介紹另一個端到端的方法——YOLO算法,該方法操做簡便且仿真速度快,效果也不差。git

YOLO算法是什麼?

       YOLO框架(You Only Look Once)與RCNN系列算法不同,是以不一樣的方式處理對象檢測。它將整個圖像放在一個實例中,並預測這些框的邊界框座標和及所屬類別機率。使用YOLO算法最大優的點是速度極快,每秒可處理45幀,也可以理解通常的對象表示。github

YOLO框架如何運做?

       在本節中,將介紹YOLO用於檢測給定圖像中的對象的處理步驟。算法

  • 首先,輸入圖像:

  • 而後,YOLO將輸入圖像劃分爲網格形式(例如3 X 3):

  • 最後,對每一個網格應用圖像分類和定位處理,得到預測對象的邊界框及其對應的類機率。

       整個過程是否是很清晰,下面逐一詳細介紹。首先須要將標記數據傳遞給模型以進行訓練。假設已將圖像劃分爲大小爲3 X 3的網格,且總共只有3個類別,分別是行人(c1)、汽車(c2)和摩托車(c3)。所以,對於每一個單元格,標籤y將是一個八維向量:網絡

其中:session

  • pc定義對象是否存在於網格中(存在的機率);
  • bx、by、bh、bw指定邊界框;
  • c一、c二、c3表明類別。若是檢測對象是汽車,則c2位置處的值將爲1,c1和c3處的值將爲0;

       假設從上面的例子中選擇第一個網格:框架


       因爲此網格中沒有對象,所以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=1c2=1。如今,看看如何決定bx、by、bh和bw的取值。在YOLO中,分配給全部網格的座標都以下圖所示:

       bx、by是對象相對於該網格的中心點的x和y座標。在例子中,近似bx=0.4by=0.3

       bh是邊界框的高度與相應單元網格的高度之比,在例子中約爲0.9:bh=0.9,bw是邊界框的寬度與網格單元的寬度之比,bw=0.5。此網格的y標籤將爲:

       請注意,bx和by將始終介於0和1之間,由於中心點始終位於網格內,而在邊界框的尺寸大於網格尺寸的狀況下,bh和bw能夠大於1。

非極大值抑制|Non-Max Suppression

       這裏有一些思考的問題——如何判斷預測的邊界框是不是一個好結果(或一個壞結果)?單元格之間的交叉點,計算實際邊界框和預測的邊界框的並集交集。假設汽車的實際和預測邊界框以下所示:

       其中,紅色框是實際的邊界框,藍色框是預測的邊界框。如何判斷它是不是一個好的預測呢?IoU將計算這兩個框的並集交叉區域:

  • 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.重複這些步驟,獲得最後的邊界框:

       以上就是非極大值抑制的所有內容,總結一下關於非極大值抑制算法的要點:

  • 丟棄機率小於或等於預約閾值(例如0.5)的全部方框;
  • 對於剩餘的邊界框:
  • 選擇具備最高几率的邊界框並將其做爲輸出預測;
  • 計算相關聯的邊界框的IoU值,捨去IoU大於閾值的邊界框;
  • 重複步驟2,直到全部邊界框都被視爲輸出預測或被捨棄;

Anchor Boxes

       在上述內容中,每一個網格只能識別一個對象。可是若是單個網格中有多個對象呢?這就行須要瞭解 Anchor Boxes的概念。假設將下圖按照3X3網格劃分:

       獲取對象的中心點,並根據其位置將對象分配給相應的網格。在上面的示例中,兩個對象的中心點位於同一網格中:

       上述方法只會得到兩個邊界框其中的一個,可是若是使用Anchor Boxes,可能會輸出兩個邊界框!咱們該怎麼作呢?首先,預先定義兩種不一樣的形狀,稱爲Anchor Boxes。對於每一個網格將有兩個輸出。這裏爲了易於理解,這裏選取兩個Anchor Boxes,也能夠根據實際狀況增長Anchor Boxes的數量:

  • 沒有Anchor Boxes的YOLO輸出標籤以下所示:

  • 有Anchor Boxes的YOLO輸出標籤以下所示:

       前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算法遵循的確切維度和步驟:

  • 準備對應的圖像(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()))


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的官網

原文連接

相關文章
相關標籤/搜索