一、预测框解码

三个Head输出特征图进行解码
inputs=[[batch_size, 255, 13, 13],
[batch_size, 255, 26, 26,
[batch_size, 255, 52, 52]]]

  1. def decode_box(self, inputs):
  2. outputs = []
  3. #-----------------------------------------------#
  4. # 输入的input一共有三个,他们的shape分别是
  5. # batch_size, 255, 13, 13
  6. # batch_size, 255, 26, 26
  7. # batch_size, 255, 52, 52
  8. #-----------------------------------------------#
  9. for i, input in enumerate(inputs):
  10. #这里每次取出一个特征图进行处理,第一个为[batch_size, 255, 13, 13]
  11. batch_size = input.size(0)
  12. input_height = input.size(2) #13
  13. input_width = input.size(3) #13
  14. #-----------------------------------------------#
  15. # 输入为416x416时
  16. # stride_h = stride_w = 32、16、8
  17. #-----------------------------------------------#
  18. stride_h = self.input_shape[0] / input_height #416/13=32
  19. stride_w = self.input_shape[1] / input_width #416/13=32
  20. #-------------------------------------------------#
  21. # 此时获得的scaled_anchors大小是相对于特征层的
  22. #-------------------------------------------------#
  23. #anchors_mask = [[6,7,8], [3,4,5], [0,1,2]]
  24. #anchors=10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
  25. #for anchor_width, anchor_height in self.anchors[self.anchors_mask[i]]]
  26. #的意思为取出第[6,7,8]个anchor,即116,90, 156,198, 373,326
  27. #这三个anchor为当前13x13特征图的在原图上的anchor的宽和高
  28. #除以步长后即为在特征图上的宽和高,输出如下:
  29. #0:(3.625, 2.8125) 116,90/32
  30. #1:(4.875, 6.1875)
  31. #2:(11.65625, 10.1875)
  32. scaled_anchors = [(anchor_width / stride_w, anchor_height / stride_h) for anchor_width, anchor_height in self.anchors[self.anchors_mask[i]]]
  33. #-----------------------------------------------#
  34. # 输出prediction
  35. # batch_size, 3, 13, 13, 85
  36. # batch_size, 3, 26, 26, 85
  37. # batch_size, 3, 52, 52, 85
  38. #-----------------------------------------------#
  39. prediction = input.view(batch_size, len(self.anchors_mask[i]),
  40. self.bbox_attrs, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous()
  41. #-----------------------------------------------#
  42. # 先验框的中心位置坐标的调整参数
  43. #-----------------------------------------------#
  44. x = torch.sigmoid(prediction[..., 0])
  45. y = torch.sigmoid(prediction[..., 1])
  46. #-----------------------------------------------#
  47. # 先验框的宽高调整参数
  48. #-----------------------------------------------#
  49. w = prediction[..., 2]
  50. h = prediction[..., 3]
  51. #-----------------------------------------------#
  52. # 获得置信度,是否有物体
  53. #-----------------------------------------------#
  54. conf = torch.sigmoid(prediction[..., 4])
  55. #-----------------------------------------------#
  56. # 种类置信度
  57. #-----------------------------------------------#
  58. pred_cls = torch.sigmoid(prediction[..., 5:])
  59. FloatTensor = torch.cuda.FloatTensor if x.is_cuda else torch.FloatTensor
  60. LongTensor = torch.cuda.LongTensor if x.is_cuda else torch.LongTensor
  61. #----------------------------------------------------------#
  62. # 生成13x13的网格,先验框的中心在网格左上角即(0,0),x轴向右,y轴向下
  63. # batch_size,3,13,13
  64. #----------------------------------------------------------#
  65. grid_x = torch.linspace(0, input_width - 1, input_width).repeat(input_height, 1).repeat(
  66. batch_size * len(self.anchors_mask[i]), 1, 1).view(x.shape).type(FloatTensor)
  67. grid_y = torch.linspace(0, input_height - 1, input_height).repeat(input_width, 1).t().repeat(
  68. batch_size * len(self.anchors_mask[i]), 1, 1).view(y.shape).type(FloatTensor)
  69. #----------------------------------------------------------#
  70. # 按照网格格式在每个网格上生成先验框的宽高
  71. # batch_size,3,13,13
  72. #----------------------------------------------------------#
  73. anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0]))
  74. anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1]))
  75. anchor_w = anchor_w.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(w.shape)
  76. anchor_h = anchor_h.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(h.shape)
  77. #----------------------------------------------------------#
  78. # 利用预测结果对先验框进行调整
  79. # 首先调整先验框的中心,从先验框中心向右下角偏移
  80. # 再调整先验框的宽高。
  81. #----------------------------------------------------------#
  82. pred_boxes = FloatTensor(prediction[..., :4].shape)#取出每个网格的前四维信息,即预测中心点坐标和预测宽高
  83. pred_boxes[..., 0] = x.data + grid_x #每个网格内移动x
  84. pred_boxes[..., 1] = y.data + grid_y #每个网格内移动y
  85. pred_boxes[..., 2] = torch.exp(w.data) * anchor_w #调整宽
  86. pred_boxes[..., 3] = torch.exp(h.data) * anchor_h
  87. #----------------------------------------------------------#
  88. # 将输出结果归一化成小数的形式
  89. #----------------------------------------------------------#
  90. _scale = torch.Tensor([input_width, input_height, input_width, input_height]).type(FloatTensor)
  91. #将处理后的中心的坐标,宽高和置信度,80个类别的概率拼接,总共85
  92. output = torch.cat((pred_boxes.view(batch_size, -1, 4) / _scale,
  93. conf.view(batch_size, -1, 1), pred_cls.view(batch_size, -1, self.num_classes)), -1)
  94. #output=([1, 507, 85]) 507=13x13x3(每个网格3个框,有13x13个网格)
  95. outputs.append(output.data) #将这个特征图的结果插入,然后在循环处理另两个特征图的
  96. #输出
  97. #([1, 507, 85])
  98. #([1, 2028, 85])
  99. #([1, 8112, 85])
  100. return outputs

