【目标检测算法

时间: 2023-12-04 admin IT培训

【目标检测算法

【目标检测算法

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

  • 0 沐神对锚框的宽高计算并未推导以及讲解
  • 1 锚框宽高公式推导
    • 1.1 基础概念
    • 1.2 锚框宽高公式推导
    • 1.3 图片验证计算
    • 1.4 小结
  • 2 代码详解
    • 2.1 看一下数据
    • 2.2 生成多个锚框(重点)
    • 2.3 在图像上绘制多个锚框
    • 2.4 图片验证锚框宽高计算

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}) (Xmin​,Ymin​,Xmax​,Ymax​)即为待检测目标的真实坐标。
  • 缩放比(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) W∗H∗(n+m−1)锚框。

1.2 锚框宽高公式推导

  • 输入图片归一化
    将输入图片的大小归一化到(0,1)的尺度,这样只要知道图像的scale就能够很容易在当前尺度下使用矩形框定位。假设 W , H W,H W,H为输入图片的宽高,归一化方程如下:
    { 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​=h0​w0​​=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​+w3​W∗w1​​=4.81+4.63+4.81889∗4.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​+w3​W∗(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​=WHwh​r1​=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​ ​=889∗500289∗500​ ​=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​=h0​w0​​=WHr​=889500∗0.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​=h0​w0​​=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(通道数)]格式

2.2 生成多个锚框(重点)

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

def multibox_prior(data,sizes,ratios):"""生成以每个像素为中心具有不同形状的锚框data:[height,width,channels];sizes:list;ratios:listoutput:[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.5,0.5)的偏移offset_h,offset_w = 0.5,0.5#归一化系数,将图像的宽高归一化到0-1之间step_h = 1/in_heightstep_w = 1/in_width#生成所有锚框的中心点坐标,并将数值归一化到0-1center_h = (torch.arange(in_height,device=device)+offset_h)*step_hcenter_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:])))*normh_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_manipulationsreturn 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_valueselif not isinstance(obj, (list, tuple)):obj = [obj]return objlabels = _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'])


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