YOLOv3的源代码精度理解(三) Decodebox类

365 阅读7分钟

代码主要是参考bubbliiing的github YOLOv3的代码:github.com/bubbliiiing…

对于源代码的解读

box的解码部分

utils/utils_bbox.py文件

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
        self.bbox_attrs     = 5 + num_classes
        self.input_shape    = input_shape
        #-----------------------------------------------------------#
        #   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

    def decode_box(self, inputs):
        outputs = []
        for i, input in enumerate(inputs):
            #-----------------------------------------------#
            #   输入的input一共有三个,他们的shape分别是
            #   batch_size, 255, 13, 13
            #   batch_size, 255, 26, 26
            #   batch_size, 255, 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
            stride_w = self.input_shape[1] / input_width
            #-------------------------------------------------#
            #   此时获得的scaled_anchors大小是相对于特征层的
            #-------------------------------------------------#
            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, 85
            #   batch_size, 3, 26, 26, 85
            #   batch_size, 3, 52, 52, 85
            #-----------------------------------------------#
            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 = torch.sigmoid(prediction[..., 0])
            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
            #----------------------------------------------------------#
            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
            #----------------------------------------------------------#
            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

    def yolo_correct_boxes(self, box_xy, box_wh, input_shape, image_shape, letterbox_image):
        #-----------------------------------------------------------------#
        #   把y轴放前面是因为方便预测框和图像的宽高进行相乘
        #-----------------------------------------------------------------#
        box_yx = box_xy[..., ::-1]
        box_hw = box_wh[..., ::-1]
        input_shape = np.array(input_shape)
        image_shape = np.array(image_shape)

        if letterbox_image:
            #-----------------------------------------------------------------#
            #   这里求出来的offset是图像有效区域相对于图像左上角的偏移情况
            #   new_shape指的是宽高缩放情况
            #-----------------------------------------------------------------#
            new_shape = np.round(image_shape * np.min(input_shape/image_shape))
            offset  = (input_shape - new_shape)/2./input_shape
            scale   = input_shape/new_shape

            box_yx  = (box_yx - offset) * scale
            box_hw *= scale

        box_mins    = box_yx - (box_hw / 2.)
        box_maxes   = box_yx + (box_hw / 2.)
        boxes  = np.concatenate([box_mins[..., 0:1], box_mins[..., 1:2], box_maxes[..., 0:1], box_maxes[..., 1:2]], axis=-1)
        boxes *= np.concatenate([image_shape, image_shape], axis=-1)
        return boxes

    def non_max_suppression(self, prediction, num_classes, input_shape, image_shape, letterbox_image, conf_thres=0.5, nms_thres=0.4):
        #----------------------------------------------------------#
        #   将预测结果的格式转换成左上角右下角的格式。
        #   prediction  [batch_size, num_anchors, 85]
        #----------------------------------------------------------#
        box_corner          = prediction.new(prediction.shape)
        box_corner[:, :, 0] = prediction[:, :, 0] - prediction[:, :, 2] / 2
        box_corner[:, :, 1] = prediction[:, :, 1] - prediction[:, :, 3] / 2
        box_corner[:, :, 2] = prediction[:, :, 0] + prediction[:, :, 2] / 2
        box_corner[:, :, 3] = prediction[:, :, 1] + prediction[:, :, 3] / 2
        prediction[:, :, :4] = box_corner[:, :, :4]

        output = [None for _ in range(len(prediction))]
        for i, image_pred in enumerate(prediction):
            #----------------------------------------------------------#
            #   对种类预测部分取max。
            #   class_conf  [num_anchors, 1]    种类置信度
            #   class_pred  [num_anchors, 1]    种类
            #----------------------------------------------------------#
            class_conf, class_pred = torch.max(image_pred[:, 5:5 + num_classes], 1, keepdim=True)

            #----------------------------------------------------------#
            #   利用置信度进行第一轮筛选
            #----------------------------------------------------------#
            conf_mask = (image_pred[:, 4] * class_conf[:, 0] >= conf_thres).squeeze()

            #----------------------------------------------------------#
            #   根据置信度进行预测结果的筛选
            #----------------------------------------------------------#
            image_pred = image_pred[conf_mask]
            class_conf = class_conf[conf_mask]
            class_pred = class_pred[conf_mask]
            if not image_pred.size(0):
                continue
            #-------------------------------------------------------------------------#
            #   detections  [num_anchors, 7]
            #   7的内容为:x1, y1, x2, y2, obj_conf, class_conf, class_pred
            #-------------------------------------------------------------------------#
            detections = torch.cat((image_pred[:, :5], class_conf.float(), class_pred.float()), 1)

            #------------------------------------------#
            #   获得预测结果中包含的所有种类
            #------------------------------------------#
            unique_labels = detections[:, -1].cpu().unique()

            if prediction.is_cuda:
                unique_labels = unique_labels.cuda()
                detections = detections.cuda()

            for c in unique_labels:
                #------------------------------------------#
                #   获得某一类得分筛选后全部的预测结果
                #------------------------------------------#
                detections_class = detections[detections[:, -1] == c]

                #------------------------------------------#
                #   使用官方自带的非极大抑制会速度更快一些!
                #------------------------------------------#
                keep = nms(
                    detections_class[:, :4],
                    detections_class[:, 4] * detections_class[:, 5],
                    nms_thres
                )
                max_detections = detections_class[keep]
                
                # # 按照存在物体的置信度排序
                # _, conf_sort_index = torch.sort(detections_class[:, 4]*detections_class[:, 5], descending=True)
                # detections_class = detections_class[conf_sort_index]
                # # 进行非极大抑制
                # max_detections = []
                # while detections_class.size(0):
                #     # 取出这一类置信度最高的,一步一步往下判断,判断重合程度是否大于nms_thres,如果是则去除掉
                #     max_detections.append(detections_class[0].unsqueeze(0))
                #     if len(detections_class) == 1:
                #         break
                #     ious = bbox_iou(max_detections[-1], detections_class[1:])
                #     detections_class = detections_class[1:][ious < nms_thres]
                # # 堆叠
                # max_detections = torch.cat(max_detections).data
                
                # Add max detections to outputs
                output[i] = max_detections if output[i] is None else torch.cat((output[i], max_detections))
            
            if output[i] is not None:
                output[i]           = output[i].cpu().numpy()
                box_xy, box_wh      = (output[i][:, 0:2] + output[i][:, 2:4])/2, output[i][:, 2:4] - output[i][:, 0:2]
                output[i][:, :4]    = self.yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image)
        return output
  • 初始化部分