二、非极大值抑制

将上面输出的output进行非极大值抑制
以下为函数传入参数
torch.cat(outputs, 1):将output输出的第二维全部拼接,即507+2028+8112
#([1, 507, 85])
#([1, 2028, 85])
#([1, 8112, 85])
self.num_classes:类别数,此处使用coco数据集总共80类
self.input_shape:网络要求输入图像尺寸,416
image_shape:输入图片实际尺寸
self.letterbox_image:false不用考虑,
conf_thres:置信度阈值0.5,
nms_thres:非极大值抑制阈值

  1. results = self.bbox_util.non_max_suppression(torch.cat(outputs, 1), self.num_classes, self.input_shape,
  2. image_shape, self.letterbox_image, conf_thres = self.confidence, nms_thres = self.nms_iou)

以下为非极大值过程代码

  1. def non_max_suppression(self, prediction, num_classes, input_shape, image_shape, letterbox_image, conf_thres=0.5, nms_thres=0.4):
  2. #----------------------------------------------------------#
  3. # 将预测结果的格式转换成左上角右下角的格式。
  4. # prediction [batch_size, num_anchors, 85],此处为([1, 10647, 85])
  5. #----------------------------------------------------------#
  6. box_corner = prediction.new(prediction.shape)
  7. #转换为先验框左上角和右下角坐标
  8. #左上角x坐标为:中心点x坐标-宽的一半
  9. box_corner[:, :, 0] = prediction[:, :, 0] - prediction[:, :, 2] / 2
  10. #左上角y坐标为:中心点y坐标-高的一半
  11. box_corner[:, :, 1] = prediction[:, :, 1] - prediction[:, :, 3] / 2
  12. #右下角x坐标为:中心点x坐标+宽的一半
  13. box_corner[:, :, 2] = prediction[:, :, 0] + prediction[:, :, 2] / 2
  14. #右下角y坐标为:中心点y坐标+高的一半
  15. box_corner[:, :, 3] = prediction[:, :, 1] + prediction[:, :, 3] / 2
  16. prediction[:, :, :4] = box_corner[:, :, :4]
  17. #初始化输出列表
  18. output = [None for _ in range(len(prediction))]
  19. for i, image_pred in enumerate(prediction):#去一个batch(如果只输入一张图片,只循环一次)
  20. #----------------------------------------------------------#
  21. # 对种类预测部分取max。
  22. # class_conf [num_anchors, 1] 种类置信度
  23. # class_pred [num_anchors, 1] 种类
  24. #----------------------------------------------------------#
  25. #取出每个网格中置信度最高的类别
  26. #class_conf=[10647,1] 最高的置信度
  27. #class_pred=[10647,1] 最高置信度代表的类别
  28. class_conf, class_pred = torch.max(image_pred[:, 5:5 + num_classes], 1, keepdim=True)
  29. #----------------------------------------------------------#
  30. # 利用置信度进行第一轮筛选
  31. #----------------------------------------------------------#
  32. conf_mask = (image_pred[:, 4] * class_conf[:, 0] >= conf_thres).squeeze()
  33. #----------------------------------------------------------#
  34. # 根据置信度进行预测结果的筛选,去掉置信度小于置信度阈值的框框
  35. #----------------------------------------------------------#
  36. image_pred = image_pred[conf_mask]
  37. class_conf = class_conf[conf_mask]
  38. class_pred = class_pred[conf_mask]
  39. if not image_pred.size(0):
  40. continue
  41. #-------------------------------------------------------------------------#
  42. # detections [num_anchors, 7] num_anchors为过滤后剩下的框框
  43. # 7的内容为:左上角右下角坐标 目标框框置信度 种类置信度 种类类别
  44. # x1, y1, x2, y2, obj_conf, class_conf, class_pred
  45. #-------------------------------------------------------------------------#
  46. detections = torch.cat((image_pred[:, :5], class_conf.float(), class_pred.float()), 1)
  47. #------------------------------------------#
  48. # 获得预测结果中包含的所有种类,每个种类可能有多个框框
  49. #------------------------------------------#
  50. unique_labels = detections[:, -1].cpu().unique()
  51. if prediction.is_cuda:
  52. unique_labels = unique_labels.cuda()
  53. detections = detections.cuda()
  54. for c in unique_labels:#对每个种类的多个框框进行处理,取出最好的那个框框
  55. #------------------------------------------#
  56. # 获得某一类得分筛选后全部的预测结果
  57. #------------------------------------------#
  58. detections_class = detections[detections[:, -1] == c]
  59. #------------------------------------------#
  60. # 使用官方自带的非极大抑制处理剩下的框
  61. #------------------------------------------#
  62. keep = nms(
  63. detections_class[:, :4],
  64. detections_class[:, 4] * detections_class[:, 5],
  65. nms_thres
  66. )
  67. max_detections = detections_class[keep]
  68. # nms原理如下:
  69. # # 按照存在物体的置信度排序
  70. # _, conf_sort_index = torch.sort(detections_class[:, 4]*detections_class[:, 5], descending=True)
  71. # detections_class = detections_class[conf_sort_index]
  72. # # 进行非极大抑制
  73. # max_detections = []
  74. # while detections_class.size(0):
  75. # # 取出这一类置信度最高的,一步一步往下判断,判断重合程度是否大于nms_thres,如果是则去除掉
  76. # max_detections.append(detections_class[0].unsqueeze(0))
  77. # if len(detections_class) == 1:
  78. # break
  79. # ious = bbox_iou(max_detections[-1], detections_class[1:])
  80. # detections_class = detections_class[1:][ious < nms_thres]
  81. # # 堆叠
  82. # max_detections = torch.cat(max_detections).data
  83. # Add max detections to outputs
  84. output[i] = max_detections if output[i] is None else torch.cat((output[i], max_detections))
  85. if output[i] is not None:
  86. output[i] = output[i].cpu().numpy()
  87. box_xy, box_wh = (output[i][:, 0:2] + output[i][:, 2:4])/2, output[i][:, 2:4] - output[i][:, 0:2]
  88. output[i][:, :4] = self.yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image)
  89. return output #此处为所有框的信息

2.1 IOU代码

以下为非极大值抑制中IOU计算代码

  1. def box_area(boxes)
  2. return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
  3. def box_iou(boxes2, boxes1)
  4. area1 = box_area(boxes1) #其余框的面积
  5. area2 = box_area(boxes2) #置信度最大框的面积
  6. lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # 交集左上角坐标
  7. rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # 交集右下角坐标
  8. wh = (rb - lt).clamp(min=0) #小于0的为0 clamp 钳;夹钳;
  9. #交集
  10. inter = wh[:, :, 0] * wh[:, :, 1]
  11. #iou = 交集/并集
  12. iou = inter / (area1[:, None] + area2 - inter)
  13. return iou
  1. #------------------------------------------#
  2. # 获得某一类得分筛选后全部的预测结果
  3. #------------------------------------------#
  4. #取出每个框框代表的类别
  5. top_label = np.array(results[0][:, 6], dtype = 'int32')
  6. #取出每个框框代表的置信度
  7. top_conf = results[0][:, 4] * results[0][:, 5]
  8. #取出每个框框代表的左上角和右下角坐标
  9. top_boxes = results[0][:, :4]