深度學習Anchor Boxes原理與實戰技術
目標檢測算法通常對輸入圖像中的大量區域進行采樣,判斷這些區域是否包含感興趣的目標,並調整這些區域的邊緣,以便更准確地預測目標的地面真實邊界框。不同的模型可能使用不同的區域采樣方法。在這里,我們介紹一種這樣的方法:它生成多個大小和縱橫比不同的邊框,同時以每個像素為中心。這些邊界框稱為錨框。我們將在下面幾節中練習基於錨盒的對象檢測。
首先,導入本文所需的包或模塊。在這里,我們修改了NumPy的打印精度。因為打印張量實際上調用了NumPy的print函數,所以本文打印的張量中的浮點數更簡潔。
%matplotlib inline
from d2l import mxnet as d2l
from mxnet import gluon, image, np, npx
np.set_printoptions(2)
npx.set_np()
1. Generating Multiple Anchor Boxes
假設輸入圖像的高度為\(h\),寬度為\(w\)。我們生成以圖像的每個像素為中心的不同形狀的錨框。假設大小為\(s\in (0, 1]\),縱橫比為\(r>0\),錨框的寬度和高度分別為\(ws\sqrt{r}\)和\(hs/\sqrt{r}\)。當中心位置給定時,確定一個已知寬度和高度的錨箱。
下面我們設置了一組大小\(s_1,\ldots,s\u n\)和一組縱橫比\(r\u 1,\ldots,r\u m\)。如果我們使用以每個像素為中心的所有尺寸和縱橫比的組合,輸入圖像將有總共\(whnm\)個錨框。雖然這些錨盒可以覆蓋所有的地面真實邊界盒,但計算復雜度往往過高。因此,我們通常只對包含\(s_1\) 或 \(r_1\)大小和縱橫比的組合感興趣,即:
\[(s_1, r_1), (s_1, r_2), \ldots, (s_1, r_m), (s_2, r_1), (s_3, r_1), \ldots, (s_n, r_1).\]
也就是說,以同一像素為中心的錨框數量為\(n+m-1\)。對於整個輸入圖像,我們將生成總共\(wh(n+m-1)\)個錨框。
上述錨箱生成方法已在multibox_prior函數中實現。我們指定輸入、一組大小和一組縱橫比,此函數將返回輸入的所有錨框。
img = image.imread('../img/catdog.jpg').asnumpy()
h, w = img.shape[0:2]
print(h, w)
X = np.random.uniform(size=(1, 3, h, w)) # Construct input data
Y = npx.multibox_prior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shape
561 728
(1, 2042040, 4)
我們可以看到返回的錨框變量y的形狀是(批大小,錨框數量,4)。將錨框變量y的形狀更改為(圖像高度、圖像寬度、以同一像素為中心的錨框數量,4)后,我們可以獲得所有以指定像素位置為中心的錨定框。在下面的示例中,我們訪問位於(250,250)中心的第一個錨定框。它有四個元素:錨框左上角的\(x,y\)軸坐標和右下角的\(x,y\)軸坐標。\(x\)和\(y\)軸的坐標值分別除以圖像的寬度和高度,因此值范圍在0和1之間。
boxes = Y.reshape(h, w, 5, 4)
boxes[250, 250, 0, :]
array([0.06, 0.07, 0.63, 0.82])
為了描述圖像中所有以一個像素為中心的錨框,我們首先定義show_bboxes函數來繪制圖像上的多個邊界框。
#@save
def show_bboxes(axes, bboxes, labels=None, colors=None):
"""Show bounding boxes."""
def _make_list(obj, default_values=None):
if obj is None:
obj = default_values
elif not isinstance(obj, (list, tuple)):
obj = [obj]
return obj
labels = _make_list(labels)
colors = _make_list(colors, ['b', 'g', 'r', 'm', 'c'])
for i, bbox in enumerate(bboxes):
color = colors[i % len(colors)]
rect = d2l.bbox_to_rect(bbox.asnumpy(), color)
axes.add_patch(rect)
if labels and len(labels) > i:
text_color = 'k' if color == 'w' else 'w'
axes.text(rect.xy[0], rect.xy[1], labels[i],
va='center', ha='center', fontsize=9, color=text_color,
bbox=dict(facecolor=color, lw=0))
正如我們看到的,在x軸和y軸的值除以坐標。在繪制圖像時,我們需要恢復錨定框的原始坐標值,從而定義變量bbox_scale。現在,我們可以在圖像中以(250,250)為中心繪制所有的錨框。如您所見,藍色錨框大小為0.75,縱橫比為1,很好地覆蓋了圖像中的狗。
d2l.set_figsize((3.5, 2.5))
bbox_scale = np.array((w, h, w, h))
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, boxes[250, 250, :, :] * bbox_scale,
['s=0.75, r=1', 's=0.5, r=1', 's=0.25, r=1', 's=0.75, r=2',
's=0.75, r=0.5'])
2. Intersection over Union
我們剛剛提到了錨盒很好地覆蓋了圖像中的狗。如果已知目標的地面真實邊界框,這里的“井”如何量化?一種直觀的方法是測量錨盒與地面真實邊界盒之間的相似度。我們知道Jaccard索引可以度量兩個集合之間的相似性。給定集合\(\mathcal{A}\)和\(\mathcal{B}\),它們的Jaccard索引是它們的交集大小除以它們的並集大小:
\[J(\mathcal{A},\mathcal{B}) = \frac{\left|\mathcal{A} \cap \mathcal{B}\right|}{\left| \mathcal{A} \cup \mathcal{B}\right|}.\]
實際上,我們可以將邊界框的像素區域視為像素集合。這樣,我們就可以通過像素集的Jaccard索引來度量兩個邊界框的相似度。當我們測量兩個邊界框的相似性時,我們通常將Jaccard索引稱為intersection over union(IoU),即兩個邊界框的相交面積與並集面積的比值,如圖13.4.1所示。IoU的值范圍在0到1之間:0表示兩個邊界框之間沒有重疊的像素,而1表示兩個邊界框相等。
Fig. 1. IoU is the ratio of the intersecting area to the union area of two bounding boxes.
將使用IoU來測量錨定框和地面真實邊界框之間以及不同錨定框之間的相似性。
3. Labeling Training Set Anchor Boxes
在訓練集中,我們將每個錨盒視為一個訓練示例。為了訓練目標檢測模型,我們需要為每個錨框標記兩種類型的標簽:第一種是錨框中包含的目標的類別(類別),第二種是地面真相邊界框相對於錨定框的偏移量(offset)。在目標檢測中,首先生成多個錨框,預測每個錨框的類別和偏移量,根據預測的偏移量調整錨定框的位置,得到用於預測的邊界框,最后過濾出需要輸出的預測邊界框。
我們知道,在目標檢測訓練集中,每幅圖像都標有地面真實邊界框的位置和所包含目標的類別。錨盒生成后,我們主要根據與錨盒相似的地面真實邊界框的位置和類別信息對錨盒進行標記。那么,我們如何將地面真實邊界框指定給與它們類似的錨框呢?
Fig 2. Assign ground-truth bounding boxes to anchor boxes.
ground_truth = np.array([[0, 0.1, 0.08, 0.52, 0.92],
[1, 0.55, 0.2, 0.9, 0.88]])
anchors = np.array([[0, 0.1, 0.2, 0.3], [0.15, 0.2, 0.4, 0.4],
[0.63, 0.05, 0.88, 0.98], [0.66, 0.45, 0.8, 0.8],
[0.57, 0.3, 0.92, 0.9]])
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, ground_truth[:, 1:] * bbox_scale, ['dog', 'cat'], 'k')
show_bboxes(fig.axes, anchors * bbox_scale, ['0', '1', '2', '3', '4']);
我們可以使用multibox_target函數標記錨定框的類別和偏移量。此函數用於將背景類別設置為0,並將目標類別的整數索引從零遞增1(1表示dog,2表示cat)。我們在錨定框和底真值邊界框中添加實例維數,並使用expand_dims函數構造形狀為(batch size批次大小、類別數包括背景、錨框數量)的隨機預測結果。
labels = npx.multibox_target(np.expand_dims(anchors, axis=0),
np.expand_dims(ground_truth, axis=0),
np.zeros((1, 3, 5)))
返回的結果中有三項,都是張量格式。第三項由標記為錨定框的類別表示。
labels[2]
array([[0., 1., 2., 0., 2.]])
我們根據錨框和地面真實邊界框在圖像中的位置來分析這些標記類別。首先,在所有的“錨框-地面真實邊界框”對中,錨框\(A_4\)到cat的地面真相邊界框的IoU最大,因此錨框(A_4\)的類別被標記為cat。
返回值的第二項是一個mask變量,其形狀為(批大小,錨框數量的四倍)。mask變量中的元素與每個定位框的四個偏移值一一對應。因為我們不關心背景檢測,所以負類的偏移量不應該影響目標函數。通過乘以元素,mask變量中的0可以在計算目標函數之前過濾掉負的類偏移量。
labels[1]
array([[0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0.,
1., 1., 1., 1.]])
返回的第一項是為每個定位框標記的四個偏移量值,負類定位框的偏移量標記為0。
labels[0]
array([[ 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 1.40e+00, 1.00e+01,
2.59e+00, 7.18e+00, -1.20e+00, 2.69e-01, 1.68e+00, -1.57e+00,
0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, -5.71e-01, -1.00e+00,
-8.94e-07, 6.26e-01]])
4. Bounding Boxes for Prediction
在模型預測階段,我們首先為圖像生成多個錨框,然后逐個預測這些錨框的類別和偏移量。然后,基於錨定框及其預測偏移量得到預測邊界框。當有多個錨盒時,同一個目標可以輸出許多相似的預測邊界框。為了簡化結果,我們可以去掉類似的預測邊界框。通常稱為非最大值抑制(NMS)。
接下來,我們將看一個詳細的例子。首先,建造四個錨箱。為了簡單起見,我們假設預測的偏移量都為0。這意味着預測邊界框是錨定框。最后,我們為每個類別構造一個預測概率。
anchors = np.array([[0.1, 0.08, 0.52, 0.92], [0.08, 0.2, 0.56, 0.95],
[0.15, 0.3, 0.62, 0.91], [0.55, 0.2, 0.9, 0.88]])
offset_preds = np.array([0] * anchors.size)
cls_probs = np.array([[0] * 4, # Predicted probability for background
[0.9, 0.8, 0.7, 0.1], # Predicted probability for dog
[0.1, 0.2, 0.3, 0.9]]) # Predicted probability for cat
在圖像上打印預測邊界框及其置信級別。
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, anchors * bbox_scale,
['dog=0.9', 'dog=0.8', 'dog=0.7', 'cat=0.9'])
我們使用multibox_detection函數來執行NMS,並將閾值設置為0.5。這將向張量輸入添加一個示例維度。我們可以看到返回結果的形狀是(批量大小,錨框數量,6)。每行的6個元素表示同一預測邊界框的輸出信息。第一個元素是預測的類別索引,從0開始(0表示dog,1表示cat)。值-1表示NMS中的背景或刪除。第二個元素是預測邊界框的置信度。其余四個元素是預測邊界框左上角的\(x,y\)軸坐標和右下角的\(x,y\)軸坐標(值范圍在0到1之間)。
output = npx.multibox_detection(
np.expand_dims(cls_probs, axis=0),
np.expand_dims(offset_preds, axis=0),
np.expand_dims(anchors, axis=0),
nms_threshold=0.5)
output
array([[[ 0. , 0.9 , 0.1 , 0.08, 0.52, 0.92],
[ 1. , 0.9 , 0.55, 0.2 , 0.9 , 0.88],
[-1. , 0.8 , 0.08, 0.2 , 0.56, 0.95],
[-1. , 0.7 , 0.15, 0.3 , 0.62, 0.91]]])
我們移除了類別1的預測邊界框,並將NMS保留的結果可視化。
fig = d2l.plt.imshow(img)
for i in output[0].asnumpy():
if i[0] == -1:
continue
label = ('dog=', 'cat=')[int(i[0])] + str(i[1])
show_bboxes(fig.axes, [np.array(i[2:]) * bbox_scale], label)
在實際應用中,我們可以在執行NMS之前移除置信水平較低的預測邊界框,從而減少NMS的計算量。我們還可以過濾NMS的輸出,例如,只保留具有較高置信水平的結果作為最終輸出。
5. Summary
我們以每個像素為中心,生成具有不同大小和縱橫比的多個錨框。
IoU,也稱為Jaccard索引,測量兩個邊界框的相似性。它是兩個邊界框的相交面積與並集面積之比。
在訓練集中,我們為每個錨盒標記兩種類型的標簽:一種是錨盒中包含的目標類別,另一種是ground-truth真實邊界框相對於錨盒的偏移量。
在預測時,我們可以使用非最大值抑制(NMS)去除相似的預測邊界框,從而簡化預測結果。