Anchor在目标检测中应用广泛,其本质上是在原图上预先定义好的一系列大小不一的矩形框。当然,这个框并不是随便画的,需要我们根据backbone以及特征图进行设计。
假设我们将一张255*255
的图片输入backbone,经过两次stride=2的卷积以及一个pooling,最终得到了17*17
的特征图,backbone的总步长为total_stride=2*2*2=8
.那么要怎么为这个特征图生成anchor呢?
首先我们需要设计anchor的长宽比。考虑为特征图的每个点生成长宽比不一样的多个anchor,比如[0.33, 0.5, 1, 2, 3]
,就代表了5种长宽比的anchor,注意,这里0.33就是1/3,0.5就是1/2,刚好就是长宽对调。
另外,我们还需要设计anchor的大小,这个大小应该根据backbone的总步长来设计。backbone的总步长指定了最初的类似感受野的区域大小,因为经过多层卷积池化之后,feature map上一点的感受野对应到原始图像就会是一个区域。我们假设的backbone的总步长为total_stride=2*2*2=8
,那么anchor的基础范围应该是[total_stride, total_stride]=[8, 8]
,这个就是我们需要的基础anchor的边长。下一步就是基于长宽比和这个基础的边长计算其余几种长宽比的边长。方法是从面积的角度出发,保证几种不同长宽比的anchor的面积大致相等,w = total_stride / sqrt(ratio)
, h = total_stride * sqrt(rario)
最后,我们还需要考虑anchor的尺度,经过上一步我们得到的anchor可能会偏小,这时候可以乘上一个系数将anchor放大,比如[8,16],就代表了8和16两种尺度.个人感觉这个参数需要根据不同的任务确定,目标是使生成的anchor们尽可能接近真实GT的大小,减小回归难度。
现在,我们还是基于这个假设的backbone,给出以下参数:
input_sz = 255 # 输入图片大小
total_stride = 8 # backbone总步长
score_size = 17 # 特征图尺寸
ratios = [0.33, 0.5, 1, 2, 3] # 长宽比
scales = [8, ] # 多尺度
最终应该生成多少个anchor呢?
答案是17*17*len(ratios)*len(scale)=1445
个,即特征图的每个像素都有5*1
个anchor,5来自于5种长宽比,1来自于一种尺度。
下面是anchor的生成以及可视化代码。
首先计算每种长宽比anchor的边长,并且乘上尺度系数。
其次计算anchor的中心点坐标。
返回的值:相对原点坐标系的anchor中心坐标以及anchor的w和h
import numpy as np
import cv2
def generate_anchor(total_stride, scales, ratios, score_size):
"""
from SiamDW
slight different with released SiamRPN-VOT18
prefer original size without flatten
生成的anchor坐标为[cx,xy,w,h], cx, cy代表anchor的中心
"""
anchor_num = len(ratios) * len(scales)
anchor = np.zeros((anchor_num, 4), dtype=np.float32)
size = total_stride * total_stride
count = 0
# 生成5种长宽比的anchor [cx, cy, w, h], cx, cy为相对中心点的坐标
for ratio in ratios:
ws = int(np.sqrt(size / ratio)) # 各种长宽比的基础anchor的w
hs = int(ws * ratio) # 各种长宽比的基础anchor的h
# 多个尺度
for scale in scales:
wws = ws * scale
hhs = hs * scale
anchor[count, 0] = 0
anchor[count, 1] = 0
anchor[count, 2] = wws
anchor[count, 3] = hhs
count += 1
score_size = int(score_size)
# 17*17的特征图中的每个像素对应anchor_num个anchor
anchor = np.tile(anchor, score_size * score_size).reshape((-1, 4)) # 5种anchor每个复制17*17次
# 上一步得到的所有anchor中心仍在[0,0],需要加上偏移量, 由[0,0,w,h]经过偏移生成[cx,cy,w,h]
ori = - (score_size // 2) * total_stride # 偏移量
# np.meshgrid: 生成网格点坐标
# 加了偏移量ori之后, 所有anchor的中心点坐标由原点->相对原点的坐标
xx, yy = np.meshgrid([ori + total_stride * dx for dx in range(score_size)],
[ori + total_stride * dy for dy in range(score_size)])
xx, yy = np.tile(xx.flatten(), (anchor_num, 1)).flatten(), \
np.tile(yy.flatten(), (anchor_num, 1)).flatten()
anchor[:, 0], anchor[:, 1] = xx.astype(np.float32), yy.astype(np.float32)
anchor = np.reshape(anchor, (5, score_size, score_size, 4)) # this order is right [5, 17, 17, 4]
anchor = np.transpose(anchor, (3, 0, 1, 2)) # [4,5,17,17]
return anchor # [4, 5, 17, 17]
total_stride = 8
scales = [8, ]
ratios = [0.33, 0.5, 1, 2, 3]
score_size = 17
input_sz = 255
anchors = generate_anchor(total_stride, scales, ratios, score_size).reshape(4, -1).transpose(1, 0)
img = np.ones((255, 255, 3))
for anchor in anchors:
x, y, w, h = anchor
cx = x + (input_sz + 1)/2 # 转换到原图左上角的坐标系
cy = y + (input_sz + 1)/2
left_up_x = cx - w / 2
left_up_y = cy - h / 2
right_down_x = left_up_x + w
right_down_y = left_up_y + h
pt1 = (int(left_up_x), int(left_up_y))
pt2 = (int(right_down_x), int(right_down_y))
cv2.rectangle(img, pt1, pt2, color=(255, 0, 0), thickness=1, lineType=1)
left_up_x = 12
left_up_y = 48
right_down_x = 116
right_down_y = 80
pt1 = (int(left_up_x), int(left_up_y))
pt2 = (int(right_down_x), int(right_down_y))
cv2.rectangle(img, pt1, pt2, color=(0, 0, 255), thickness=1, lineType=1)
cv2.namedWindow("img", cv2.WINDOW_NORMAL)
cv2.imshow("img", img)
print(img.shape)
cv2.waitKey(0)