yolov3代码详解3-预测结果的解码
由第二步可以得到三个特征层,分别为(N,13,13,255)、(N,26,26,255)、(N,52,52,255),每一个有效特征层将整个图片分成与其长宽对应的网格,如(N,13,13,255)的特征层就是将整个图像分成13x13个网格;
然后从每个网格中心建立多个先验框,这些框是网络预先设定好的框,网络的预测结果会判断这些框内是否包含物体,以及这个物体的种类。由于每一个网格点都具有三个先验框,所以上述的预测结果可以
reshape为:
- (N,13,13,3,85)
- (N,26,26,3,85)
- (N,52,52,3,85)
85可以拆分为3 * (20 + 1 + 4),分别代表二十个类,一个置信度和先验框的调整参数(x,y,w,h),每一个节点有三个先验框。
yolov3的解码过程可以分为两个步骤:
1. 给每个网格点加上它对应的x和y,加完后的结果就是预测框的中心
2. 再利用先验框和h、w结合计算出预测框的宽高,就可以得到整个预测框的位置了
得到最终的预测结果后还要进行得分排序与非极大抑制筛选。
这一部分基本上是所有目标检测通用的部分。其对于每一个类进行判别:
1、取出每一类得分大于self.obj_threshold的框和得分。
2、利用框的位置和得分进行非极大抑制。
from __future__ import division import numpy as np import torch import torch.nn as nn from PIL import Image from torchvision.ops import nms
每次只能对一个特征层进行解码,故在调用的时候循环三次,对三个特征层的框进行解码
# 每次只能对一个特征层进行解码 class DecodeBox(nn.Module): def __init__(self, anchors, num_classes, img_size): # 形参分别为先验框的大小、预测的种类、图像的大小 super(DecodeBox, self).__init__() #-----------------------------------------------------------# # 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 = anchors self.num_anchors = len(anchors) self.num_classes = num_classes self.bbox_attrs = 5 + num_classes self.img_size = img_size def forward(self, input): #-----------------------------------------------# # 输入的input一共有三个,他们的shape分别是 # batch_size, 255, 13, 13 # batch_size, 255, 26, 26 # batch_size, 255, 52, 52 # 一共有多少张图片 batch_size = input.size(0) # 以 13 * 13 为例 input_height = input.size(2) # 13 input_width = input.size(3) # 13 # 计算步长,每一个特征点对应原来的图片上相应数量的像素点,若特征层为13*13,则每一个特征点对应416/13 = 32个像素点 stride_h = self.img_size[1] / input_height # 416/13 stride_w = self.img_size[0] / input_width # 416/13 # 此时获得的scaled_anchors大小是相对于特征层的 # 把先验框的尺寸调整为特征层的大小,计算出特征层再特征层上对应的宽高 scaled_anchors = [(anchor_width / stride_w, anchor_height / stride_h) for anchor_width, anchor_height in self.anchors] # 输入的input一共有三个,他们的shape分别是 # batch_size, 3, 13, 13, 85 # batch_size, 3, 26, 26, 85 # batch_size, 3, 52, 52, 85 # 对预测结果进行reshape通道变换 # bs,3*(5 + num_classes),13,13 --> bs,3,13,13,(5 + num_classes) prediction = input.view(batch_size, self.num_anchors, self.bbox_attrs, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous() # 先验框的中心位置的调整参数 '实际预测框的中心和先验框中心的距离' x = torch.sigmoid(prediction[..., 0]) # 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 # ======先验框的生成,包含一下两步====== # 生成网格,先验框中心,网格左上角 ,先验框的中心就是网格的左上角 batch_size,3,13,13 # batch_size,3,13,13 grid_x = torch.linspace(0, input_width - 1, input_width).repeat(input_height, 1).repeat( batch_size * self.num_anchors, 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 * self.num_anchors, 1, 1).view(y.shape).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) # ======利用预测结果对先验框进行调整====== # 首先调整先验框的中心,从先验框中心向右下角偏移 # 再调整先验框的宽高。 #----------------------------------------------------------# 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 #----------------------------------------------------------# # 将输出结果调整成相对于输入图像大小 416 * 416 #----------------------------------------------------------# _scale = torch.Tensor([stride_w, stride_h] * 2).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) return output.data