def __init__(self, anchors, num_classes, input_shape, anchors_mask = [[6,7,8], [3,4,5], [0,1,2]]):
    super(DecodeBox, self).__init__()
    
    # 真实框的宽高的array array([[10,13],[16,30]...])
    self.anchors        = anchors
    
    # 80
    self.num_classes    = num_classes
    
    # self.bbox_attrs 框的属性(4(x,y,w,h) + 1(置信度) + x(类))
    self.bbox_attrs     = 5 + num_classes
    
    # 输入的图片的形状(416,416)
    self.input_shape    = input_shape
    #-----------------------------------------------------------#
    #   13x13的特征层对应的anchor是[116,90],[156,198],[373,326]
    #   26x26的特征层对应的anchor是[30,61],[62,45],[59,119]
    #   52x52的特征层对应的anchor是[10,13],[16,30],[33,23]
    #-----------------------------------------------------------#
    
    # anchors的下序号列表
    self.anchors_mask   = anchors_mask
  • 重点解读:decode_box
def decode_box(self, inputs):
    # 我们输入inputs是一个列表,其中放着三个不同size的anchors数据[[1,255,13,13],[1,255,26,26],[1,255,52,52]]
    outputs = []
    for i, input in enumerate(inputs):
        #-----------------------------------------------#
        #   输入的input一共有三个,他们的shape分别是
        #   batch_size, 255, 13, 13
        #   batch_size, 255, 26, 26
        #   batch_size, 255, 52, 52
        #-----------------------------------------------#
        
        # 我们的batch_size在第1个位置,height 在第3个位置 width 在第4个位置
        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
        stride_w = self.input_shape[1] / input_width
        #-------------------------------------------------#
        #   此时获得的scaled_anchors大小是相对于特征层的
        #-------------------------------------------------#
        
        # 我们也要将我们的anchor的框进行等比例的缩小 13 * 13的尺寸的我们的[[116,90],  [156,198],  [373,326]] 变成 [(3.625, 2.8125), (4.875, 6.1875), (11.65625, 10.1875)],其实这个是我们的先验框在特征层上对应的宽高
        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, 85
        #   batch_size, 3, 26, 26, 85
        #   batch_size, 3, 52, 52, 85
        #-----------------------------------------------#
        
        # view的作用就是resize调整通道数,我们将[batch_size,255,13,13]调整为[batch_size,3,13,13,85]
        prediction = input.view(batch_size, len(self.anchors_mask[i]),self.bbox_attrs, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous()

        #-----------------------------------------------#
        #   先验框的中心位置的调整参数
        #-----------------------------------------------#
        
        # 首先先将这个输入的值进行0~1处理,主要是为了防止偏移量大于一或者为负数,跳出当前的格子,出现预测框乱飞,出现训练过程难收敛的情况(重点)
        # 这个是我们的预测框的中心和相对先验框中心的偏移距离
        x = torch.sigmoid(prediction[..., 0])  
        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是[0,1,2,3,4,5,6,7,8,9,10,11,12]的循环
        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_x是[[0,0,0,0,0,0,0,0,0,0,0],[1,1,1,1,1,1,1,1,1,1,1,1,1]....]的循环
        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
        #----------------------------------------------------------#
        
        anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0]))
        anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1]))
        """
        tensor([[[ 3.6250,  3.6250,  ....],
         [ 4.8750,  4.8750,  ....],
         [11.6562, 11.6562, ....]]])
        """
        # 高同理
        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)

        #----------------------------------------------------------#
        #   利用预测结果对先验框进行调整
        #   首先调整先验框的中心,从先验框中心向右下角偏移
        #   再调整先验框的宽高。
        #----------------------------------------------------------#
        
        # 根据论文中的公式对我们得到的坐标进行调整,其实我们得到的预测值x,y是相对框中心点的偏移量,w,h是针对预测框的变化因子,并不是真实的中心点的坐标和宽高。
        # 调整后我们就能得到预测的中心点坐标和预测框的宽高了
        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

        #----------------------------------------------------------#
        #   将输出结果归一化成小数的形式
        #----------------------------------------------------------#
        
        # 因为我们上边调整完,我们的中心点的坐标,变成了格子的左上角坐标 + 偏移量 例如:x = 11.22 我们要对其进行归一化
        # _scale = tensor([13,13,13,13])
        _scale = torch.Tensor([input_width, input_height, input_width,input_height]).type(FloatTensor)
        # 归一化并且将数据进行cancat
        
        # conf.view(batch_size, -1, 1) [batch_size,507,1]
        # pred_boxes.view(batch_size, -1, 4) / _scale  [batch_size,507,4] 归一化后的结果
        # pred_cls.view(batch_size, -1, self.num_classes) [batch_size,507,80]
        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)
    # 经过三轮循环:不同特征图我们将数据列表返回回去[[batch_size,507,85],[batch_size,2028,85],[batch_size,8112,85]]
    return outputs

