• yolov4 预测框解码详解【附代码】


    在上一篇文章中yolov4 LOSS代码详解【附代码】 * 详细讲解了训练过程中loss的实现过程。这篇文章将关注预测过程中box的解码问题。

    1. # ---------------------------------------------------------#
    2. # 将图像输入网络当中进行预测!
    3. # ---------------------------------------------------------#
    4. outputs = self.net(images)
    5. outputs = self.bbox_util.decode_box(outputs)

    上面这段代码中,net是我们定义的yolov4网络,images就是我们输入的图像,outputs就是网络输出。

    此刻我的outputs类型是tuple形式,里面有三个output,outputs=(output0,output1,output2)。这三个output的shape分别是(1,255,19,19)、(1,255,38,38)、(1,255,76,76)。 255=3*(5+80),80是coco类别数量,5表示box的信息和是否有类。

    decode_box()函数就是本次的研究内容,它会对网络的输出进行解码,获得新的输出。


    decode_box详解:

    获得输入尺寸

    首先定义一个outputs的空列表,用来存储后面我们处理后的输出。

    这里的inputs就是 网络的输出,通过enumerate进行枚举,input就是yolo的三个输出特征层。可以获得各个尺寸,以输入大小608为例,第一次遍历时,batch_size=1,input_height=input_height=19。

    1. outputs = []
    2. for i, input in enumerate(inputs):
    3. #-----------------------------------------------#
    4. # 输入的input一共有三个,他们的shape分别是
    5. # batch_size, 255, 13, 13
    6. # batch_size, 255, 26, 26
    7. # batch_size, 255, 52, 52
    8. #-----------------------------------------------#
    9. batch_size = input.size(0)
    10. input_height = input.size(2)
    11. input_width = input.size(3)

     获得步长(缩放比)

     接下来是计算缩放比例,就是计算网络输入缩小多少倍变成特征层大小【有些地方把这个比例也叫步长】。

    1. #-----------------------------------------------#
    2. # 输入为416x416时
    3. # stride_h = stride_w = 32、16、8
    4. #-----------------------------------------------#
    5. stride_h = self.input_shape[0] / input_height
    6. stride_w = self.input_shape[1] / input_width

    由于此时的网络输入大小是608,特征层大小为19,因此部长为32,就是缩小了32倍。

    stride_h=stride_w=32 

    计算anchor相对特征层缩放尺寸

     接下来是计算anchors的缩放尺寸。也就是将anchors映射到特征层上。

    缩放前anchors:

    [[ 12.  16.],

    [ 19.  36.],

    [ 40.  28.],------>对应76 * 76特征层

    [ 36.  75.],

    [ 76.  55.],

    [ 72. 146.],----->对应38 * 38 特征层

    [142. 110.],

    [192. 243.],

    [459. 401.]]---->对应19 * 19特征层

    由于我们现在先需要处理的是19 * 19 这个特征层的,通过anchors_mask进行索引获得对应的anchors。此时我们计算得到的缩放比例为32,因此这三个尺寸的anchors也需要缩小32倍。

    1. #-------------------------------------------------#
    2. # 此时获得的scaled_anchors大小是相对于特征层的
    3. #-------------------------------------------------#
    4. scaled_anchors = [(anchor_width / stride_w, anchor_height / stride_h) for anchor_width, anchor_height in self.anchors[self.anchors_mask[i]]]

    得到缩放后的19 * 19 特征层上3种anchors的尺寸。 

    [(4.4375, 3.4375),

    (6.0, 7.59375),

    (14.34375, 12.53125)] 

    获得网络预测信息(box和类) 

    我们的input shape为(1,255,19,19),这样不太直观,也不好处理,因此我们对shape进行一个改变,变为(1,3,85,19,19),在用permute(0,1,3,4,2)对维度顺序进行改变,此刻变为(1,3,19,19,85),也就是我们得到的prediction。

    这里需要说一下,prediction[...,0] = prediction[:,:,:,:,0]。

    取最后一个维度【也就是5+80这个维度上】的前4列可以获得所有cell上预测box的center_x,center_y,w,h,第五列(prediction[...,4])是是否有物体【此刻的conf还是hard形式,所以需要经过sigmoid函数给出概率值】。从prediction[...,5:]就是80个类信息【同样也是hard形式】,进行sigmoid可以获得三种anchor在19 * 19网格上所有类别的预测置信度。

    1. #-----------------------------------------------#
    2. # 输入的input一共有三个,他们的shape分别是
    3. # batch_size, 3, 13, 13, 85
    4. # batch_size, 3, 26, 26, 85
    5. # batch_size, 3, 52, 52, 85
    6. #-----------------------------------------------#
    7. prediction = input.view(batch_size, len(self.anchors_mask[i]),
    8. self.bbox_attrs, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous()
    9. #-----------------------------------------------#
    10. # 先验框的中心位置的调整参数
    11. #-----------------------------------------------#
    12. x = torch.sigmoid(prediction[..., 0])
    13. y = torch.sigmoid(prediction[..., 1])
    14. #-----------------------------------------------#
    15. # 先验框的宽高调整参数
    16. #-----------------------------------------------#
    17. w = prediction[..., 2]
    18. h = prediction[..., 3]
    19. #-----------------------------------------------#
    20. # 获得置信度,是否有物体
    21. #-----------------------------------------------#
    22. conf = torch.sigmoid(prediction[..., 4])
    23. #-----------------------------------------------#
    24. # 种类置信度
    25. #-----------------------------------------------#
    26. pred_cls = torch.sigmoid(prediction[..., 5:])

    比如我这里举个例子。比如我现在要获得所有person这个类在所有cell中内所有anchor的置信度。那么一个有3*19*19*1个值。

    tensor([[[[1.5125e-03, 5.7717e-03, 1.2517e-02,  ..., 9.4604e-03,
               6.4645e-03, 3.9380e-03],
              [3.4480e-02, 3.0047e-02, 1.8495e-02,  ..., 1.7670e-02,
               2.7245e-02, 1.4117e-02],
              [1.1721e-01, 8.4902e-02, 2.8659e-02,  ..., 4.6449e-02,
               1.5122e-01, 9.6072e-02],
              ...,
              [8.8295e-01, 9.0356e-01, 3.8897e-01,  ..., 1.9456e-03,
               1.5115e-02, 1.2139e-01],
              [8.5030e-01, 9.4131e-01, 6.9385e-01,  ..., 7.4368e-04,
               4.2023e-03, 1.2149e-01],
              [8.6811e-01, 8.6302e-01, 6.4376e-01,  ..., 2.2633e-03,
               5.0898e-03, 7.8965e-03]],

             [[6.2741e-03, 1.0798e-02, 1.7037e-02,  ..., 9.2335e-03,
               8.9423e-03, 2.2053e-02],
              [3.6071e-02, 2.1278e-02, 1.4221e-02,  ..., 1.4337e-02,
               1.4463e-02, 1.8076e-02],
              [1.4068e-01, 9.8277e-02, 4.1939e-02,  ..., 5.7934e-02,
               1.5278e-01, 1.1965e-01],
              ...,
              [8.6118e-01, 8.5999e-01, 5.3942e-01,  ..., 1.2218e-03,
               1.4375e-02, 2.0362e-01],
              [9.1455e-01, 9.4715e-01, 7.2468e-01,  ..., 8.6567e-04,
               4.2050e-03, 1.5936e-01],
              [8.8682e-01, 9.0414e-01, 7.7474e-01,  ..., 1.7651e-03,
               4.7418e-03, 1.7866e-02]],

             [[1.2892e-02, 1.0655e-02, 1.8664e-02,  ..., 1.4098e-02,
               1.0832e-02, 2.6804e-02],
              [4.9359e-02, 1.9839e-02, 3.1507e-02,  ..., 1.4819e-02,
               9.5383e-03, 2.0111e-02],
              [1.6157e-01, 1.6885e-01, 1.2669e-01,  ..., 9.5604e-02,
               1.5007e-01, 1.0721e-01],
              ...,
              [5.9196e-01, 6.2065e-01, 7.3476e-01,  ..., 2.0107e-03,
               1.2156e-02, 1.9672e-01],
              [8.8735e-01, 9.2701e-01, 8.6674e-01,  ..., 2.9043e-03,
               5.8838e-03, 1.0288e-01],
              [8.3874e-01, 8.6092e-01, 7.4291e-01,  ..., 7.3370e-03,
               8.0380e-03, 1.6741e-02]]]], device='cuda:0')

    生成网格

     然后我们就可以划分网格了。可以得到grid_x和grid_y。shape均为【1,3,19,19】.每种anchor都有19*19个网格。

    1. #----------------------------------------------------------#
    2. # 生成网格,先验框中心,网格左上角
    3. # batch_size,3,13,13
    4. #----------------------------------------------------------#
    5. grid_x = torch.linspace(0, input_width - 1, input_width).repeat(input_height, 1).repeat(
    6. batch_size * len(self.anchors_mask[i]), 1, 1).view(x.shape).type(FloatTensor)
    7. grid_y = torch.linspace(0, input_height - 1, input_height).repeat(input_width, 1).t().repeat(
    8. batch_size * len(self.anchors_mask[i]), 1, 1).view(y.shape).type(FloatTensor)

    按照网络格式生成先验框的宽和高

    下面的代码相当于是给所有的cell都设置好anchor的尺寸。

    1. #----------------------------------------------------------#
    2. # 按照网格格式生成先验框的宽高
    3. # batch_size,3,13,13
    4. #----------------------------------------------------------#
    5. anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0]))
    6. anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1]))
    7. anchor_w = anchor_w.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(w.shape)
    8. anchor_h = anchor_h.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(h.shape)

     利用预测结果对anchors进行调整

    接下来的这一步比较关键,因为我们获得分类结果是很好获得的,只需要获得置信度即可,但如果去利用anchor获得目标的尺寸【或者说box呢】。

    pred-boxes是用来存储调整后的box,x.data+grid_x这种操作其实就是将预测框的坐标信息映射到特征层(我们的网格)坐标。

    1. #----------------------------------------------------------#
    2. # 利用预测结果对先验框进行调整
    3. # 首先调整先验框的中心,从先验框中心向右下角偏移
    4. # 再调整先验框的宽高。
    5. #----------------------------------------------------------#
    6. pred_boxes = FloatTensor(prediction[..., :4].shape)
    7. pred_boxes[..., 0] = x.data + grid_x
    8. pred_boxes[..., 1] = y.data + grid_y
    9. pred_boxes[..., 2] = torch.exp(w.data) * anchor_w
    10. pred_boxes[..., 3] = torch.exp(h.data) * anchor_h

    输出结果归一化

    得到的output的shape为【1,3*19*19,85】

    1. #----------------------------------------------------------#
    2. # 将输出结果归一化成小数的形式
    3. #----------------------------------------------------------#
    4. _scale = torch.Tensor([input_width, input_height, input_width, input_height]).type(FloatTensor)
    5. output = torch.cat((pred_boxes.view(batch_size, -1, 4) / _scale,
    6. conf.view(batch_size, -1, 1), pred_cls.view(batch_size, -1, self.num_classes)), -1)
    7. outputs.append(output.data)

    上面就是我们box的解码过程了。最后再将输出结果经过NMS进行box过滤即可。

    完整的代码:

    1. def decode_box(self, inputs):
    2. outputs = []
    3. for i, input in enumerate(inputs):
    4. #-----------------------------------------------#
    5. # 输入的input一共有三个,他们的shape分别是
    6. # batch_size, 255, 13, 13
    7. # batch_size, 255, 26, 26
    8. # batch_size, 255, 52, 52
    9. #-----------------------------------------------#
    10. batch_size = input.size(0)
    11. input_height = input.size(2)
    12. input_width = input.size(3)
    13. #-----------------------------------------------#
    14. # 输入为416x416时
    15. # stride_h = stride_w = 32、16、8
    16. #-----------------------------------------------#
    17. stride_h = self.input_shape[0] / input_height
    18. stride_w = self.input_shape[1] / input_width
    19. #-------------------------------------------------#
    20. # 此时获得的scaled_anchors大小是相对于特征层的
    21. #-------------------------------------------------#
    22. scaled_anchors = [(anchor_width / stride_w, anchor_height / stride_h) for anchor_width, anchor_height in self.anchors[self.anchors_mask[i]]]
    23. #-----------------------------------------------#
    24. # 输入的input一共有三个,他们的shape分别是
    25. # batch_size, 3, 13, 13, 85
    26. # batch_size, 3, 26, 26, 85
    27. # batch_size, 3, 52, 52, 85
    28. #-----------------------------------------------#
    29. prediction = input.view(batch_size, len(self.anchors_mask[i]),
    30. self.bbox_attrs, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous()
    31. #-----------------------------------------------#
    32. # 先验框的中心位置的调整参数
    33. #-----------------------------------------------#
    34. x = torch.sigmoid(prediction[..., 0])
    35. y = torch.sigmoid(prediction[..., 1])
    36. #-----------------------------------------------#
    37. # 先验框的宽高调整参数
    38. #-----------------------------------------------#
    39. w = prediction[..., 2]
    40. h = prediction[..., 3]
    41. #-----------------------------------------------#
    42. # 获得置信度,是否有物体
    43. #-----------------------------------------------#
    44. conf = torch.sigmoid(prediction[..., 4])
    45. #-----------------------------------------------#
    46. # 种类置信度
    47. #-----------------------------------------------#
    48. pred_cls = torch.sigmoid(prediction[..., 5:])
    49. FloatTensor = torch.cuda.FloatTensor if x.is_cuda else torch.FloatTensor
    50. LongTensor = torch.cuda.LongTensor if x.is_cuda else torch.LongTensor
    51. #----------------------------------------------------------#
    52. # 生成网格,先验框中心,网格左上角
    53. # batch_size,3,13,13
    54. #----------------------------------------------------------#
    55. grid_x = torch.linspace(0, input_width - 1, input_width).repeat(input_height, 1).repeat(
    56. batch_size * len(self.anchors_mask[i]), 1, 1).view(x.shape).type(FloatTensor)
    57. grid_y = torch.linspace(0, input_height - 1, input_height).repeat(input_width, 1).t().repeat(
    58. batch_size * len(self.anchors_mask[i]), 1, 1).view(y.shape).type(FloatTensor)
    59. #----------------------------------------------------------#
    60. # 按照网格格式生成先验框的宽高
    61. # batch_size,3,13,13
    62. #----------------------------------------------------------#
    63. anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0]))
    64. anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1]))
    65. anchor_w = anchor_w.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(w.shape)
    66. anchor_h = anchor_h.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(h.shape)
    67. #----------------------------------------------------------#
    68. # 利用预测结果对先验框进行调整
    69. # 首先调整先验框的中心,从先验框中心向右下角偏移
    70. # 再调整先验框的宽高。
    71. #----------------------------------------------------------#
    72. pred_boxes = FloatTensor(prediction[..., :4].shape)
    73. pred_boxes[..., 0] = x.data + grid_x
    74. pred_boxes[..., 1] = y.data + grid_y
    75. pred_boxes[..., 2] = torch.exp(w.data) * anchor_w
    76. pred_boxes[..., 3] = torch.exp(h.data) * anchor_h
    77. #----------------------------------------------------------#
    78. # 将输出结果归一化成小数的形式
    79. #----------------------------------------------------------#
    80. _scale = torch.Tensor([input_width, input_height, input_width, input_height]).type(FloatTensor)
    81. output = torch.cat((pred_boxes.view(batch_size, -1, 4) / _scale,
    82. conf.view(batch_size, -1, 1), pred_cls.view(batch_size, -1, self.num_classes)), -1)
    83. outputs.append(output.data)
    84. return outputs

  • 相关阅读:
    zookeeper学习、配置文件参数详解
    【EI会议征稿】第三届大数据、人工智能与风险管理国际学术会议 (ICBAR 2023)
    asp.net core 资源过滤器
    W31-02-excel和logging使用,实现自动登录百度,并搜索雷军
    【期末大作业】基于HTML+CSS+JavaScript网上订餐系统(23个页面)
    zabbix添加监控主机和自定义监控项
    推荐几个技术学习的网站
    Photoshop 2023:重塑创意,引领数字艺术新纪元
    基变换与坐标变换
    景联文科技:专业提供高质量大语言模型训练数据
  • 原文地址:https://blog.csdn.net/z240626191s/article/details/126644632