【目标检测算法-锚框公式推导及代码详解】

0 沐神对锚框的宽高计算并未推导以及讲解

在沐神b站视频以及书籍中并未对锚框的宽高计算进行推导,只是给出结论:锚框的宽度和高度分别分别是 h s r hs\sqrt{r} hsr h s / r hs/\sqrt{r} 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:])))

基于此,我将对锚框宽高的公式进行推导,并且利用实际图片进行验证。
参考资料:

[1]: 动手学深度学习-锚框.
[2]: 锚框(anchor box)理解和代码实现.

1 锚框宽高公式推导

1.1 基础概念

假设输入图像的高度为H,宽度为W 。以图像的每个像素为中心生成不同形状的锚框,假设锚框的高度为h,宽度为w:

  • 真实边界框(ground-truth bounding box)
    在训练集中人工标定的已经包含标号物体的边框,其坐标 ( X m i n , Y m i n , X m a x , Y m a x ) (X_{min},Y_{min},X_{max},Y_{max}) (XminYminXmaxYmax)即为待检测目标的真实坐标。
  • 缩放比(scale)
    锚框的面积照输入图像按比例缩放的大小,其中 s ∈ ( 0 , 1 ] s∈(0,1] s(0,1]。用公式解释就是:
    w h W H = s 2 \frac{wh}{WH}=s^2 WHwh=s2此处歧义点是h和H是不是有 h H = s \frac{h}{H}=s Hh=s的关系,其实是没有的,不然锚框的宽高都是固定的了。锚框的宽和高与输入图片的宽高没有对应关系,只是锚框的面积与输入图片的面积成整体缩小的关系。
  • 宽高比(aspect ratio)
    生成锚框的宽和高的比率,其中 r > 0 r>0 r>0。用公式解释就是:
    w h = r \frac{w}{h}=r hw=r
  • 锚框(aspect ratio)
    锚框是以每个像素为中心,生成多个缩放比和宽高比不同的框体。目标检测就是拿这若干个锚框与真实边界框进行比较,寻找更贴近真实边界框的过程。
  • 锚框的个数
    为了生成多个不同形状的锚框,让我们设置许多缩放比取值 s 1 , s 2 , . . . , s n s_1,s_2,...,s_n s1,s2,...,sn 。宽高比取值 r 1 , r 2 , . . . , r m r_1,r_2,...,r_m r1,r2,...,rm。为了方便取值,我们只考虑包含 s 1 s_1 s1 r 1 r_1 r1的组合: ( s 1 , r 1 ) , ( s 1 , r 2 ) , . . . , ( s 1 , r m ) , ( s 2 , r 1 ) , . . . , ( s n , r 1 ) , (s_1,r_1),(s_1,r_2),...,(s_1,r_m),(s_2,r_1),...,(s_n,r_1), (s1,r1),(s1,r2),...,(s1,rm),(s2,r1),...,(sn,r1),引用参考链接2的图片:
    在这里插入图片描述
    也就是说,以同一像素为中心的锚框的数量是n+m-1
    代码举例则是:
    Y = multibox_prior(X,sizes = [0.75,0.5,0.25],
    					 ratios=[1,2,0.5])
    #len(sizes)=3,len(ratios)=3,一个像素共生成3+3-1=5个锚框
    
    对于整个输入图像,将共生成个 W ∗ H ∗ ( n + m − 1 ) W*H*(n+m-1) WH(n+m1)锚框。

