深度学习入门--锚框Anchor的生成处理及可视化(详细说明及代码实现)

大家好,我是CuddleSabe,目前大四在读,深圳准入职算法工程师,研究主要方向为多模态(VQA、ImageCaptioning等),欢迎各位佬来讨论!
我最近在有序地计划整理CV入门实战系列及NLP入门实战系列。在这两个专栏中,我将会带领大家一步步进行经典网络算法的实现,
欢迎各位读者(da lao)订阅

本教程皆是对李沐老师的动手学深度学习-锚框视频教程及zh.d2l.ai中代码的详细解释(李沐老师跳过了一部分的代码解释)

锚框Anchor的生成处理及可视化

  • 锚框的生成
  • 显示锚框

锚框的生成

⚠️代码警告!

def multibox_prior(data, sizes, ratios):
    """生成以每个像素为中心具有不同形状的锚框"""
    in_height, in_width = data.shape[-2:]
    device, num_sizes, num_ratios = data.device, len(sizes), len(ratios)
    boxes_per_pixel = (num_sizes + num_ratios - 1)
    size_tensor = torch.tensor(sizes, device=device)
    ratio_tensor = torch.tensor(ratios, device=device)

    # 为了将锚点移动到像素的中心,需要设置偏移量。
    # 因为一个像素的高为1且宽为1,我们选择偏移我们的中心0.5
    offset_h, offset_w = 0.5, 0.5
    steps_h = 1.0 / in_height  # 在y轴上缩放步长
    steps_w = 1.0 / in_width  # 在x轴上缩放步长

    # 生成锚框的所有中心点
    center_h = (torch.arange(in_height, device=device) + offset_h) * steps_h
    center_w = (torch.arange(in_width, device=device) + offset_w) * steps_w
    shift_y, shift_x = torch.meshgrid(center_h, center_w)
    shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)

    # 生成“boxes_per_pixel”个高和宽,
    # 之后用于创建锚框的四角坐标(xmin,xmax,ymin,ymax)
    w = torch.cat((size_tensor * torch.sqrt(ratio_tensor[0]),
                   sizes[0] * torch.sqrt(ratio_tensor[1:])))\
                   * in_height / in_width  # 处理矩形输入
    h = torch.cat((size_tensor / torch.sqrt(ratio_tensor[0]),
                   sizes[0] / torch.sqrt(ratio_tensor[1:])))
    # 除以2来获得半高和半宽
    anchor_manipulations = torch.stack((-w, -h, w, h)).T.repeat(
                                        in_height * in_width, 1) / 2

    # 每个中心点都将有“boxes_per_pixel”个锚框,
    # 所以生成含所有锚框中心的网格,重复了“boxes_per_pixel”次
    out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y],
                dim=1).repeat_interleave(boxes_per_pixel, dim=0)
    output = out_grid + anchor_manipulations
    return output.unsqueeze(0)

第一眼看上去各位同学可能会有点迷糊,没关系,我们一行一行来解析!
首先,我们需要确定的是,作为生成锚框的函数,它的输入和输出都是什么?尺寸多少呢?

输入参数 含义 形状 类型
data 那当然是图片的数据啦 [b, c, h, w] tensor
sizes 锚框的尺寸!也称 S S S 任意长度,本文为3 list
ratios 锚框的高宽比!也称 R R R 任意长度,本文为3 list
输出参数 含义 形状 类型
output 各个锚框的左上右下点坐标 [b, 该图片的锚框数, 4] tensor

可以看到,该函数的任务是输入图片和想要的锚框的尺寸信息,则会返回所有该图片所有锚框的左上顶点和右下顶点顶坐标!那么它是如何实现的?我们接下来逐行看代码:

in_height, in_width = data.shape[-2:]
device, num_sizes, num_ratios = data.device, len(sizes), len(ratios)

这两行的作用是获取图片的尺寸(高和宽)以及其余参数的数量
boxes_per_pixel = (num_sizes + num_ratios - 1)

这里是获得一个像素有多少个锚框。
我们本教程中使用3个size和3个ratios,如果是标准官方实现的话是3*3=9,
但是我们这里为了加快运算,每个像素只会获得3+3-1=5个锚框

那么我们怎么通过 sizesratios 来判断锚框的尺寸呢? 例如,我们输入的
sizes=[0.75, 0.5, 0.25]ratios=[1, 2, 0.5],这里我们可以听下李沐老师的说法:

深度学习入门--锚框Anchor的生成处理及可视化(详细说明及代码实现)_第1张图片

