YOLO中Anchor生成介绍

YOLOv1

利用全连接层直接对边界框进行预测

YOLOv2

YOLOv2通过缩减网络,使用416x416的输入,模型下采样的总步长为32,最后得到13x13的特征图,然后对13x13的特征图的每个cell预测5个anchor boxes,对每个anchor box预测边界框的位置信息、置信度和一套分类概率值。使用anchor boxes之后,YOLOv2可以预测13x13x5=845个边界框

YOLOv4

完成cfg文件的解析,模型的创建与权重文件的加载之后,现在要做的就是执行检测操作,主要调用了utils/utils.py中的do_detect() 函数,在demo.py中:

boxes = do_detect(m, sized, 0.5, 0.4, use_cuda)

模型forward后输出结果存在list_boxes中,因为有3个yolo输出层,所以这个列表list_boxes中又分为3个子列表;

其中list_boxes[0]中存放的是第一个yolo层输出,其特征图大小对于原图缩放尺寸为8,即strides[0], 对于608x608图像来说,该层的featuremap尺寸为608/8=76;则该层的yolo输出数据维度为[batch, (classnum+4+1)*num_anchors, feature_h, feature_w] , 对于80类的coco来说,测试图像为1,每个yolo层每个特征图像点有3个锚点,该yolo层输出是[1,255,76,76];对应锚点大小为[1.5,2.0,2.375,4.5,5.0,3.5]; (这6个数分别是3个锚点的w和h,按照 w 1 , h 1 , w 2 , h 2 , w 3 , h 3 w_1,h_1,w_2,h_2,w_3,h_3 w1,h1,w2,h2,w3,h3排列);

  • 第二个yolo层检测结果维度为[1,255,38,38],对应锚点大小为:[2.25,4.6875,4.75,3.4375,4.5,9.125],
    输出为 [1,255,38,38]
  • 第三个yolo层检测维度为[1,255,19,19],对应锚点大小为:[4.4375,3.4375,6.0,7.59375,14.34375,12.53125],
    输出为 [1,255,19,19];

do_detect() 函数中主要是调用了
get_region_boxes1(output, conf_thresh, num_classes, anchors, num_anchors, only_objectness=1, validation=False)
这个函数对forward后的output做解析并做nms操作;

每个yolo层输出数据分析,对于第一个yolo层,输出维度为[1,85*3,76,76 ]; 会将其reshape为[85, 1*3*76*76],即有1*3*76*76个锚点在预测,每个锚点预测信息有80个类别的概率和4个位置信息和1个是否包含目标的置信度;

yolov4的输出需要包含的信息有:物体的位置信息、有物体的概率、物体的分类,可以写为:( t x , t y , t w , t h , o b j , c l s t_x, t_y, t_w, t_h, obj, cls tx,ty,tw,th,obj,cls),其中前四个是物体的位置信息,最后一个cls,根据分类的类别数,维度不同,如果只有1个类别,那就只占1个位置,如果是2个类别,就是2个位置,使用one-hot编码。

而在实际的yolov4的最终输出之前是(19,19,1024),通过蓝色的18个(当只有1个分类类别的时候,如果有2个,那就是19个卷积核)111024的卷积核,得到(19,19,18),最后reshape成(3,19,19,tx, ty, tw, th, obj, cls)。后面的是检测的物体信息,而前面的(3,19,19)的理解如下:

19*19个特征点,等同于把原图分成了19 *19个网格,原图的输入是608 *608的,那么每一个网格的大小就是608/19=32,每一个特征点只关注对应的网格,判断对应的网格是否有物体。

模型输出decode

模型的输出有三个,分别是(B, 255, 19, 19),(B, 255, 38, 38),(B, 255, 76, 76),因此需要对这三个输出分别解码。

1.维度变换

首先需要将输出view成(B, A, n_ch, H, W)的形式,其中H和W就是输出的尺寸,A是锚框数量,n_ch是包含了bx, by, bw, bh, obj, cls的信息,维度为4+1+80=85。之后再进行一个维度变换,最终得到(B, 3, 19, 19, 85)的维度(以第一个为例)。此时,最后一个维度85,包含了我们解码所需的所有信息,也就是说,我们需要对前面B319*19这么多的数据,都进行同样方式的解码。

2.读取位置信息

接下来我们取出来bx, by, bw, bh, obj, cls。注意,此时除了cls之外,其他所有的维度,都减少了一维变成了(B, 3, 19, 19),因为cls是以切片形式取的,所以维度数量不变,是(B, 3, 19, 19, 80)。