image.png

  • 重点解读:yolo_correct_boxes
# 这个函数主要用来对框的坐标进行修正
def yolo_correct_boxes(self, box_xy, box_wh, input_shape, image_shape, letterbox_image):
    #-----------------------------------------------------------------#
    #   把y轴放前面是因为方便预测框和图像的宽高进行相乘
    #-----------------------------------------------------------------#
    
    # 反转数据
    box_yx = box_xy[..., ::-1]
    box_hw = box_wh[..., ::-1]
    
    # 指定图片的大小[416,416]
    input_shape = np.array(input_shape) 
    # 图片的原始尺寸[1300,1300]
    image_shape = np.array(image_shape)

    # 进行了无失真的resize的话,我们需要解决中心点偏移的问题
    # 假设我们一张图片中心点位置是(x1,y1),在他的左边进行填充,我们最终的中心点会右移
    if letterbox_image:
        #-----------------------------------------------------------------#
        #   这里求出来的offset是图像有效区域相对于图像左上角的偏移情况
        #   new_shape指的是宽高缩放情况
        #-----------------------------------------------------------------#
        
        # 后面在分析
        new_shape = np.round(image_shape * np.min(input_shape/image_shape))
        offset  = (input_shape - new_shape)/2./input_shape
        scale   = input_shape/new_shape

        box_yx  = (box_yx - offset) * scale
        box_hw *= scale
	
    # 我们可以得到最小值y1,x1的坐标,方式中心点 - 宽高的一半
    box_mins    = box_yx - (box_hw / 2.)
	# 我们可以得到最大值y2,x2的坐标,方式中心点 + 宽高的一半
    box_maxes   = box_yx + (box_hw / 2.)
    
    # 我们将[y1,x1,y2,x2]进行cancat操作
    boxes  = np.concatenate([box_mins[..., 0:1], box_mins[..., 1:2], box_maxes[..., 0:1], box_maxes[..., 1:2]], axis=-1)
    # 我们[h,w],[h,w]进行cancat 得到[h,w,h,w]
    # 直接和我们的归一化的坐标点相乘,就能得到真实的预测框的位置信息
    boxes *= np.concatenate([image_shape, image_shape], axis=-1)
    
    # 注意得到的box的
    return boxes
  • 重点解读:non_max_suppression
