Anchor,為 錨點。
定義:錨點描述了在現實世界中一個固定的位置和方向,當用戶想要放置一個虛擬物體時在平面上時,需要定義一個錨點來確保AR可以跟蹤虛擬物體隨用戶運動推移所在的相對位置?;阱^點可以將虛擬物體錨定在現實空間中某一特定位置,進而實現用戶可以從不同位置和角度進行觀察。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1、選擇性搜索(Selective Search)
先介紹一下傳統的人臉識別算法,是怎么檢測出圖片中的人臉的?
以下圖為例,如果我們要檢測圖中小女孩的人臉位置,一個比較簡單暴力的方法就是滑窗,我們使用不同大小、不同長寬比的候選框在整幅圖像上進行窮盡式的滑窗,然后提取窗口內的特征(例如Haar、LBP、Hog等特征),再送入分類器(SVM、Adaboost等)判斷該窗口內包含的是否為人臉。這種方法簡單易理解,但是這類方法受限于手動設計的特征,召回率和準確率通常不是很高。
大名鼎鼎的RCNN 和 Fast RCNN依舊依賴滑窗來產生候選框,也就是Selective Search算法,該算法優化了候選框的生成策略,但仍舊會產生大量的候選框,導致即使Fast RCNN算法,在GPU上的速度也只有三、四幀每秒。
直到Faster RCNN的出現,提出了RPN網絡(Region Proposal Network),使用RPN直接預測出候選框的位置。用RPN代替Fast RCNN中Selective Search,RPN與object detection network共享卷積層,節省了大量選擇候選區域的時間。
RPN網絡一個最重要的概念就是anchor,啟發了后面的SSD和YOLOv2等算法,雖然SSD算法稱之為default box,也有算法叫做prior box,其實都是同一個概念,他們都是anchor的別稱。
2、什么是Anchor ?
2.1 Anchor
anchor到底是什么呢?如果我們用一句話概括——就是在圖像上預設好的不同大小,不同長寬比的參照框。(其實非常類似于上面的滑窗法所設置的窗口大?。?/p>
假設一個256x256大小的圖片,經過64、128和256倍下采樣,會產生4x4、2x2、1x1大小的特征圖,我們在這三個特征圖上每個點上都設置三個不同大小的anchor。
當然,這只是一個例子,實際的SSD模型,在300x300的輸入下,anchor數量也特別多,其在38x38、19x19、10x10、5x5、3x3、1x1的六個特征圖上,每個點分別設置4、6、6、6、6、4個不同大小和長寬比的anchor,所以一共有38x38x4+ 19x19x6+ 10x10x6+ 5x5x6+ 3x3x4+ 1x1x4= 8732個anchor。
在SSD中6層卷積層的每個特征圖的每個中心點會產生2個不同大小的正方形默認框,另外每設置一個aspect_ratio則會增加兩個長方形默認框,而文中代碼對于6層的aspect_ratio個數分別為1、2、2、2、1、1,所以這也就是為什么會產生4、6、6、6、4、4個默認框了。
aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2], [2]] 。paper中所給出的ar={1,2,3,1/2,1/3},這個比例是計算出來的。
我們不再需要計算Haar、Hog等特征,直接讓神經網絡輸出,每個anchor是否包含物體(anchor與物體Bounding Box有較大重疊,交并比IoU(Intersection-over-Union,IoU)較大),以及被檢測物體相對本anchor的中心點偏移以及長寬比例。
1、每個anchor認為自己是否含有物體的概率,
2、物體中心點與anchor自身的中心點位置的偏移量,
3、以及相對于anchor寬高的比例。
一般的目標檢測網絡可能有成千上萬個anchor,例如標準SSD在300x300輸入下有8732個anchor,在500x500下anchor數量過萬。
因為anchor的位置都是固定的,所以就可以很容易的換算出來實際物體的位置。以圖中的小貓為例,紅色的anchor就以99%的概率認為它是一只貓,并同時給出了貓的實際位置相對于該anchor的偏移量,這樣,我們將輸出解碼后就得到了實際貓的位置,如果它能通過NMS(非最大抑制,non-maximum suppression)篩選,它就能順利的輸出來。但是,綠色的anchor就認為它是貓的概率就很小,紫色的anchor雖然與貓有重疊,但是概率只有26%。
SSD的推理很簡單,根據anchor進行位置解碼,然后進行NMS過程,就完成了。
設置IoU閾值,例如大于0.5就認為是正樣本,否則是負樣本,(SSD算法中會給它強行分配一個IoU最大的anchor,即使IoU只有0.3)。因為anchor非常密集,所以SSD算法中,會有多個anchor與物體的IoU大于閾值,所以可能多個anchor都是對應同一個物體的正樣本。圖中的貓已經有了2個匹配的正樣本(藍色框)。
注意:在訓練的時候,anchor的大小和長寬比應與待檢測的物體尺度基本一致。
2.2 anchor_sizes從何而來?
2.2.1 FasterRCNN
FasterRCNN的RPN網絡部分,anchor為三個尺度{128, 256, 512},三個比例{1:1, 1:2, 2:1},所以一共9組anchor。
2.2.2 SSD
在SSD論文中,作者使用6組定位層,(6個用于分類和回歸的特征層(feature map),分別是'conv4_3', 'fc7', 'conv6_2', 'conv7_2', 'conv8_2', 'conv9_2'。每個定位層分別有6個anchor(不過第一和最后一個定位層只有4個)。一個尺度,分別有1:1、1:2、2:1、1:3、3:1五個不同寬高比,再加一個后面特征圖的anchor尺度與該特征圖的尺度相乘再開根號,也就是:
同樣是1:1比例,所以一共5+1=6組anchor。
但是,大家查看SSD開源的代碼,作者給出的得anchor大小并不是這么計算得到的。而是30、60、111、162、213、264(再加一個315)這7個尺度。
ssd_pascal.py
“#參數生成先驗。
#輸入圖像的最小尺寸
min_dim = 300 #######維度
# conv4_3 ==> 38 x 38
# fc7 ==> 19 x 19
# conv6_2 ==> 10 x 10
# conv7_2 ==> 5 x 5
# conv8_2 ==> 3 x 3
# conv9_2 ==> 1 x 1
mbox_source_layers = ['conv4_3', 'fc7', 'conv6_2', 'conv7_2', 'conv8_2', 'conv9_2'] #####prior_box來源層,可以更改。很多改進都是基于此處的調整。
# in percent %
min_ratio = 20 ####這里即是論文中所說的Smin=0.2,Smax=0.9的初始值,經過下面的運算即可得到min_sizes,max_sizes。具體如何計算以及兩者代表什么,請關注我的博客SSD詳解。這里產生很多改進。
max_ratio = 90
####math.floor()函數表示:求一個最接近它的整數,它的值小于或等于這個浮點數。
step = int(math.floor((max_ratio - min_ratio) / (len(mbox_source_layers) - 2)))####取一個間距步長,即在下面for循環給ratio取值時起一個間距作用??梢杂靡粋€具體的數值代替,這里等于17。
min_sizes = [] ###經過以下運算得到min_sizes和max_sizes。
max_sizes = []
for ratio in xrange(min_ratio, max_ratio + 1, step): ####從min_ratio至max_ratio+1每隔step=17取一個值賦值給ratio。注意xrange函數的作用。
########min_sizes.append()函數即把括號內部每次得到的值依次給了min_sizes。
min_sizes.append(min_dim * ratio / 100.)
max_sizes.append(min_dim * (ratio + step) / 100.)
min_sizes = [min_dim * 10 / 100.] + min_sizes
max_sizes = [min_dim * 20 / 100.] + max_sizes
steps = [8, 16, 32, 64, 100, 300] ###這一步要仔細理解,即計算卷積層產生的prior_box距離原圖的步長,先驗框中心點的坐標會乘以step,相當于從feature map位置映射回原圖位置,比如conv4_3輸出特征圖大小為38*38,而輸入的圖片為300*300,所以38*8約等于300,所以映射步長為8。這是針對300*300的訓練圖片。
aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2], [2]] #######這里指的是橫縱比,六種尺度對應六個產生prior_box的卷積層。具體可查看生成的train.prototxt文件一一對應每層的aspect_ratio參數,此參數在caffe.proto中有定義,關于aspect_ratios如何把其內容傳遞給了aspect_ratio,在model_libs.py文件中有詳細定義。
##在此我們要說明一個事實,就是文中的長寬比是如何產生的,這里請讀者一定要參看博主博文《SSD詳解(一)》中的第2部分內容,關于prior_box的產生?!?/em>
(1)對ssd產生的默認框的大小計算首先要計算參數min_sizes和max_sizes
step的作用,其實就是取一個間隔
首先定義數組min_sizes[]和max_sizes[]用來存放計算結果,沒有初始化說明默認為0,。然后計算conv4_3產生的min_sizes和max_sizes。根據代碼中的公式計算:
·min_sizes=[min_dim*10/100]+min_sizes
·max_sizes=[min_dim*20/100]+max_sizes
得到結果為min_sizes=[300*10/100]+0=30,而max_sizes=[300*20/100]+0=60。這樣conv4_3的計算公式被計算分別為30和60。
在min_ratio和max_ratio之間即20-90之間以step=17為間隔產生一組數據賦值給ratio,最終ratio=[20,37,54,71,88]。所以對于剩余5層所產生的min_sizes和max_sizes分別為:
fc7:min_sizes=min_dim*ratio/100=300*20/100=60,max_sizes=min_dim*(ratio+step)/100=300*(20+17)/100=111;
conv6_2:min_sizes=min_dim*ratio/100=300*37/100=111,max_sizes=min_dim*(ratio+step)/100=300*(37+17)/100=162;
conv7_2:min_sizes=min_dim*ratio/100=300*54/100=162,max_sizes=min_dim*(ratio+step)/100=300*(54+17)/100=213;
conv8_2:min_sizes=min_dim*ratio/100=300*71/100=213,max_sizes=min_dim*(ratio+step)/100=300*(71+17)/100=264;
conv9_2:min_sizes=min_dim*ratio/100=300*88/100=213,max_sizes=min_dim*(ratio+step)/100=300*(88+17)/100=315;
結果:7個數據,6個區間段
(2)產生的默認框的大小計算
·正方形小邊長=min_size
·而大正方形邊長=sqrt(min_size*max_size)
·width=sqrt(aspect_ratio)*min_size
·height=1/sqrt(aspect_ratio)*min_size
對其高和寬翻轉后得到另一個面積相同但寬高相互置換的長方形(width和height公式對調)。
現在我們可以計算6層中每個特征圖的每個中心點所產生的默認框的大小,分別如下:
conv4_3:
小正方形邊長=min_size=30,大正方形邊長=sqrt(min_size*max_size)=sprt(30*60)=42.42;
長方形的寬=sqrt(aspect_ratio)*min_size=sqrt(2)*30,高=1/sqrt(aspect_ratio)*min_size=30/sqrt(2),寬高比剛好為2:1;
將以上寬高旋轉90度產生另一個長方形,寬高比變為1:2。
fc7:
小正方形邊長=min_size=60,大正方形邊長=sqrt(min_size*max_size)=sprt(60*111)=81.6;
第1組長方形的寬=sqrt(aspect_ratio)*min_size=sqrt(2)*60,高=1/sqrt(aspect_ratio)*min_size=60/sqrt(2),寬高比剛好為2:1;
將以上寬高旋轉90度產生另一個長方形,寬高比變為1:2。
第2組長方形的寬=sqrt(aspect_ratio)*min_size=sqrt(3)*60,高=1/sqrt(aspect_ratio)*min_size=60/sqrt(3),寬高比剛好為3:1;
將以上寬高旋轉90度產生另一個長方形,寬高比變為1:3。
conv6_2:
小正方形邊長=min_size=111,大正方形邊長=sqrt(min_size*max_size)=sprt(111*162);
第1組長方形的寬=sqrt(aspect_ratio)*min_size=sqrt(2)*111,高=1/sqrt(aspect_ratio)*min_size=111/sqrt(2),寬高比剛好為2:1;
將以上寬高旋轉90度產生另一個長方形,寬高比變為1:2。
第2組長方形的寬=sqrt(aspect_ratio)*min_size=sqrt(3)*111,高=1/sqrt(aspect_ratio)*min_size=111/sqrt(3),寬高比剛好為3:1;
將以上寬高旋轉90度產生另一個長方形,寬高比變為1:3。
1、anchor_size, 這個參數直接決定了當前特征層的box 的大小, 可以看出越靠近輸入層, box越小, 越靠近輸出層, box越大, 所以 SSD的底層用于檢測小目標, 高層用于檢測大目標。
2、conv7_2、conv8_2、conv9_2我們這里就不再計算了,具體實現的步驟請大家參考腳本prior_box_layer.cpp
3、另外先驗框與ground truth框的匹配通過函數CHECK_GT()函數實現,具體在bbox_util.cpp腳本中實現
附:tensorflow實現的SSD的源碼:
default_params = SSDParams(
img_shape=(300, 300),
num_classes=21,
no_annotation_label=21,
feat_layers=['block4', 'block7', 'block8', 'block9', 'block10', 'block11'],
feat_shapes=[(38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1)],
# anchor_size_bounds=[0.15, 0.90],
anchor_size_bounds=[0.20, 0.90],
#anchor_sizes=[(21., 45.),
# (45., 99.),
# (99., 153.),
# (153., 207.),
# (207., 261.),
# (261., 315.)],
anchor_sizes=[(30., 60.),
(60., 111.),
(111., 162.),
(162., 213.),
(213., 264.),
(264., 315.)],
anchor_ratios=[[2, .5],
[2, .5, 3, 1./3],
[2, .5, 3, 1./3],
[2, .5, 3, 1./3],
[2, .5],
[2, .5]],
anchor_steps=[8, 16, 32, 64, 100, 300],
anchor_offset=0.5,
normalizations=[20, -1, -1, -1, -1, -1],
prior_scaling=[0.1, 0.1, 0.2, 0.2]
)
2.2.3 YOLOv3
在三個不同尺度,每個尺度三個不同大小的anchor,一共九組。
2.3 anchor_ratios???????
anchor_ratios,定義了anchor的寬高比,這里設置anchor_rations = [0.5, 1, 2]。
需要注意的是寬高比變化的同時保持面積不變。對于 base_size = 16 的情況下:
·ratio為0.5時,尺寸為 (16/sqrt(2)) x (16*sqrt(2)),即 11 x 22;(寬 × 高)
·ratio為1時,anchor尺寸為16 x 16;
·ratio為2時,尺寸為 23 x 12 。
得到的anchor如下圖所示,藍色點代表feature map中的特征點,每種顏色框代表一種長寬比,同一顏色不同大小的矩形框代表不同的尺度:
紅色:ration = 2
藍色:ration = 1
綠色:ration = 0.5
坐標順序為:左下右上。
附:anchors 的生成過程(generate_anchors源碼解析)
import numpy as np
import time
def generate_anchors(base_size=16, ratios=[0.5, 1, 2], scales=2**np.arange(3, 6)):
"""
Generate anchor (reference) windows by enumerating aspect ratios X
scales wrt a reference (0, 0, 15, 15) window.
scales = [8, 16, 32]
16x16的區域變成(16*8)*(16*8)=128*128的區域,(16*16)*(16*16)=256*256的區域,(16*32)*(16*32)=512*512的區域
"""
# 表示最基本的一個大小為16x16的區域,四個值,分別代表這個區域的左上角和右下角的點的坐標。
base_anchor = np.array([1, 1, base_size, base_size]) - 1
print ("base anchors", base_anchor)
# 這一句是將前面的16x16的區域進行ratio變化,也就是輸出三種寬高比的anchors,這里調用了_ratio_enum函數
ratio_anchors = _ratio_enum(base_anchor, ratios)
print ("anchors after three ratio \n", ratio_anchors)
anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales)
for i in range(ratio_anchors.shape[0])])
print ("achors after ration and scale \n", anchors)
return anchors
def _ratio_enum(anchor, ratios):
'''
Enumerate a set of anchors for each aspect ratio wrt an anchor.
Parameters:
anchor : A list contains coordinates of the upper left and lower right corners.
ratios : A list contains different aspect ratios.
Return:
anchors : Coordinates with different aspect ratios.
'''
w, h, x_ctr, y_ctr = _whctrs(anchor)
size = w * h #size:16*16=256
size_ratios = size / np.array(ratios) #256/ratios[0.5,1,2]=[512,256,128]
ws = np.round(np.sqrt(size_ratios)) #ws:[23 16 11]
hs = np.round(ws * ratios) #hs:[12 16 22],ws和hs一一對應。as:23*12、16*16、11*22,(高 × 寬)
# print(hs)
# print(ws.shape)
# 給定一組寬高向量,輸出各個預測窗口,也就是將(寬,高,中心點橫坐標,中心點縱坐標)的形式,轉成四個坐標值的形式
anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
return anchors
def _whctrs(anchor):
"""
主要作用是將輸入的anchor的四個坐標值[0, 0, 15, 15]轉化成(寬,高,中心點橫坐標,中心點縱坐標)的形式。
Return width, height, x center, and y center for an anchor (window).
"""
w = anchor[2] - anchor[0] + 1# w = 16
h = anchor[3] - anchor[1] + 1# h = 16
x_ctr = anchor[0] + 0.5 * (w - 1)# 7.5
y_ctr = anchor[1] + 0.5 * (h - 1)# 7.5
return w, h, x_ctr, y_ctr
def _mkanchors(ws, hs, x_ctr, y_ctr):
"""
Getting coordinates of different window width ratios around the same center.
Parameters:
ws : A sist of X coordinates in the upper left corner of a anchor.
hs : A sist of Y coordinates in the upper left corner of a anchor.
x_ctr : X-coordinates of the center of a anchor.
y_ctr : Y-coordinates of the center of a anchor.
Return:
anchors : Coordinates with different aspect ratios.
"""
ws = ws[:, np.newaxis]# ws的維度變為[3,1],即[[23]
# [16]
# [11]]
# print(ws)
# print(ws.shape)
hs = hs[:, np.newaxis]# hs同理
anchors = np.hstack((x_ctr - 0.5 * (ws - 1),
y_ctr - 0.5 * (hs - 1),
x_ctr + 0.5 * (ws - 1),
y_ctr + 0.5 * (hs - 1)))
return anchors
def _scale_enum(anchor, scales):
"""
_enum表示枚舉,以_ratio_enum(...)得到的3個anchor,得到其中心點和寬、高值,并將寬、高值與3個scale相乘(保持中心點不變),
最終得到9個在scaled圖像中(0,0)位置的base anchors,被generate_anchors(...)調用
Enumerate(枚舉) a set of anchors for each scale wrt an anchor.
Parameters:
anchor : Orginal anchor.
scales : Scaling factor.
Return:
Scaled coordinates of anchor.
"""
w, h, x_ctr, y_ctr = _whctrs(anchor)
ws = w * scales
hs = h * scales
anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
return anchors
if __name__ == '__main__':
import time
t = time.time()
a = generate_anchors() #最主要的就是這個函數
print("it takes time:", time.time() - t)
補充:
SSD中min_size&max_size的另一種計算方法(來自網上,不知道是否正確):
在SSD中,作者提到了anchor尺度大?。╯cale)的計算方法,也就是從最小的0.2,到最大的0.9,中間四個定位層的大小是等間隔采樣得到的。
Sk是每個特征層的先驗框大小與原圖片大小之比,Smax和Smin分別是最大,最小的比例.
m是就是特征層的個數,按理說分母應該是6-1=5,但是這里是5-1=4.很神奇。
k是第幾個特征層的意思,注意k的范圍是1~m, 也就是1~6.
需要向下取整
將m=5, Smin, Smax=(0.2, 0.9) 就是anchor_size_bounds=[0.20, 0.90],帶入之后,得到S1~S6: 20, 37, 54, 71, 88, 105, 其實就是挨個+17。此時還需要將其除以100還原回來,也就是:0.2, 0.37, 0.54, 0.71, 0.88, 1.05。然后,我們這個是比例,我們需要得到其在原圖上的尺寸,所以需要依次乘以300,得到:60, 111, 162, 213, 264, 315。
最后,你會問: 30呢? 這就要說到S1’了,S1’=0.5*S1, 就是0.1, 再乘以300, 就是30.
有七個數, 6個區間段, 目的還是為了得到上面那6組min_size和max_size. 就可以計算每個特征層的anchor_box 的尺寸了。
我想這應該是作者根據數據集的物體框大小的分布而設置的。因為上面我們介紹了,anchor只有跟你要檢測的物體的大小和長寬比更貼近,才能讓模型的效果更好。
延伸閱讀來源:https://blog.csdn.net/weixin_44285715/article/details/105124650