SSD目标检测的个人总结(1)—— 锚框的生成

前言

沐神的代码看了很久、B站上的视频也刷了很多遍,感叹下自己的基础确实不怎么扎实,锚框部分的底层代码几乎是一行行撸过来的,因此对整个学习的部分做一个总结先放上沐神的b站和电子教程,以示敬意!
沐神的b站

动手学深度学习电子版

锚框

以SSD(单发多框检测)为例,在输入图像上,我们会以图片的每一个像素为中心,生成多个缩放比(Size)和宽高比(Ratio)的边界框,这些边界框被称为锚框(anchor box)

锚框的生成

锚框的生成是以图像像素为中心,生成不同形状的锚框,对于每一张图片,均有宽度w和高度h,对宽度和高度进行缩放,决定了锚框的缩放比,而缩放后的锚框,通过更改宽高比,从而使得锚框对不同比例的物体有更好的适应能力。
如下列代码所示,size表示分别覆盖了20%——96%左右的图像区域;ratios表示每个缩放后的锚框的长和宽的比值;

#sizes——锚框在特征图上的覆盖范围
#ratios——锚框长和宽的比例组合
sizes = [[0.2,0.272],
        [0.3,0.447],
        [0.54,0.619],
        [0.71,0.79],
        [0.88,0.961]]

ratios = [[1,2,0.5]]*5

沐神在动手学深度学习的第二版中,还提到了一些锚框生成的条件,由于SSD目标检测的思想是通过每个像素来生成N个锚框,对于一张480320的图片而言,如果我们假定买个像素生成3个锚框,我们在一张图像上将会得到480320*3 = 460800个锚框,很容易计算量过大;因此在具体实践中,我们只考虑在相同缩放比下,不同宽高比的组合或者是相同宽高比下,不同缩放比的组合
假设图像的高度为h,宽度为w,缩放比的个数为n,宽高比的个数为m,则对于每一个像素来说,我们能够生成的锚框的数量为

n+m-1 既 缩放比个数+宽高比个数-1

具体代码实现如下,可以参考李沐动手学深度学习中的代码,本人仅根据个人理解和实际运行,做了抽取和注释

def multibox_prior(data, sizes, ratios):
    """生成以每个像素为中心具有不同形状的锚框"""
    # 输入图像的高宽,输入数据数据格式为(批次,通道数,高,宽)
    in_height, in_width = data.shape[-2:]
    # 提供size和ratio的数量,s为缩放比,r为宽高比
    num_sizes, num_ratios = len(sizes), len(ratios)
    # 每个像素生成锚框数量的计算,该例为3+3-1
    boxes_per_pixel = (num_sizes + num_ratios - 1)
    # 生成缩放比和宽高比的tensor
    size_tensor = torch.tensor(sizes)
    ratio_tensor = torch.tensor(ratios)

    # 为了将锚点移动到像素的中心,需要设置偏移量。
    # 因为一个像素的的高为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) + offset_h) * steps_h
    center_w = (torch.arange(in_width) + 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√sr;锚框的高度:hs/√r,
    # 固定缩放比,调整宽高比进行锚框宽高组合;固定宽高比,调整缩放比进行锚框宽高组合; 
    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)

需要注意的是,在沐神的代码中,锚框的生成做了缩放,这样做的好处是,可以根据任意比例大小的图片的宽高,进行锚框的生成,也能够基于此进行预测,我们给定一组测试数据,对该方法进行验证

h,w = 561,728
test_data = torch.rand(size = (1,3,h,w))
test_sample = multibox_prior(test_data , sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
test_sample.shape

以下结果表明,每一张561*728分辨率的图片,根据宽高比和缩放比生成的锚框约有204万个,每一个锚框包含四个值,分别是以单个像素为中心缩放和偏移后的锚框的左上角的x,y以及右下角的x,y

torch.Size([1, 2042040, 4])

通过数组下标对单个像素生成的锚框进行访问,我们先通过reshape张量的形状,以便我们进行访问,输入以下代码后,可以得出该像素下缩放+偏移后锚框的坐标:

# 转变数据为(图像高、图像宽,-1,坐标点)的形式
boxes = test_sample.reshape(h, w, -1, 4)
# 访问(250,250)像素点生成的第一个锚框的坐标值
boxes[250, 250, 0, :]

tensor([0.06, 0.07, 0.63, 0.82])

若想还原在图像上的真实坐标点,可以采用如下方法,返回该像素点所生成的锚框在原始图片上的真实坐标信息,变量box_scale为原始图像的宽和高:

boxes = test_sample.reshape(h, w, -1, 4)
box_scale = torch.tensor((w,h,w,h))
boxes[250, 250, :, :]*box_scale

tensor([[ 40.13, 40.12, 460.88, 460.87],
[110.25, 110.25, 390.75, 390.75],
[180.38, 180.38, 320.62, 320.62],
[-47.02, 101.74, 548.02, 399.26],
[101.74, -47.02, 399.26, 548.02]]

锚框的绘制

在生成锚框后,我们可以结合沐神的d2l包,在样例图片上绘制我们得到的锚框,我们所选择的是图片上坐标为(250,250)的像素所生成的锚框:

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 = d2l.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))
d2l.set_figsize()
bbox_scale = torch.tensor((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'])

运行上述代码的实际效果

版权声明:本文为CSDN博主「zyf_freashman」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_32374357/article/details/122745861

zyf_freashman

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

YOLOX笔记

目录 1. 样本匹配 正负样本划分过程 2. yoloxwarmcos 学习率 3. 无法开启多gpu训练, 或者多gpu训练卡住? 1. 样本匹配 正负样本划分过程 说明: gt_centerbbox是在gt_bbox中心点向四周