1. 程式人生 > >YOLO-v3模型引數anchor設定

YOLO-v3模型引數anchor設定

1. 背景知識

在YOLO-v2版本中就引入了anchor box的概念,極大增加了目標檢測的效能。但是在訓練自己資料的時候還是用模型中原有的anchor設定顯然是有點不合適的,那麼就涉及到根據自己的訓練資料來設定anchor。

那麼,首先我們需要知道anchor的本質是什麼,本質是SPP(spatial pyramid pooling)思想的逆向。而SPP本身是做什麼的呢,就是將不同尺寸的輸入resize成為相同尺寸的輸出。所以SPP的逆向就是,將相同尺寸的輸出,倒推得到不同尺寸的輸入。

接下來是anchor的視窗尺寸,這個不難理解,三個面積尺寸(128225625122128^2,256^2,512^2

),然後在每個面積尺寸下,取三種不同的長寬比例1:1,1:2,2:1(1:1,1:2,2:1).這樣一來,我們得到了一共9種面積尺寸各異的anchor。示意圖如下: 在這裡插入圖片描述 那麼anchor在目標檢測中是怎麼使用的呢?那就先來看Faster R-CNN中的運用,下面是整個網路的結構圖: 在這裡插入圖片描述 利用anchor是從第二列這個位置開始進行處理,這個時候,原始圖片已經經過一系列卷積層和池化層以及relu,得到了這裡的 feature:51x39x256(256是層數)在這個特徵引數的基礎上,通過一個3x3的滑動視窗,在這個51x39的區域上進行滑動,stride=1,padding=2,這樣一來,滑動得到的就是51x39個3x3的視窗。對於每個3x3的視窗,作者就計算這個滑動視窗的中心點所對應的原始圖片的中心點。然後作者假定,這個3x3視窗,是從原始圖片上通過SPP池化得到的,而這個池化的區域的面積以及比例,就是一個個的anchor。換句話說,對於每個3x3視窗,作者假定它來自9種不同原始區域的池化,但是這些池化在原始圖片中的中心點,都完全一樣。這個中心點,就是剛才提到的,3x3視窗中心點所對應的原始圖片中的中心點。如此一來,在每個視窗位置,我們都可以根據9個不同長寬比例、不同面積的anchor,逆向推匯出它所對應的原始圖片中的一個區域,這個區域的尺寸以及座標,都是已知的。而這個區域,就是我們想要的 proposal。所以我們通過滑動視窗和anchor,成功得到了 51x39x9 個原始圖片的proposal。接下來,每個proposal我們只輸出6個引數:每個 proposal 和 ground truth 進行比較得到的前景概率和背景概率(2個引數)(對應圖上的 cls_score);由於每個 proposal 和 ground truth 位置及尺寸上的差異,從 proposal 通過平移放縮得到 ground truth 需要的4個平移放縮引數(對應圖上的 bbox_pred)。

2. Anchor先驗引數計算

這裡計算訓練資料的anchor先驗直接使用的是轉好的資料,即是已經轉好的可直接訓練的資料。這裡使用的程式碼參考的是這個倉庫kmeans-anchor-boxes。各位可以到倉庫裡面下載裡面的KMeans檔案就可以了,這裡給出我使用的部分程式碼:

# -*- coding=utf-8 -*-
import glob
import os
import sys
import xml.etree.ElementTree as ET
import numpy as np
from kmeans import kmeans, avg_iou

# 根資料夾
ROOT_PATH = '/data/DataBase/YOLO_Data/V3_DATA/'
# 聚類的數目
CLUSTERS = 6
# 模型中影象的輸入尺寸,預設是一樣的
SIZE = 640

# 載入YOLO格式的標註資料
def load_dataset(path):
    jpegimages = os.path.join(path, 'JPEGImages')
    if not os.path.exists(jpegimages):
        print('no JPEGImages folders, program abort')
        sys.exit(0)
    labels_txt = os.path.join(path, 'labels')
    if not os.path.exists(labels_txt):
        print('no labels folders, program abort')
        sys.exit(0)

    label_file = os.listdir(labels_txt)
    print('label count: {}'.format(len(label_file)))
    dataset = []

    for label in label_file:
        with open(os.path.join(labels_txt, label), 'r') as f:
            txt_content = f.readlines()

        for line in txt_content:
            line_split = line.split(' ')
            roi_with = float(line_split[len(line_split)-2])
            roi_height = float(line_split[len(line_split)-1])
            if roi_with == 0 or roi_height == 0:
                continue
            dataset.append([roi_with, roi_height])
            # print([roi_with, roi_height])

    return np.array(dataset)

data = load_dataset(ROOT_PATH)
out = kmeans(data, k=CLUSTERS)

print(out)
print("Accuracy: {:.2f}%".format(avg_iou(data, out) * 100))
print("Boxes:\n {}-{}".format(out[:, 0] * SIZE, out[:, 1] * SIZE))

ratios = np.around(out[:, 0] / out[:, 1], decimals=2).tolist()
print("Ratios:\n {}".format(sorted(ratios)))

經過執行之後得到一組如下資料:

[[0.21203704 0.02708333]
 [0.34351852 0.09375   ]
 [0.35185185 0.06388889]
 [0.29513889 0.06597222]
 [0.24652778 0.06597222]
 [0.24861111 0.05347222]]
Accuracy: 89.58%
Boxes:
 [135.7037037  219.85185185 225.18518519 188.88888889 157.77777778
 159.11111111]-[17.33333333 60.         40.88888889 42.22222222 42.22222222 34.22222222]

其中的Boxes就是得到的anchor引數,以上面給出的計算結果為例,最後的anchor引數設定為

anchors = 135,17,  219,60,  225,40,  188,42,  157,42,  159,34

由於KMeans演算法的結果對於初始點的選取敏感,因而每次執行的結果並不相同,只有Accuracy結果比較穩定點。至於那個anchor引數好,只有自己去嘗試了。但是在使用的時候,沒改過anchor經過一段時間的訓練之後網路也能夠適應,至於會不會對檢測的精度有影響,暫時還沒驗證,-_-||。。。

為什麼YOLOv2和YOLOv3的anchor大小有明顯區別? 在YOLOv2中,作者用最後一層feature map的相對大小來定義anchor大小。也就是說,在YOLOv2中,最後一層feature map大小為13X13(不同輸入尺寸的影象最後的feature map也不一樣的),相對的anchor大小範圍就在(0x0,13x13],如果一個anchor大小是9x9,那麼其在原圖上的實際大小是288x288。

而在YOLOv3中,作者又改用相對於原圖的大小來定義anchor,anchor的大小為(0x0,input_w x input_h]。所以,在兩份cfg檔案中,anchor的大小有明顯的區別。如下是作者自己的解釋:

So YOLOv2 I made some design choice errors, I made the anchor box size be relative to the feature size in the last layer. Since the network was down-sampling by 32. This means it was relative to 32 pixels so an anchor of 9x9 was actually 288px x 288px.
In YOLOv3 anchor sizes are actual pixel values. this simplifies a lot of stuff and was only a little bit harder to implement
https://github.com/pjreddie/darknet/issues/555#issuecomment-376190325

3. 參考