赞
踩
在利用YOLOv3网络结构提取到out0、out1、out2之后,不同尺度下每个网格点上均有先验框,网络训练过程会对先验框的参数进行调整,继而得到预测框,从不同尺度下预测框还原到原图输入图像上,同时包括该框内目标预测的结果情况(预测框位置、类别概率、置信度分数),这个过程称之为解码。
注释主要以VOC数据集,YOLOv3 net最后一层输出进行解读。
import torch import numpy as np class DecodeBox(): def __init__(self, anchors, num_classes, input_shape, anchors_mask = [[6,7,8], [3,4,5], [0,1,2]]): super(DecodeBox, self).__init__() self.anchors = anchors self.num_classes = num_classes # int 20 self.bbox_attrs = 5 + num_classes # int 25 self.input_shape = input_shape # (416, 416) 元组 #-----------------------------------------------------------# # 13x13的特征层对应的anchor是[116,90],[156,198],[373,326] # 26x26的特征层对应的anchor是[30,61],[62,45],[59,119] # 52x52的特征层对应的anchor是[10,13],[16,30],[33,23] #-----------------------------------------------------------# self.anchors_mask = anchors_mask # ----------------------------------------------# # 得到out0、out1、out2不同尺度下每个网格点上的的预测情况(预测框位置、类别概率、置信度分数) # ----------------------------------------------# def decode_box(self, inputs): # input一共有三组数据,out0,out1,out2 outputs = [] for i, input in enumerate(inputs): # 一次只能对一个特征层的输出进行解码操作 # -----------------------------------------------# # 输入的input一共有三个,他们的shape分别是 针对voc数据集 # batch_size, 75, 13, 13 batch_size, channels, weight, height # batch_size, 75, 26, 26 # batch_size, 75, 52, 52 # -----------------------------------------------# batch_size = input.size(0) input_height = input.size(2) input_width = input.size(3) # -----------------------------------------------# # 输入为416x416时 # stride_h = stride_w = 32、16、8 # 一个特征点对应原来图上多少个像素点 # -----------------------------------------------# stride_h = self.input_shape[0] / input_height # 输出特征图和resize之后的原图上对应步长,映射回去的操作 stride_w = self.input_shape[1] / input_width #-------------------------------------------------# # 把先验框的尺寸调整成特征层的大小形式,用来对应两者宽和高 # 此时获得的scaled_anchors大小是相对于特征层的,anchors是大数据kmeans聚类经验所得 # out0越小,stride越大,用来检测大目标 #-------------------------------------------------# scaled_anchors = [(anchor_width / stride_w, anchor_height / stride_h) for anchor_width, anchor_height in self.anchors[self.anchors_mask[i]]] #-----------------------------------------------# # 输入的input一共有三个,他们的shape分别是 # batch_size, 3, 13, 13, 25 # batch_size, 3, 26, 26, 25 # batch_size, 3, 52, 52, 25 # batch_size,3*(5+num_classes),13,13 -> batch_size,3,5+num_classes,13,13 -> batch_size, 3, 13, 13, 25 # 此处参考链接:https://blog.csdn.net/weixin_45377629/article/details/124028098 #-----------------------------------------------# prediction = input.view(batch_size, len(self.anchors_mask[i]), self.bbox_attrs, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous() #-----------------------------------------------# # 先验框的中心位置的调整参数 # x shape: torch.size([batch_size,3,13,13]) # y shape: torch.size([batch_size,3,13,13]) #-----------------------------------------------# x = torch.sigmoid(prediction[..., 0]) # sigmoid可以把输出值固定到0~1之间 y = torch.sigmoid(prediction[..., 1]) # 先验框中心点的调整只能在其右下角的网格里面 #-----------------------------------------------# # 先验框的宽高调整参数 #-----------------------------------------------# w = prediction[..., 2] h = prediction[..., 3] #-----------------------------------------------# # 获得置信度,是否有物体,有物体的概率是多少 #-----------------------------------------------# conf = torch.sigmoid(prediction[..., 4]) #-----------------------------------------------# # 种类置信度,属于某类别的概率是多少 #-----------------------------------------------# pred_cls = torch.sigmoid(prediction[..., 5:]) FloatTensor = torch.cuda.FloatTensor if x.is_cuda else torch.FloatTensor LongTensor = torch.cuda.LongTensor if x.is_cuda else torch.LongTensor #----------------------------------------------------------# # 生成网格,先验框中心=网格左上角 # grid_x shape:torch.size([batch_size,3,13,13]) # grid_y shape:torch.size([batch_size,3,13,13]) # 关于该行代码解读,详细参考本文第3节 #----------------------------------------------------------# 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) 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) #----------------------------------------------------------# # 按照网格格式生成先验框的宽高 # batch_size,3,13,13 # 关于该行代码解读,详细参考本文第4节 #----------------------------------------------------------# anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0])) anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1])) anchor_w = anchor_w.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(w.shape) anchor_h = anchor_h.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(h.shape) #----------------------------------------------------------# # 利用预测结果对先验框进行调整 # 首先调整先验框的中心,从先验框中心向右下角偏移 # 再调整先验框的宽高。 #----------------------------------------------------------# pred_boxes = FloatTensor(prediction[..., :4].shape) pred_boxes[..., 0] = x.data + grid_x pred_boxes[..., 1] = y.data + grid_y pred_boxes[..., 2] = torch.exp(w.data) * anchor_w pred_boxes[..., 3] = torch.exp(h.data) * anchor_h #----------------------------------------------------------# # 将输出结果归一化成小数的形式 #----------------------------------------------------------# _scale = torch.Tensor([input_width, input_height, input_width, input_height]).type(FloatTensor) output = torch.cat((pred_boxes.view(batch_size, -1, 4) / _scale, conf.view(batch_size, -1, 1), pred_cls.view(batch_size, -1, self.num_classes)), -1) outputs.append(output.data) return outputs # 得到out0、out1、out2不同尺度下每个网格点上的的预测情况(预测框位置、类别概率、置信度分数) if __name__ == '__main__': anchors = [10.0, 13.0, 16.0, 30.0, 33.0, 23.0, 30.0, 61.0, 62.0, 45.0, 59.0, 119.0, 116.0, 90.0, 156.0, 198.0, 373.0, 326.0] # anchors: ndarray:(9, 2) anchors = np.array(anchors).reshape(-1,2) num_classes = 20 # voc类别个数 anchors_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]] input_shape = [416,416] bbox_util = DecodeBox(anchors, num_classes, (input_shape[0], input_shape[1]), anchors_mask) # ---------------------------------------------------------# # 将图像输入网络当中进行预测! # ---------------------------------------------------------# net = YoloBody(anchors_mask, num_classes) # 此地YoloBody可见https://www.jianshu.com/p/27f3b967646c outputs = net(images) # 此地images表示输入图片,outputs为三个输出out0, out1, out2 outputs = bbox_util.decode_box(outputs) # 得到out0、out1、out2不同尺度下每个网格点上的预测情况(预测框位置、类别概率、置信度分数)
先验框中心=网格左上角,下面这行代码到底如何理解呢?
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)
以宽为5,高为5, batch_size为1为例,详细解读见下方代码及输出。
import torch if __name__ == "__main__": input_width = 5 input_height = 5 batch_size = 1 anchors_mask = [[6,7,8], [3,4,5], [0,1,2]] a = torch.linspace(0, input_width - 1, input_width) # torch.linspace左闭右闭 print(a) # 输出一个张量列表 """ tensor([0., 1., 2., 3., 4.]) """ b = a.repeat(input_height, 1) print(b) """ tensor([[0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.]]) """ c = b.repeat(batch_size * 3, 1, 1) # len(anchors_mask[i]) = 3 print(c) """ tensor([[[0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.]], [[0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.]], [[0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.]]]) """ d = c.view(batch_size, 3, input_height, input_width) # 对已知的进行reshape print(d) """ tensor([[[[0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.]], [[0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.]], [[0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.], [0., 1., 2., 3., 4.]]]]) """ e = d.type(FloatTensor) # 数据类型
按照网格格式生成先验框的宽高,其代码如下:
#----------------------------------------------------------#
# 按照网格格式生成先验框的宽高
# batch_size,3,13,13
#----------------------------------------------------------#
anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0]))
anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1]))
anchor_w = anchor_w.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(w.shape)
anchor_h = anchor_h.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(h.shape)
对于上面这四行代码,我们以最小特征层为例,详细理解:
import torch if __name__ == "__main__": #-----------------------------------------------------------------------------# # 把先验框的尺寸调整成特征层的大小形式,用来对应两者宽和高 # 此时获得的scaled_anchors大小是相对于特征层的,anchors是大数据kmeans聚类经验所得 # out0越小,stride越大,用来检测大目标 # 此以最小特征层为例,batch_size, 75, 13, 13 #-----------------------------------------------------------------------------# scaled_anchors = [(3.625,2.8125), (4.875,6.1875), (11.65625, 10.1875)] x_is_cuda = False # x.is_cuda = False,表示没用cuda FloatTensor = torch.cuda.FloatTensor if x_is_cuda else torch.FloatTensor LongTensor = torch.cuda.LongTensor if x_is_cuda else torch.LongTensor # ------------------------------# # 解读第 1 行anchor_w # ------------------------------# a = LongTensor([0]) print(a) # tensor([0]) b = FloatTensor(scaled_anchors) print(b) # 保留的小数点位数变了 """ tensor([[ 3.6250, 2.8125], [ 4.8750, 6.1875], [11.6562, 10.1875]]) """ # ----------------------------------------------------------# # tensor.index_select(dim, index) # dim :表示要查找的维度,对于二维,0代表行,1代表列 # index:表示要索引的序列,是一个tensor对象 # a = tensor([0]),表示要索引的为宽 # a = tensor([1]),表示要索引的为高 # ----------------------------------------------------------# anchor_w = b.index_select(1, a) print(anchor_w) # anchor_w shape: torch.size([3,1]) """ tensor([[ 3.6250], [ 4.8750], [11.6562]]) """ # ------------------------------# # 解读第 2 行anchor_h # 类似上面 # ------------------------------# anchor_h = b.index_select(1, LongTensor([1])) """ tensor([[ 2.8125], [ 6.1875], [10.1875]]) """ # ----------------------------------------------------# # 解读第 3 行anchor_w # w.shape 和 h.shape: torch.size([1,3,13,13]) # ----------------------------------------------------# batch_size = 1 # 以batch_size=1为例 input_height = 13 # 最小特征层输出,宽高均为13 input_width = 13 # ------------------------------------# # tensor.repeat(dim1,dim2,...) # 复制多个tensor # ------------------------------------# c = anchor_w.repeat(batch_size, 1) print(c) """ tensor([[ 3.6250], [ 4.8750], [11.6562]]) 若batch_size = 2, c 的结果: tensor([[ 3.6250], [ 4.8750], [11.6562], [ 3.6250], [ 4.8750], [11.6562]]) 毕竟有几张图片,先验框的宽,参数个数就应该有几倍,每张图片都有 """ d = c.repeat(1, 1, input_height * input_width) print(d.shape) # torch.Size([1, 3, 169]) # ---------------------------------------------------# # 每个像素点,都有三个先验框,每个先验框,都有宽 # 有点各用各的,的感觉 # ---------------------------------------------------# anchor_w = d.view(1,3,13,13) print(anchor_w.shape) # torch.Size([1, 3, 13, 13]),先验框的宽就都生成了,高类似
https://www.bilibili.com/video/BV1Hp4y1y788?p=6&spm_id_from=pageDriver
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。