1.2 锚框宽高公式推导

  • 输入图片归一化
    将输入图片的大小归一化到(0,1)的尺度,这样只要知道图像的scale就能够很容易在当前尺度下使用矩形框定位。假设 W , H W,H WH为输入图片的宽高,归一化方程如下:
    { W W = 1 H H = 1 \begin{cases}\frac{W}{W}=1\\[3ex]\frac{H}{H}=1 \\[3ex]\end{cases} WW=1HH=1
  • 锚框宽高推导
    w , h w,h w,h为锚框的实际宽和高,则有:
    { w h W H = s 2 w h = r \begin{cases}\frac{wh}{WH}=s^2\\[3ex]\frac{w}{h}=r \\[3ex]\end{cases} WHwh=s2hw=r → \rightarrow { w = s H W r h = s W H r \begin{cases} w=s\sqrt{HWr} \\[3ex] h=s\sqrt{\frac{WH}{r}} \\[3ex]\end{cases} w=sHWr h=srWH
    w , h w,h w,h归一化:
    w 0 = w W = s H r W w_0 = \frac{w}{W} = s\sqrt{\frac{Hr}{W}} w0=Ww=sWHr h 0 = h H = s W H r h_0 = \frac{h}{H} = s\sqrt{\frac{W}{Hr}} h0=Hh=sHrW 显然:当H = W时 w 0 = s r w_0 =s\sqrt{r} w0=sr h 0 = s r h_0 =\frac{s}{\sqrt{r}} h0=r s
  • 归一化后的锚框宽高比
    因为输入图片和锚框都进行了归一化计算,所以归一化后的锚框宽高比也对应发生变化,我们利用公式进行推导,并且利用图片进行验证其变化。
    r 0 = w 0 h 0 = H r W r_0 = \frac{w_0}{h_0}= \frac{Hr}{W} r0=h0w0=WHr

1.3 图片验证计算

在这里插入图片描述

已知图片的像素为(889,500),为了形象的表示图片锚框的计算和归一化的过程。我将其放在ppt中进行绘制。如上图我巧妙的设置了一个锚框框住了喵喵。在ppt中三个框的宽度分别为4.81cm,4.63cm,4.81cm,于是锚框的坐标信息为 x m i n = W ∗ w 1 w 1 + w 2 + w 3 = 889 ∗ 4.81 4.81 + 4.63 + 4.81 = 300 x_{min} = \frac{W*w_1}{w_1+w_2+w_3} = \frac{889*4.81}{4.81+4.63+4.81} = 300 xmin=w1+w2+w3Ww1=4.81+4.63+4.818894.81=300 x m a x = W ∗ ( w 1 + w 2 ) w 1 + w 2 + w 3 = 889 ∗ ( 4.81 + 4.63 ) 4.81 + 4.63 + 4.81 = 589 x_{max} = \frac{W*(w_1+w_2)}{w_1+w_2+w_3} = \frac{889*(4.81+4.63)}{4.81+4.63+4.81} = 589 xmax=w1+w2+w3W(w1+w2)=4.81+4.63+4.81889(4.81+4.63)=589,高度为图片的高度。
归一化后 x m i n N = x m i n W = 300 889 = 0.34 x_{minN} = \frac{x_{min} }{W} = \frac{300}{889} = 0.34 xminN=Wxmin=889300=0.34 x m a x = x m a x W = 589 889 = 0.66 x_{max} = \frac{x_{max} }{W} = \frac{589}{889} = 0.66 xmax=Wxmax=889589=0.66
于是计算出锚框的宽高分别为 h = 300 , w = 289 h=300,w=289 h=300,w=289,根据缩放比和宽高比的定义,反算缩放比和宽高比,计算过程如下:
{ s 1 2 = w h W H r 1 = w h \begin{cases}s_1^2=\frac{wh}{WH}\\[3ex] r_1=\frac{w}{h} \\[3ex]\end{cases} s12=WHwhr1=hw → \rightarrow { s = w h W H = 289 ∗ 500 889 ∗ 500 = 0.57 r 1 = w h = 289 500 = 0.58 \begin{cases} s=\sqrt{\frac{wh}{WH}}=\sqrt{\frac{289*500}{889*500}}=0.57 \\[3ex] r_1=\frac{w}{h} =\frac{289}{500}=0.58 \\[3ex]\end{cases} s=WHwh =889500289500 =0.57r1=hw=500289=0.58
计算得出 s = 0.57 , r = 0.58 s=0.57,r=0.58 s=0.57,r=0.58
此时我们用这组(r1,s1)来计算归一化的 r 0 r_0 r0,看看是不是和实际情况相同。
r 0 = w 0 h 0 = H r W = 500 ∗ 0.58 889 = 0.32 r_0 = \frac{w_0}{h_0}= \frac{Hr}{W}= \frac{500*0.58}{889}=0.32 r0=h0w0=WHr=8895000.58=0.32
而右图中 r 0 = w 0 h 0 = 0.32 1 = 0.32 r_0 =\frac{w_0}{h_0}=\frac{0.32}{1}=0.32 r0=h0w0=10.32=0.32,说明计算公式正确。之后我们会将 s = 0.57 , r = 0.58 s=0.57,r=0.58 s=0.57,r=0.58设置为代码参数,进一步验证代码的正确性。

1.4 小结

  • 1.锚框的宽高计算公式:
    w 0 = w W = s H r W w_0 = \frac{w}{W} = s\sqrt{\frac{Hr}{W}} w0=Ww=sWHr h 0 = h H = s W H r h_0 = \frac{h}{H} = s\sqrt{\frac{W}{Hr}} h0=Hh=sHrW
  • 2.归一化后的锚框宽高比不为r,而是 r 0 = H r W r_0=\frac{Hr}{W} r0=WHr

2 代码详解

代码部分主要分为4块。

  • 1:调包并导入数据;
  • 2:生成多个锚框的代码;
  • 3:编写展示锚框代码;
  • 4:数据验证代码正确性
    我尽量在每个关键步骤都写上注释。

2.1 看一下数据

import torch
from d2l import torch as d2l
import matplotlib.pyplot as plt
d2l.set_figsize()
img = d2l.plt.imread(r"./torch_data/test_imgs/cat_dogs.jpg")
d2l.plt.imshow(img)
data = torch.tensor(img)
#看一下输入图片的形状
data.shape
#jpg图片是[h(高),w(宽),c(通道数)]格式

torch.Size([500, 889, 3])

2.2 生成多个锚框(重点)

根据输入图片以及多个不同缩放比和宽高比,在同一个像素下生成多个锚框。此代码较沐神给出的代码做出了一些更改,主要更改的内容在w和h的计算公式以及一些标号的解释。希望大家能看的明白!

def multibox_prior(data,sizes,ratios):
    """生成以每个像素为中心具有不同形状的锚框
        data:[height,width,channels];
        sizes:list;
        ratios:list
        output:[1,height*width*(len(sizes)+len(ratios)-1),4]"""
    #输入图像的高和宽
    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,0.5,因为中心点要设置(0.50.5)的偏移
    offset_h,offset_w = 0.5,0.5
    
    #归一化系数,将图像的宽高归一化到0-1之间
    step_h = 1/in_height
    step_w = 1/in_width
    
    #生成所有锚框的中心点坐标,并将数值归一化到0-1
    center_h = (torch.arange(in_height,device=device)+offset_h)*step_h
    center_w = (torch.arange(in_width,device=device)+offset_w)*step_w
    
    #网格化中心点坐标
    shift_y,shift_x = torch.meshgrid(center_h,center_w)
    
    #reshape成一维,shift_y和shift_x坐标一一对应
    shift_y,shift_x = shift_y.reshape(-1),shift_x.reshape(-1)
    
    #每个像素生成的锚框的宽和高,因为图片归一化了,将锚框高宽缩小到和图片一样的格式
    #计算公式:w_0 = s*(r)*(H/W)  h_0 = s/((r)*(H/W))
    
    #norm=(H/W),这个就是个标号,方便计算 
    norm = torch.sqrt(torch.tensor(in_height)/torch.tensor(in_width)) 
    
    #只考虑包含s1或r1的组合,因此S*r1 与s1*R合并即为n+m-1个锚框
    w_0 = torch.cat((size_tensor * torch.sqrt(ratio_tensor[0]),
                   size_tensor[0] * torch.sqrt(ratio_tensor[1:])))*norm
    h_0 = torch.cat((size_tensor / torch.sqrt(ratio_tensor[0]),
                   size_tensor[0] / torch.sqrt(ratio_tensor[1:])))/norm
                   
    #除以2来获得半高和半宽,转置是生成(锚框个数)*4个位置数据)的格式
    anchor_manipulations = torch.stack((-w_0, -h_0, w_0, h_0)).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)

2.3 在图像上绘制多个锚框

构建show_bboxes()函数,用来展示多个锚框。此处代码不必深度理解,只是个工具。

#@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

    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))

2.4 图片验证锚框宽高计算

根据1.3部分的内容,设置(s1,r1)=(0.57,0.58),且中心点为(250,445),利用(s1,r1)代入代码生成对应图像,验证生成的锚框图像是否框住了喵喵,并且是否与1.3设计的锚框形状相同。

H,W = data.shape[:2]
#s1=0.57 r1=0.58
Y = multibox_prior(data,sizes = [0.57],ratios=[0.58])
#一共生成生成500*889*(1+1-1)=444500个锚框,锚框,4是位置信息[xmin,ymin,xmax,ymax]
print(Y.shape)
#锚框分割(H,W,1,4)由于s=1,r=1,每个像素点生成1个锚框,每个锚框有4个位置信息
boxes = Y.reshape(H,W,1,4)
#此处查看中心点(250,445)(s1,r1)=0.57,0.58)建立的锚框
boxes[250,445,:,:]

在这里插入图片描述
利用show_bboxs()函数,查看该锚框的生成情况。

d2l.set_figsize()
bbox_scale = torch.tensor((W, H, W, H))
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, boxes[250, 445, :, :] * bbox_scale,
            ['s=0.57, r=0.58'])

代码生成锚框
设计锚框

由上图可知,该锚框很好的圈住了喵喵,且与设计的形状基本相同,可以认为锚框的计算公式和代码正确。

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值