天天看点

【MXNet】(二十九):Anchor box

目标检测算法通常会在输入图像中采样大量的区域,这里学习的是其中的一种方法:它以每个像素为中心生成多个大小和宽高比(aspect ratio)不同的边界框。这些边界框被称为锚框(anchor box)。

首先导入包。

%matplotlib inline
from mxnet import contrib, gluon, image, nd
from matplotlib import pyplot as plt
from IPython import display
import numpy as np
np.set_printoptions(2)
           

假设输入图像高为h,宽为w。分别以图像的每个像素为中心生成不同形状的锚框。设大小为s∈(0,1]且宽高比为r>0,那么锚框的宽和高将分别为ws√r和hs/r。当中心位置给定时,已知宽和高的锚框是确定的。

假设定好一组大小s1,…,sn和一组宽高比r1,…,rm。如果以每个像素为中心时使用所有的大小与宽高比的组合,输入图像将一共得到whnm个锚框。虽然这些锚框可能覆盖了所有的真实边界框,但计算复杂度容易过高。因此,我们通常只对包含s1或r1的大小与宽高比的组合感兴趣,即

【MXNet】(二十九):Anchor box

以相同像素为中心的锚框的数量为n+m−1。对于整个输入图像,我们将一共生成wh(n+m−1)个锚框。

img = image.imread('img/catdog.jpg').asnumpy()
h, w = img.shape[0:2]

print(h, w)
X = nd.random.uniform(shape=(1, 3, h, w))
Y = contrib.nd.MultiBoxPrior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shape
           
【MXNet】(二十九):Anchor box

返回锚框变量

y

的形状为(批量大小,锚框个数,4)。将锚框变量

y

的形状变为(图像高,图像宽,以相同像素为中心的锚框个数,4)后,就可以通过指定像素位置来获取所有以该像素为中心的锚框了。

boxes = Y.reshape((h, w, 5, 4))
boxes[250, 250, 0, :]
           
【MXNet】(二十九):Anchor box

定义

show_bboxes

函数以便在图像上画出多个边界框。

def bbox_to_rect(bbox, color):
    return plt.Rectangle(xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0],
                         height=bbox[3]-bbox[1], fill=False, edgecolor=color,
                         linewidth=2)

def show_bboxes(axes, bboxes, labels=None, colors=None):
    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 = 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))
           
def set_figsize(figsize=(3.5, 2.5)):
    display.set_matplotlib_formats('svg')
    plt.rcParams['figure.figsize'] = figsize

set_figsize()
bbox_scale = nd.array((w, h, w, h))
fig = 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'])
           
【MXNet】(二十九):Anchor box

交并比(Intersection over Union,IoU)是衡量锚框和真实边界框之间的相似度的一种方法。

【MXNet】(二十九):Anchor box

需要为每个锚框标注两类标签:一是锚框所含目标的类别,简称类别;二是真实边界框相对锚框的偏移量,简称偏移量(offset)。在目标检测时,首先生成多个锚框,然后为每个锚框预测类别以及偏移量,接着根据预测的偏移量调整锚框位置从而得到预测边界框,最后筛选需要输出的预测边界框。

设锚框A及其被分配的真实边界框B的中心坐标分别为(xa,ya)和(xb,yb),A和B的宽分别为wa和wb,高分别为ha和hb,一个常用的技巧是将A的偏移量标注为

【MXNet】(二十九):Anchor box

其中常数的默认值为μx=μy=μw=μh=0,σx=σy=0.1,σw=σh=0.2。

下面演示一下锚框与真实边界框在图像中的位置。

ground_truth = nd.array([[0, 0.1, 0.08, 0.52, 0.92],
                         [1, 0.55, 0.2, 0.9, 0.88]])
anchors = nd.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 = 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']);
           
【MXNet】(二十九):Anchor box

可以通过

contrib.nd

模块中的

MultiBoxTarget

函数来为锚框标注类别和偏移量。

labels = contrib.nd.MultiBoxTarget(anchors.expand_dims(axis=0),
                                  ground_truth.expand_dims(axis=0),
                                  nd.zeros((1, 3, 5)))
           

返回的第一项是为每个锚框标注的四个偏移量,其中负类锚框的偏移量标注为0。

labels[0]
           
【MXNet】(二十九):Anchor box

返回值的第二项为掩码(mask)变量,形状为(批量大小, 锚框个数的四倍)。掩码变量中的元素与每个锚框的4个偏移量一一对应。 

labels[1]
           
【MXNet】(二十九):Anchor box

返回的第三项表示为锚框标注的类别。

labels[2]
           
【MXNet】(二十九):Anchor box

在预测时,当锚框数量较多时,同一个目标上可能会输出较多相似的预测边界框。为了使结果更加简洁,可以用非极大值抑制(non-maximum suppression,NMS)移除相似的预测边界框。

下面构造先构造4个锚框和每个类别的预测概率,假设预测偏移量全是0。

anchors = nd.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 = nd.array([0] * anchors.size)
cls_probs = nd.array([[0] * 4, 
                     [0.9, 0.8, 0.7, 0.1],
                     [0.1, 0.2, 0.3, 0.9]])
           
fig = plt.imshow(img)
show_bboxes(fig.axes, anchors * bbox_scale,
           ['dog=0.9', 'dog=0.8', 'dog=0.7', 'cat=0.9'])
           
【MXNet】(二十九):Anchor box

使用

contrib.nd

模块的

MultiBoxDetection

函数来执行非极大值抑制并设阈值为0.5。返回的结果的形状为(批量大小, 锚框个数, 6)。其中每一行的6个元素代表同一个预测边界框的输出信息。第一个元素是索引从0开始计数的预测类别(0为狗,1为猫),其中-1表示背景或在非极大值抑制中被移除。第二个元素是预测边界框的置信度。剩余的4个元素分别是预测边界框左上角的x和y轴坐标以及右下角的x和y轴坐标(值域在0到1之间)。

output = contrib.ndarray.MultiBoxDetection(
cls_probs.expand_dims(axis=0),offset_preds.expand_dims(axis=0),
anchors.expand_dims(axis=0), nms_threshold=0.5)
output
           
【MXNet】(二十九):Anchor box

移除掉类别为-1的预测边界框,并可视化非极大值抑制保留的结果。

fig = 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, [nd.array(i[2:]) * bbox_scale], label)
           
【MXNet】(二十九):Anchor box

继续阅读