# 取出来 bx, by, bw, bh
bx, by = output[..., 0], output[..., 1]
bw, bh = output[..., 2], output[..., 3]
# 取出来obj和cls
obj = output[..., 4]
cls = output[..., 5:]

3.坐标变换

我们需要先把bx, by取一个sigmoid,把bw和bh取一个exp。这里加上了缩放因子,据说当图片中的目标既有大又有小的时候,会起作用,暂时没见到实际起作用的情况,不过先加上了,当缩放因子为1的时候,相当于不起作用。这里我们同时也对物体以及分类的置信度取sigmoid。

    # 进行初步转换
    bx = torch.sigmoid(bx)
    by = torch.sigmoid(by)
    bw = torch.exp(bw) * scale_x_y - 0.5 * (scale_x_y - 1)
    bh = torch.exp(bh) * scale_x_y - 0.5 * (scale_x_y - 1)
    # 对物体置信度,分类置信度也取sigmoid
    det_confs = torch.sigmoid(obj)
    cls_confs = torch.sigmoid(cls)

4.构建网格

目标是找到点相对于整张图的偏移的比例,但由于我们分了网格,因此先找相对于网格的偏移比例。

这里,图像被分成了19*19个网格,假如中心点在第2行第3列的网格里面。

相对网格的偏移量肯定是小数,比如在x轴方向上偏移是0.5,在y轴上偏移是0.2,也就是在网格中间偏上的位置。

那么,以网格为单位,相对于所有网格来说,中心点的实际偏移量:

在x轴方向的0.5,加上偏移的网格数,也就是2(从0开始计数),那么得到了2.5就是以网格为单位,相对于整张网格图的x轴偏移量。同理,y轴方向上,相对于y轴的,就是1.5。

因此我们需要把x和y方向上的网格数量构建一下,并找到以网格为单位的偏移量。

    # 构建网格grid_x和grid_y
    grid_x = torch.arange(W, dtype=torch.float).repeat(1, 3, W, 1).to(device)
    grid_y = torch.arange(W, dtype=torch.float).repeat(1, 3, H, 1).permute(0, 1, 3, 2).to(device)
 
    # 求bx和by
    bx = bx + grid_x
    by = by + grid_y

5. 计算实际偏移量

既然找到了相对于网格的偏移量,那么偏移的比例就是偏移量除以网格长度,这个比例,就是相对于网格的偏移比例,同时也是相对于整张图的偏移比例。另外根据解码图,我们对于w和h,还需要乘上先验框的宽、高,得到最终的bw和bh。

    # 取每个anchor的长和宽,求bw和bh
    for i in range(num_anchors):
        bw[:, i, ...] *= anchors[i * 2]
        bh[:, i, ...] *= anchors[i * 2 + 1]
    # 对数据转换,除以网格数量,得到相对整张图的偏移的比例,并增加一个维度
    bx = (bx / W).unsqueeze(-1)
    by = (by / H).unsqueeze(-1)
    bw = (bw / W).unsqueeze(-1)
    bh = (bh / H).unsqueeze(-1)

6.得到输出

现在对于中心点相对于原图的偏移量,以及宽高都得到了,我们把这四个数据结合起来,再把obj和cls的置信度都合在一起,就得到了我们最终解码后的输出,用于后续的画图等计算。

    # 四个数据拼接起来,并reshape成[B, -1, 4]的形状
    boxes = torch.cat([bx, by, bw, bh], dim=-1).reshape(B,A*W*H, 4)
    det_confs = det_confs.unsqueeze(-1).reshape(B,A*W*H, 1)
    cls_confs = cls_confs.reshape(B,A*W*H, num_classes)
 
    outputs = torch.cat([boxes, det_confs, cls_confs], dim=-1)

注:(网格生成)

grid_x = torch.linspace(0, input_width - 1, input_width).repeat(input_height, 1).repeat(
                batch_size * len(self.anchors_mask[i]), 1, 1).view(x.shape).type(FloatTensor)
#先生成一行(width),在重复一列(height),再扩充维度,最后view成[batchsize, 3, h, w]

grid_y = torch.linspace(0, input_height - 1, input_height).repeat(input_width, 1).t().repeat(
                batch_size * len(self.anchors_mask[i]), 1, 1).view(y.shape).type(FloatTensor)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值