深度学习目标检测系列:一文弄懂YOLO算法|附Python源码
在之前的文章中,介紹了計算機視覺領域中目標檢測的相關方法——RCNN系列算法原理,以及Faster RCNN的實現。這些算法面臨的一個問題,不是端到端的模型,幾個構件拼湊在一起組成整個檢測系統,操作起來比較復雜,本文將介紹另外一個端到端的方法——YOLO算法,該方法操作簡便且仿真速度快,效果也不差。
YOLO算法是什么?
? ? ? ?YOLO框架(You Only Look Once)與RCNN系列算法不一樣,是以不同的方式處理對象檢測。它將整個圖像放在一個實例中,并預測這些框的邊界框坐標和及所屬類別概率。使用YOLO算法最大優的點是速度極快,每秒可處理45幀,也能夠理解一般的對象表示。
YOLO框架如何運作?
? ? ? ?在本節中,將介紹YOLO用于檢測給定圖像中的對象的處理步驟。
- 首先,輸入圖像:
?
- 然后,YOLO將輸入圖像劃分為網格形式(例如3 X 3):
?
- 最后,對每個網格應用圖像分類和定位處理,獲得預測對象的邊界框及其對應的類概率。
? ? ? ?整個過程是不是很清晰,下面逐一詳細介紹。首先需要將標記數據傳遞給模型以進行訓練。假設已將圖像劃分為大小為3 X 3的網格,且總共只有3個類別,分別是行人(c1)、汽車(c2)和摩托車(c3)。因此,對于每個單元格,標簽y將是一個八維向量:
其中:
- pc定義對象是否存在于網格中(存在的概率);
- bx、by、bh、bw指定邊界框;
- c1、c2、c3代表類別。如果檢測對象是汽車,則c2位置處的值將為1,c1和c3處的值將為0;
? ? ? ?假設從上面的例子中選擇第一個網格:
? ? ? ?由于此網格中沒有對象,因此pc將為零,此網格的y標簽將為:
? ? ? ???意味著其它值是什么并不重要,因為網格中沒有對象。下面舉例另一個有車的網格(c2=1):
? ? ? ?在為此網格編寫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。
非極大值抑制|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文件,其中包含運行此代碼所需的預訓練權重。
? ? ? ?首先定義一些函數,這些函數將用來選擇高于某個閾值的邊界框,并對其應用非極大值抑制。首先,導入所需的庫:
然后,實現基于概率和閾值過濾邊界框的函數:
def yolo_filter_boxes(box_confidence, boxes, box_class_probs, threshold = .6):box_scores = box_confidence*box_class_probsbox_classes = K.argmax(box_scores,-1)box_class_scores = K.max(box_scores,-1)filtering_mask = box_class_scores>thresholdscores = 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_areaiou = inter_area/union_areareturn 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_outputsboxes = 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算法,看看其工作效果:
在加載類別信息和預訓練模型之后,使用上面定義的函數來獲取·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 filedraw_boxes(image, out_scores, out_boxes, out_classes, class_names, colors)# Save the predicted bounding box on the imageimage.save(os.path.join("out", image_file), quality=90)# Display the results in the notebookoutput_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算法的全部內容
?
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的深度学习目标检测系列:一文弄懂YOLO算法|附Python源码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安全看得见,阿里云性能监控 ARMS 全
- 下一篇: 物联网落地三大困境破解