from torchvision.ops import nms
def non_max_suppression(self, prediction, num_classes, input_shape, image_shape, letterbox_image, conf_thres=0.5, nms_thres=0.4):
    #----------------------------------------------------------#
    #   将预测结果的格式转换成左上角右下角的格式。
    #   prediction  [batch_size, num_anchors, 85]
    #----------------------------------------------------------#
    
    # 我们新生成一个box_corner对象,用来存储x1,y1,x2,y2等数据(这个地方求出来的坐标也是归一化0~1之间)
    box_corner          = prediction.new(prediction.shape)
    # 在box_corner对象中我们使用中心点x坐标 - 宽的一半 就是左上角x1的值
    box_corner[:, :, 0] = prediction[:, :, 0] - prediction[:, :, 2] / 2
    # 在box_corner对象中我们使用中心点y坐标 - 高的一半 就是左上角y1的值
    box_corner[:, :, 1] = prediction[:, :, 1] - prediction[:, :, 3] / 2
    # 在box_corner对象中我们使用中心点y坐标 + 宽的一半 就是右下角x2的值
    box_corner[:, :, 2] = prediction[:, :, 0] + prediction[:, :, 2] / 2
    # 在box_corner对象中我们使用中心点y坐标 + 高的一半 就是右下角y2的值
    box_corner[:, :, 3] = prediction[:, :, 1] + prediction[:, :, 3] / 2
    # 置信度直接进行对位赋值即可
    prediction[:, :, :4] = box_corner[:, :, :4]

    # 我们先准备好一个容器取装最后的输出结果,实际上就是[None]
    output = [None for _ in range(len(prediction))]
    
    for i, image_pred in enumerate(prediction): # 实际上就是一次循环,image_pred的shape是[10647,85]
        #----------------------------------------------------------#
        #   对种类预测部分取max。
        #   class_conf  [num_anchors, 1]    种类置信度
        #   class_pred  [num_anchors, 1]    种类
        #----------------------------------------------------------#
        
        # 我们是使用image_pred[:, 5:5 + num_classes]得到的是每个框中类的tensor
        # 我们取出类的最大值,和获取到最大的预测值的位置 [0.2552] - [9]
        # 得到的class_conf的shape是[10647,1]
        # 得到的class_pred的shape是[10647,1]
        class_conf, class_pred = torch.max(image_pred[:, 5:5 + num_classes], 1, keepdim=True)

        #----------------------------------------------------------#
        #   利用置信度进行第一轮筛选
        #----------------------------------------------------------#
        
        # 我们将框中存在物体的置信度和类别概率进行相乘 我们可以得到框中存在某类物体的置信度,大于我们设置的置信度阈值的位置保留下来,得到一个
        # [False,False,True,False...]的10647长度的列表
        conf_mask = (image_pred[:, 4] * class_conf[:, 0] >= conf_thres).squeeze()

        #----------------------------------------------------------#
        #   根据置信度进行预测结果的筛选
        #----------------------------------------------------------#
        
        # 这个动作就相当于是我们从image的第一个维度中,抽取在conf_mask中为真的哪些序号,组成一个新的向量、经过抽取[10647,85]-->[33,85]
        image_pred = image_pred[conf_mask]
        # 同上,我们分析下 得到的数据:tensor([[0.9574],[0.9533],[0.9695],[0.9804],[0.9998],[0.9993],[0.9894],[0.9940],[0.9972],[0.9990],[0.9908],[0.9889],[0.9955],[0.9897],[0.9556],[0.9999],[0.9999],[0.9999],[0.9982],[0.9963],[0.9995],[1.0000],[1.0000],[1.0000],[0.9996],[0.9997],[0.9996],[0.9974],[0.9998],[0.9999],[0.9999],[0.9815],[0.9802]])
        # 我们发现是33 * 85 最终我们得到的是11个框和物体,所以我们是预测到了11个点,并且每个点都有3个框,
        # 我们nms最终去掉的就是同一个点预测的三个框
        class_conf = class_conf[conf_mask]
        class_pred = class_pred[conf_mask]
        
        # 讲述我们经过第一轮的分类置信度过滤之后我们没有任何的数据存在,那我们直接跳出循环即可,最终返回[None]
        if not image_pred.size(0):
            continue
        #-------------------------------------------------------------------------#
        #   detections  [num_anchors, 7]
        #   7的内容为:x1, y1, x2, y2, obj_conf, class_conf, class_pred
        #-------------------------------------------------------------------------#
        detections = torch.cat((image_pred[:, :5], class_conf.float(), class_pred.float()), 1)

        #------------------------------------------#
        #   获得预测结果中包含的所有种类
        #------------------------------------------#
        
        # 我们将结果中所有种类进行总结
        unique_labels = detections[:, -1].cpu().unique()

        # 假设我们开启了GPU,我们将上面构造好的detections和unique_labels加载到cuda上
        if prediction.is_cuda:
            unique_labels = unique_labels.cuda()
            detections = detections.cuda()
         
        # 我们循环遍历不同的类
        for c in unique_labels:
            #------------------------------------------#
            #   获得某一类得分筛选后全部的预测结果
            #------------------------------------------#
            
            # 将预测同一个类的detections_class拿出来,和上面的mask的思路一样
            detections_class = detections[detections[:, -1] == c]

            #------------------------------------------#
            #   使用官方自带的非极大抑制会速度更快一些!
            #------------------------------------------#
            
            # 使用官方自带的nms
            # 第一个参数是boxes (Tensor[N, 4])) – bounding boxes坐标. 格式:(x1, y1, x2, y2)
            # 第二个参数是scores (Tensor[N]) – bounding boxes得分 就是分类的置信度
            # 第三个参数是iou_threshold (float) – IoU过滤阈值 
            # keep :NMS过滤后的bouding boxes索引(降序排列)
            keep = nms(
                detections_class[:, :4],
                detections_class[:, 4] * detections_class[:, 5],
                nms_thres
            )
            # 将最终的数据保存下来,和上面的mask的思想一致
            max_detections = detections_class[keep]

            # 手写NMS算法(可以但是没必要)
            # # 按照存在物体的置信度排序
            # _, conf_sort_index = torch.sort(detections_class[:, 4]*detections_class[:, 5], descending=True)
            # detections_class = detections_class[conf_sort_index]
            # # 进行非极大抑制
            # max_detections = []
            # while detections_class.size(0):
            #     # 取出这一类置信度最高的,一步一步往下判断,判断重合程度是否大于nms_thres,如果是则去除掉
            #     max_detections.append(detections_class[0].unsqueeze(0))
            #     if len(detections_class) == 1:
            #         break
            #     ious = bbox_iou(max_detections[-1], detections_class[1:])
            #     detections_class = detections_class[1:][ious < nms_thres]
            # # 堆叠
            # max_detections = torch.cat(max_detections).data

            # Add max detections to outputs
            # 因为我们最开始设置的[None],所以我们需要判断下output的内容 是None的话,进行替换操作
            # 不是的话,我们就将新生成的数据和之前的数据继续cancat操作
            output[i] = max_detections if output[i] is None else torch.cat((output[i], max_detections))

        # 如果我们的output不是None的时候,我们
        if output[i] is not None:
            output[i] = output[i].cpu().numpy()
            # 这个地方我们还是要还原回来中心点坐标(x1 + x2)/2中心点横坐标、(y1 + y2)/2中心点的纵坐标
            # x2 - x1是w,y2 - y1是h
            box_xy, box_wh = (output[i][:, 0:2] + output[i][:, 2:4])/2, output[i][:, 2:4] - output[i][:, 0:2]
            # 然后我们调用yolo_correct_boxes来修正位置信息
            output[i][:, :4] = self.yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image)
	# 返回预测的结果
    return output