# 为了将锚点移动到像素的中心,需要设置偏移量。
# 因为一个像素的高为1且宽为1,我们选择偏移我们的中心0.5
offset_h, offset_w = 0.5, 0.5
steps_h = 1.0 / in_height  # 在y轴上缩放步长
steps_w = 1.0 / in_width  # 在x轴上缩放步长
# 生成锚框的所有中心点
center_h = (np.arange(in_height, ctx=device) + offset_h) * steps_h
center_w = (np.arange(in_width, ctx=device) + offset_w) * steps_w

假定图片大小为100*100,这里是先生成0,1,2,3...100的序列,
加上0.5后即是在图片中的像素坐标,再乘以steps_w(h)等同于除图片的宽()
等同于归一化到0-1,即在图片内到百分比坐标
shift_y, shift_x = torch.meshgrid(center_h, center_w)

这里我们使用meshgrid来对xy坐标两两组合得到网格坐标,
两个输出的尺寸信息为[输入图片高,输入图片宽],[100, 100]
shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)

摊平,因为函数输出是宽和高在同一维度
w = np.concatenate((size_tensor * np.sqrt(ratio_tensor[0]),
                        sizes[0] * np.sqrt(ratio_tensor[1:]))) \
                        * in_height / in_width  # 处理矩形输入
    h = np.concatenate((size_tensor / np.sqrt(ratio_tensor[0]),
                        sizes[0] / np.sqrt(ratio_tensor[1:])))

怎么样,是不是有点眼熟?!

请添加图片描述
请添加图片描述

接下来重头戏来了!

anchor_manipulations = torch.stack((-w, -h, w, h)).T.repeat(in_height * in_width, 1) / 2

相信很多同学第一次看到的时候头都晕了,没关系,我们把它拆解开

torch.stack((-w, -h, w, h))
这里输出的shape是[4, 5],那么我们再加一个转置,相当于两个维度交换

torch.stack((-w, -h, w, h)).T
shpe为[54],即一个像素的锚框坐标,这里的5是因为每个像素有(num_sizes + num_ratios - 1)个锚框,也就是5。
而这里的4就是一个锚框的左上顶点和右下顶点的xy坐标。
那么这个只是一个像素的,我们在第一个维度(dim=0)重复h*w(总像素个数)次

torch.stack((-w, -h, w, h)).T.repeat(in_height * in_width, 1)
这时候的shape为[h*w*54],第一维度也就是一张图片所有的锚框数,第二维度为两个顶点的xy坐标。
但我们所需的是半宽半高(因为我们使用半宽半高加上中心点才是顶点坐标)

即
torch.stack((-w, -h, w, h)).T.repeat(in_height * in_width, 1)/2

接下来倒数第二行代码: 我们依旧一点点进行分解

torch.stack([shift_x, shift_y, shift_x, shift_y], dim=1)
输出为[h*w, 4],我们这里为什么要重复添加两个x和y呢?
因为我们上边的代码中获得了(-w/2, -h/2, w/2, h/2)即一个锚框的半宽半高。
但是我们现在一个像素只有一个框,因此我们将每一行重复5次,即(num_sizes + num_ratios - 1)次

torch.stack([shift_x, shift_y, shift_x, shift_y], dim=1).repeat_interleave(boxes_per_pixel, dim=0)

最后,我们将每个锚框的中心点坐标加上每个锚框的[负半宽,负半高,半宽,半高],就是每个锚框的左上顶点和右下顶点坐标(图片的纵坐标向下增长)

output = out_grid + anchor_manipulations

返回尺寸为 [b, h * w * 每个像素的锚框数, 4]

显示锚框

#@save
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
        
    """将边界框(左上x, 左上y, 右下x, 右下y)格式转换成matplotlib格式((左上x, 左上y), 宽, 高)"""
	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)

    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.detach().numpy(), 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))

使用

我们使用上述的锚框生成函数,获得如下shape的输出 torch.Size([1, 2042040, 4])

Y = multibox_prior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])

为了方便,我们将其reshape一下,如下

boxes = Y.reshape(h, w, 5, 4)

则这个变量代表了在250行250列像素的第一个锚框的俩顶点坐标值

boxes[250, 250, 0, :]
如下
tensor([0.06, 0.07, 0.63, 0.82])

同时,因为我们的顶点坐标都缩放到0-1之间,因此我们需要将其还原到原本的图像坐标上去,即乘上原图像的w和h

bbox_scale = torch.tensor((w, h, w, h))
boxes[250, 250, :, :] * bbox_scale 就是图片[250250]处的锚框在图片中的真实坐标
bbox_scale = torch.tensor((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'])

深度学习入门--锚框Anchor的生成处理及可视化(详细说明及代码实现)_第2张图片

你可能感兴趣的:(深度学习入门系列,深度学习,人工智能,目标检测,算法)