锚框

其实根据multibox_prior函数的代码推算,
虽然输入的ration确实就是最终生成的锚框的宽高比,
但是文中的size却并不是真正的 ”锚框/原图面积比“,
记真正的锚框/原图面积比为size_real ,
代码中的size为size_old,输入的原图的高为H,宽为W,
则 size_real = (size_old)^2*H/W, size_real是size_old 的二次函数,
在第一象限内是单调递增的,而根据该方法的代码,
宽高比确实就是r,所以虽然描述和实际不一致,但是却不影响使用

以下推算该式
设生成的锚框宽为w,高为h (均为归一化后的数值)
则根据multibox_prior中的算法
真实锚框宽为 wW = (size_oldsqrt(ratio)H/W)W= Hsize_oldsqrt(ratio)
真实锚框高为 h*H = (size_old/sart(ratio))H = Hsize_old/sart(ratio)

上面两式相除得锚框真实款高比为ratio 符合预期

下面推算真实的锚框/原图面积比size_real
size_real
= (wW hH)/(WH)
= (Hsize_old)**2 /(WH)
= (size_old**2)*H/W
可以看到 size_real是size_old的二次函数

关于assign_anchor_to_bbox() 函数,这个函数分两阶段,第一阶段是在对每一个真实边框界,找到最大IoU的锚框,并且确保这个锚框的IoU大于设定的阈值,如果达不到阈值,这个匹配就会暂缺。进行完第一阶段,第二阶段才是讲解中介绍的丢弃寻找法,这个方法补充了第一阶段没有匹配上的真实边框界,最终确保所有的真实边框界都有一个匹配上的锚框。第二阶段的匹配不会影响第一阶段已经匹配好的结果。讲解部分只涵盖了第二阶段,如果加上第一阶段的解释,有助于对函数的理解。

这里进行了两次变换。他说的不清楚,首先 默认 h = 1 , 计算出来 锚框的 w 和 h
。然后因为原来的锚框的中心坐标被缩放成了,1 X 1 的一个范围,这里要保证,w和h在1X1 的这个缩放下比例也要正确,默认保持 h 不变,只是修改 w ,把 w 在进行缩放。
例如 假设 原图 是 in_w = 2, in_h = 1
然后 锚框h 默认 = 1,假设我们让锚框 根据 r 和 s算出来的形状 是
锚框 w = 2, h = 1 (这里是使用sqrt(s) 是为了防止 s 数值过大,平滑数据用,实际应该是 s )

这时候 原图 缩放成 1 x 1 的正方形, 锚框 缩放完之后也是 1 * 1, 因此对锚框进行一次缩放,缩放因子 是 h / w
h默认是基准1 ,不需要改变

关于 assign_anchor_to_bbox 函数的实现, 其实可以将

col_discard = torch.full((num_anchors,), -1)
row_discard = torch.full((num_gt_boxes,), -1)

直接移除, 之后循环内

jaccard[:, box_idx] = -1
jaccard[anc_idx, :] = -1

这样更易读且利用广播效率更高 :grinning:

这一章有些代码感觉写的不太好,比较混乱。
感觉可以先从数据结构出发,
还没弄懂为啥要把这些数据这样组合来组合去的,
可能后面章节会讲吧。

对于multibox_prior我重写了一版,返回形状和书中不一样且没有缩放,书中给的全部揉在一起不知道是不是后面要这么用,但我觉得这么返回容易理解些。

def multibox_prior(data, sizes, ratios):
    """生成以每个像素为中心具有不同形状的锚框
    我这里将锚框的宽高使用以下运算得到: sqrt(h*w*r) * s 和 sqrt(h*w/r) * s ,
    这样相乘可以得到缩放的面积h*w*s^2,相除得到宽高比r
    且我这个函数出来的结果可以直接使用,不需要reshape后再乘原图高和宽

    :param data: 图片数据,主要用来获取宽和高和数据设备
    :param sizes: 多组缩放比,推断文中的意思是从原来的 h*w 的面积变为 h*w*s^2
    :param ratios: 多组锚框宽高比
    :return shape [h, w, n+m-1锚框数量, 4左上坐标和右下坐标]  结果数据是从上往下每一行的从左往右,从形状也可以看出来
    """
    in_height, in_width = data.shape[-2:]
    device, num_sizes, num_ratios = data.device, len(sizes), len(ratios)
    size_tensor = torch.tensor(sizes, device=device)
    ratio_tensor = torch.tensor(ratios, device=device)

    # s[0]和r[0]与其他元素结合得到n+m-1组不同的比例
    size_ratio_tensor = torch.concat([torch.stack((size_tensor, ratio_tensor[0].repeat(num_sizes)), dim=1),
                                      torch.stack((size_tensor[0].repeat(num_ratios - 1), ratio_tensor[1:]), dim=1)])
    # 每个锚框的宽和高
    width_height_tensor = torch.stack(
        [size_ratio_tensor[:, 0] * torch.sqrt(h * w * size_ratio_tensor[:, 1]),
         size_ratio_tensor[:, 0] * torch.sqrt(h * w / size_ratio_tensor[:, 1])], dim=1)

    # 中心点横纵坐标,再通过unsqueeze得到形状[h, w, 1, 2]便于后续广播直接加减
    xy_origin = torch.stack(
        torch.meshgrid(torch.arange(0.5, in_width + 0.5, device=device),
                       torch.arange(0.5, in_height + 0.5, device=device),
                       indexing='xy'),
        dim=2).unsqueeze(dim=-2)

    # 最后输出锚框的左上角和右下角 x1,y1,x2,y2
    offset_width_height = width_height_tensor / 2
    x1y1 = xy_origin - offset_width_height
    x2y2 = xy_origin + offset_width_height

    return torch.concat((x1y1, x2y2), dim=-1)

看了后面的章节,知道了这里计算不依赖h和w然后再在外面乘一遍是有原因的,那么上面的缩放系数的确切定义还是和文章里不一样,这里看不懂建议先看后面这些具体是如何使用的。