总结

解码过程主要是以下服务:因为我们预测框的位置中心点的相对坐标可能大于1或者小于0,这样就会导致我们的框的中心点偏移出原本应该预测的格子,宽高也可能超出我们的限定,导致训练过程标识框出现乱飞的情况,所以我们加上sigmoid函数进行限定,限定中心点不能离开格子,且宽高的变化合理范围内,并且对修正后的坐标进行归一化,还是相对坐标,但是将维度进行修改[1,3 * 13 * 13,85] = [1,507,85]

进行非极大值抑制:首先是将中心点坐标还原成[x1,y1,x2,y2]的形式,然后对所有的框进行扫描,首先剔除掉class_conf置信度小于指定值的框框[10647,85],下降到[33,85],然后我们在按照那个类对这33框,进行分组,每一组中的所有框都进行nms抑制,最后将我们的存留下来的框数据保存下来,对我们的预测框数据调用yolo_correct_boxes得到真实的预测框,因为nms要求的输入是[x1,y1,x2,y2]的形式,所以我们将输入的中心点宽高的坐标转化成[x1,y1,x2,y2]的形式,但是我们要转化成真实框的位置和宽高时候我们还是要转化成中心坐标宽高的形式,在输入yolo_correct_boxes之前,记得完成转化

在这个yolo_correct_boxes里面:如我们之前没有进行过不失真resize的话,直接通过归一化的中心坐标和宽高能得到归一化的xmin,ymin,xmax,ymax的归一化坐标,我们将其乘以原始图片的宽和高,就可以得到框的左上角和右下角的真实坐标,并将真实坐标返回回去;如果我们之前进行了不失真的resize的话,那么我们需要先还原原始的resize之前的中心点坐标,然后在通过归一化的中心坐标和宽高能得到归一化的xmin,ymin,xmax,ymax的归一化坐标,我们将其乘以原始图片的宽和高,就可以得到框的左上角和右下角的真实坐标,并将真实坐标返回回去.