Hello Mat

 找回密码
 立即注册
查看: 1105|回复: 4

yolo pytorch

[复制链接]

1294

主题

1520

帖子

110

金钱

管理员

Rank: 9Rank: 9Rank: 9

积分
22633
发表于 2023-3-4 16:20:07 来自手机 | 显示全部楼层 |阅读模式
yolov3 pytorch
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Sat Jun 17 23:33:13 2023
  4. @author: ysw
  5. """
  6. #-------------------------------------#
  7. #       对数据集进行训练
  8. #-------------------------------------#
  9. import datetime
  10. import os
  11. import math
  12. import cv2
  13. from PIL import Image
  14. import numpy as np
  15. from collections import OrderedDict
  16. import torch
  17. import torch.backends.cudnn as cudnn
  18. import torch.distributed as dist
  19. import torch.nn as nn
  20. import torch.optim as optim
  21. from torch import nn
  22. from torch.utils.data import DataLoader
  23. from torch.utils.data.dataset import Dataset
  24. # from nets.yolo import YoloBody
  25. # from nets.yolo_training import YOLOLoss
  26. # from utils.dataloader import YoloDataset, yolo_dataset_collate
  27. # from utils.utils import get_anchors, get_classes

  28. def conv2d(filter_in, filter_out, kernel_size):
  29.     pad = (kernel_size - 1) // 2 if kernel_size else 0
  30.     return nn.Sequential(OrderedDict([
  31.         ("conv", nn.Conv2d(filter_in, filter_out, kernel_size=kernel_size, stride=1, padding=pad, bias=False)),
  32.         ("bn", nn.BatchNorm2d(filter_out)),
  33.         ("relu", nn.LeakyReLU(0.1)),
  34.     ]))
  35. #------------------------------------------------------------------------#
  36. #   make_last_layers里面一共有七个卷积,前五个用于提取特征。
  37. #   后两个用于获得yolo网络的预测结果
  38. #------------------------------------------------------------------------#
  39. def make_last_layers(filters_list, in_filters, out_filter):
  40.     m = nn.Sequential(
  41.         conv2d(in_filters, filters_list[0], 1),
  42.         conv2d(filters_list[0], filters_list[1], 3),
  43.         conv2d(filters_list[1], filters_list[0], 1),
  44.         conv2d(filters_list[0], filters_list[1], 3),
  45.         conv2d(filters_list[1], filters_list[0], 1),
  46.         conv2d(filters_list[0], filters_list[1], 3),
  47.         nn.Conv2d(filters_list[1], out_filter, kernel_size=1, stride=1, padding=0, bias=True)
  48.     )
  49.     return m

  50. class YoloBody(nn.Module):
  51.     def __init__(self, anchors_mask, num_classes, phi=0, load_weights = False):
  52.         super(YoloBody, self).__init__()
  53.         #---------------------------------------------------#   
  54.         #   生成efficientnet的主干模型,以efficientnetB0为例
  55.         #   获得三个有效特征层,他们的shape分别是:
  56.         #   52, 52, 40
  57.         #   26, 26, 112
  58.         #   13, 13, 320
  59.         #---------------------------------------------------#
  60.         self.backbone = EfficientNet(phi, load_weights = load_weights)

  61.         out_filters = {
  62.             0: [40, 112, 320],
  63.             1: [40, 112, 320],
  64.             2: [48, 120, 352],
  65.             3: [48, 136, 384],
  66.             4: [56, 160, 448],
  67.             5: [64, 176, 512],
  68.             6: [72, 200, 576],
  69.             7: [80, 224, 640],
  70.         }[phi]

  71.         self.last_layer0            = make_last_layers([out_filters[-1], int(out_filters[-1]*2)], out_filters[-1], len(anchors_mask[0]) * (num_classes + 5))

  72.         self.last_layer1_conv       = conv2d(out_filters[-1], out_filters[-2], 1)
  73.         self.last_layer1_upsample   = nn.Upsample(scale_factor=2, mode='nearest')
  74.         self.last_layer1            = make_last_layers([out_filters[-2], int(out_filters[-2]*2)], out_filters[-2] + out_filters[-2], len(anchors_mask[1]) * (num_classes + 5))

  75.         self.last_layer2_conv       = conv2d(out_filters[-2], out_filters[-3], 1)
  76.         self.last_layer2_upsample   = nn.Upsample(scale_factor=2, mode='nearest')
  77.         self.last_layer2            = make_last_layers([out_filters[-3], int(out_filters[-3]*2)], out_filters[-3] + out_filters[-3], len(anchors_mask[2]) * (num_classes + 5))

  78.     def forward(self, x):
  79.         #---------------------------------------------------#   
  80.         #   获得三个有效特征层,他们的shape分别是:
  81.         #   52, 52, 40
  82.         #   26, 26, 112
  83.         #   13, 13, 320
  84.         #---------------------------------------------------#
  85.         x2, x1, x0 = self.backbone(x)

  86.         #---------------------------------------------------#
  87.         #   第一个特征层
  88.         #   out0 = (batch_size,255,13,13)
  89.         #---------------------------------------------------#
  90.         out0_branch = self.last_layer0[:5](x0)
  91.         out0        = self.last_layer0[5:](out0_branch)

  92.         x1_in = self.last_layer1_conv(out0_branch)
  93.         x1_in = self.last_layer1_upsample(x1_in)

  94.         x1_in = torch.cat([x1_in, x1], 1)
  95.         #---------------------------------------------------#
  96.         #   第二个特征层
  97.         #   out1 = (batch_size,255,26,26)
  98.         #---------------------------------------------------#
  99.         out1_branch = self.last_layer1[:5](x1_in)
  100.         out1        = self.last_layer1[5:](out1_branch)

  101.         x2_in = self.last_layer2_conv(out1_branch)
  102.         x2_in = self.last_layer2_upsample(x2_in)

  103.         x2_in = torch.cat([x2_in, x2], 1)
  104.         #---------------------------------------------------#
  105.         #   第一个特征层
  106.         #   out3 = (batch_size,255,52,52)
  107.         #---------------------------------------------------#
  108.         out2 = self.last_layer2(x2_in)
  109.         return out0, out1, out2

  110. class YOLOLoss(nn.Module):
  111.     def __init__(self, anchors, num_classes, input_shape, cuda, anchors_mask = [[6,7,8], [3,4,5], [0,1,2]]):
  112.         super(YOLOLoss, self).__init__()
  113.         #-----------------------------------------------------------#
  114.         #   13x13的特征层对应的anchor是[116,90],[156,198],[373,326]
  115.         #   26x26的特征层对应的anchor是[30,61],[62,45],[59,119]
  116.         #   52x52的特征层对应的anchor是[10,13],[16,30],[33,23]
  117.         #-----------------------------------------------------------#
  118.         self.anchors        = anchors
  119.         self.num_classes    = num_classes
  120.         self.bbox_attrs     = 5 + num_classes
  121.         self.input_shape    = input_shape
  122.         self.anchors_mask   = anchors_mask

  123.         self.giou           = True
  124.         self.balance        = [0.4, 1.0, 4]
  125.         self.box_ratio      = 0.05
  126.         self.obj_ratio      = 5 * (input_shape[0] * input_shape[1]) / (416 ** 2)
  127.         self.cls_ratio      = 1 * (num_classes / 80)

  128.         self.ignore_threshold = 0.5
  129.         self.cuda           = cuda

  130.     def clip_by_tensor(self, t, t_min, t_max):
  131.         t = t.float()
  132.         result = (t >= t_min).float() * t + (t < t_min).float() * t_min
  133.         result = (result <= t_max).float() * result + (result > t_max).float() * t_max
  134.         return result

  135.     def MSELoss(self, pred, target):
  136.         return torch.pow(pred - target, 2)

  137.     def BCELoss(self, pred, target):
  138.         epsilon = 1e-7
  139.         pred    = self.clip_by_tensor(pred, epsilon, 1.0 - epsilon)
  140.         output  = - target * torch.log(pred) - (1.0 - target) * torch.log(1.0 - pred)
  141.         return output

  142.     def box_giou(self, b1, b2):
  143.         """
  144.         输入为:
  145.         ----------
  146.         b1: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
  147.         b2: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh

  148.         返回为:
  149.         -------
  150.         giou: tensor, shape=(batch, feat_w, feat_h, anchor_num, 1)
  151.         """
  152.         #----------------------------------------------------#
  153.         #   求出预测框左上角右下角
  154.         #----------------------------------------------------#
  155.         b1_xy       = b1[..., :2]
  156.         b1_wh       = b1[..., 2:4]
  157.         b1_wh_half  = b1_wh/2.
  158.         b1_mins     = b1_xy - b1_wh_half
  159.         b1_maxes    = b1_xy + b1_wh_half
  160.         #----------------------------------------------------#
  161.         #   求出真实框左上角右下角
  162.         #----------------------------------------------------#
  163.         b2_xy       = b2[..., :2]
  164.         b2_wh       = b2[..., 2:4]
  165.         b2_wh_half  = b2_wh/2.
  166.         b2_mins     = b2_xy - b2_wh_half
  167.         b2_maxes    = b2_xy + b2_wh_half

  168.         #----------------------------------------------------#
  169.         #   求真实框和预测框所有的iou
  170.         #----------------------------------------------------#
  171.         intersect_mins  = torch.max(b1_mins, b2_mins)
  172.         intersect_maxes = torch.min(b1_maxes, b2_maxes)
  173.         intersect_wh    = torch.max(intersect_maxes - intersect_mins, torch.zeros_like(intersect_maxes))
  174.         intersect_area  = intersect_wh[..., 0] * intersect_wh[..., 1]
  175.         b1_area         = b1_wh[..., 0] * b1_wh[..., 1]
  176.         b2_area         = b2_wh[..., 0] * b2_wh[..., 1]
  177.         union_area      = b1_area + b2_area - intersect_area
  178.         iou             = intersect_area / union_area

  179.         #----------------------------------------------------#
  180.         #   找到包裹两个框的最小框的左上角和右下角
  181.         #----------------------------------------------------#
  182.         enclose_mins    = torch.min(b1_mins, b2_mins)
  183.         enclose_maxes   = torch.max(b1_maxes, b2_maxes)
  184.         enclose_wh      = torch.max(enclose_maxes - enclose_mins, torch.zeros_like(intersect_maxes))
  185.         #----------------------------------------------------#
  186.         #   计算对角线距离
  187.         #----------------------------------------------------#
  188.         enclose_area    = enclose_wh[..., 0] * enclose_wh[..., 1]
  189.         giou            = iou - (enclose_area - union_area) / enclose_area
  190.         
  191.         return giou
  192.         
  193.     def forward(self, l, input, targets=None):
  194.         #----------------------------------------------------#
  195.         #   l代表的是,当前输入进来的有效特征层,是第几个有效特征层
  196.         #   input的shape为  bs, 3*(5+num_classes), 13, 13
  197.         #                   bs, 3*(5+num_classes), 26, 26
  198.         #                   bs, 3*(5+num_classes), 52, 52
  199.         #   targets代表的是真实框。
  200.         #----------------------------------------------------#
  201.         #--------------------------------#
  202.         #   获得图片数量,特征层的高和宽
  203.         #   13和13
  204.         #--------------------------------#
  205.         bs      = input.size(0)
  206.         in_h    = input.size(2)
  207.         in_w    = input.size(3)
  208.         #-----------------------------------------------------------------------#
  209.         #   计算步长
  210.         #   每一个特征点对应原来的图片上多少个像素点
  211.         #   如果特征层为13x13的话,一个特征点就对应原来的图片上的32个像素点
  212.         #   如果特征层为26x26的话,一个特征点就对应原来的图片上的16个像素点
  213.         #   如果特征层为52x52的话,一个特征点就对应原来的图片上的8个像素点
  214.         #   stride_h = stride_w = 32、16、8
  215.         #   stride_h和stride_w都是32。
  216.         #-----------------------------------------------------------------------#
  217.         stride_h = self.input_shape[0] / in_h
  218.         stride_w = self.input_shape[1] / in_w
  219.         #-------------------------------------------------#
  220.         #   此时获得的scaled_anchors大小是相对于特征层的
  221.         #-------------------------------------------------#
  222.         scaled_anchors  = [(a_w / stride_w, a_h / stride_h) for a_w, a_h in self.anchors]
  223.         #-----------------------------------------------#
  224.         #   输入的input一共有三个,他们的shape分别是
  225.         #   bs, 3*(5+num_classes), 13, 13 => batch_size, 3, 13, 13, 5 + num_classes
  226.         #   batch_size, 3, 26, 26, 5 + num_classes
  227.         #   batch_size, 3, 52, 52, 5 + num_classes
  228.         #-----------------------------------------------#
  229.         prediction = input.view(bs, len(self.anchors_mask[l]), self.bbox_attrs, in_h, in_w).permute(0, 1, 3, 4, 2).contiguous()
  230.         
  231.         #-----------------------------------------------#
  232.         #   先验框的中心位置的调整参数
  233.         #-----------------------------------------------#
  234.         x = torch.sigmoid(prediction[..., 0])
  235.         y = torch.sigmoid(prediction[..., 1])
  236.         #-----------------------------------------------#
  237.         #   先验框的宽高调整参数
  238.         #-----------------------------------------------#
  239.         w = prediction[..., 2]
  240.         h = prediction[..., 3]
  241.         #-----------------------------------------------#
  242.         #   获得置信度,是否有物体
  243.         #-----------------------------------------------#
  244.         conf = torch.sigmoid(prediction[..., 4])
  245.         #-----------------------------------------------#
  246.         #   种类置信度
  247.         #-----------------------------------------------#
  248.         pred_cls = torch.sigmoid(prediction[..., 5:])

  249.         #-----------------------------------------------#
  250.         #   获得网络应该有的预测结果
  251.         #-----------------------------------------------#
  252.         y_true, noobj_mask, box_loss_scale = self.get_target(l, targets, scaled_anchors, in_h, in_w)

  253.         #---------------------------------------------------------------#
  254.         #   将预测结果进行解码,判断预测结果和真实值的重合程度
  255.         #   如果重合程度过大则忽略,因为这些特征点属于预测比较准确的特征点
  256.         #   作为负样本不合适
  257.         #----------------------------------------------------------------#
  258.         noobj_mask, pred_boxes = self.get_ignore(l, x, y, h, w, targets, scaled_anchors, in_h, in_w, noobj_mask)

  259.         if self.cuda:
  260.             y_true          = y_true.type_as(x)
  261.             noobj_mask      = noobj_mask.type_as(x)
  262.             box_loss_scale  = box_loss_scale.type_as(x)
  263.         #--------------------------------------------------------------------------#
  264.         #   box_loss_scale是真实框宽高的乘积,宽高均在0-1之间,因此乘积也在0-1之间。
  265.         #   2-宽高的乘积代表真实框越大,比重越小,小框的比重更大。
  266.         #--------------------------------------------------------------------------#
  267.         box_loss_scale = 2 - box_loss_scale
  268.             
  269.         loss        = 0
  270.         obj_mask    = y_true[..., 4] == 1
  271.         n           = torch.sum(obj_mask)
  272.         if n != 0:
  273.             if self.giou:
  274.                 #---------------------------------------------------------------#
  275.                 #   计算预测结果和真实结果的giou
  276.                 #----------------------------------------------------------------#
  277.                 giou        = self.box_giou(pred_boxes, y_true[..., :4]).type_as(x)
  278.                 loss_loc    = torch.mean((1 - giou)[obj_mask])
  279.             else:
  280.                 #-----------------------------------------------------------#
  281.                 #   计算中心偏移情况的loss,使用BCELoss效果好一些
  282.                 #-----------------------------------------------------------#
  283.                 loss_x      = torch.mean(self.BCELoss(x[obj_mask], y_true[..., 0][obj_mask]) * box_loss_scale[obj_mask])
  284.                 loss_y      = torch.mean(self.BCELoss(y[obj_mask], y_true[..., 1][obj_mask]) * box_loss_scale[obj_mask])
  285.                 #-----------------------------------------------------------#
  286.                 #   计算宽高调整值的loss
  287.                 #-----------------------------------------------------------#
  288.                 loss_w      = torch.mean(self.MSELoss(w[obj_mask], y_true[..., 2][obj_mask]) * box_loss_scale[obj_mask])
  289.                 loss_h      = torch.mean(self.MSELoss(h[obj_mask], y_true[..., 3][obj_mask]) * box_loss_scale[obj_mask])
  290.                 loss_loc    = (loss_x + loss_y + loss_h + loss_w) * 0.1

  291.             loss_cls    = torch.mean(self.BCELoss(pred_cls[obj_mask], y_true[..., 5:][obj_mask]))
  292.             loss        += loss_loc * self.box_ratio + loss_cls * self.cls_ratio

  293.         loss_conf   = torch.mean(self.BCELoss(conf, obj_mask.type_as(conf))[noobj_mask.bool() | obj_mask])
  294.         loss        += loss_conf * self.balance[l] * self.obj_ratio
  295.         # if n != 0:
  296.         #     print(loss_loc * self.box_ratio, loss_cls * self.cls_ratio, loss_conf * self.balance[l] * self.obj_ratio)
  297.         return loss

  298.     def calculate_iou(self, _box_a, _box_b):
  299.         #-----------------------------------------------------------#
  300.         #   计算真实框的左上角和右下角
  301.         #-----------------------------------------------------------#
  302.         b1_x1, b1_x2 = _box_a[:, 0] - _box_a[:, 2] / 2, _box_a[:, 0] + _box_a[:, 2] / 2
  303.         b1_y1, b1_y2 = _box_a[:, 1] - _box_a[:, 3] / 2, _box_a[:, 1] + _box_a[:, 3] / 2
  304.         #-----------------------------------------------------------#
  305.         #   计算先验框获得的预测框的左上角和右下角
  306.         #-----------------------------------------------------------#
  307.         b2_x1, b2_x2 = _box_b[:, 0] - _box_b[:, 2] / 2, _box_b[:, 0] + _box_b[:, 2] / 2
  308.         b2_y1, b2_y2 = _box_b[:, 1] - _box_b[:, 3] / 2, _box_b[:, 1] + _box_b[:, 3] / 2

  309.         #-----------------------------------------------------------#
  310.         #   将真实框和预测框都转化成左上角右下角的形式
  311.         #-----------------------------------------------------------#
  312.         box_a = torch.zeros_like(_box_a)
  313.         box_b = torch.zeros_like(_box_b)
  314.         box_a[:, 0], box_a[:, 1], box_a[:, 2], box_a[:, 3] = b1_x1, b1_y1, b1_x2, b1_y2
  315.         box_b[:, 0], box_b[:, 1], box_b[:, 2], box_b[:, 3] = b2_x1, b2_y1, b2_x2, b2_y2

  316.         #-----------------------------------------------------------#
  317.         #   A为真实框的数量,B为先验框的数量
  318.         #-----------------------------------------------------------#
  319.         A = box_a.size(0)
  320.         B = box_b.size(0)

  321.         #-----------------------------------------------------------#
  322.         #   计算交的面积
  323.         #-----------------------------------------------------------#
  324.         max_xy  = torch.min(box_a[:, 2:].unsqueeze(1).expand(A, B, 2), box_b[:, 2:].unsqueeze(0).expand(A, B, 2))
  325.         min_xy  = torch.max(box_a[:, :2].unsqueeze(1).expand(A, B, 2), box_b[:, :2].unsqueeze(0).expand(A, B, 2))
  326.         inter   = torch.clamp((max_xy - min_xy), min=0)
  327.         inter   = inter[:, :, 0] * inter[:, :, 1]
  328.         #-----------------------------------------------------------#
  329.         #   计算预测框和真实框各自的面积
  330.         #-----------------------------------------------------------#
  331.         area_a = ((box_a[:, 2]-box_a[:, 0]) * (box_a[:, 3]-box_a[:, 1])).unsqueeze(1).expand_as(inter)  # [A,B]
  332.         area_b = ((box_b[:, 2]-box_b[:, 0]) * (box_b[:, 3]-box_b[:, 1])).unsqueeze(0).expand_as(inter)  # [A,B]
  333.         #-----------------------------------------------------------#
  334.         #   求IOU
  335.         #-----------------------------------------------------------#
  336.         union = area_a + area_b - inter
  337.         return inter / union  # [A,B]
  338.    
  339.     def get_target(self, l, targets, anchors, in_h, in_w):
  340.         #-----------------------------------------------------#
  341.         #   计算一共有多少张图片
  342.         #-----------------------------------------------------#
  343.         bs              = len(targets)
  344.         #-----------------------------------------------------#
  345.         #   用于选取哪些先验框不包含物体
  346.         #-----------------------------------------------------#
  347.         noobj_mask      = torch.ones(bs, len(self.anchors_mask[l]), in_h, in_w, requires_grad = False)
  348.         #-----------------------------------------------------#
  349.         #   让网络更加去关注小目标
  350.         #-----------------------------------------------------#
  351.         box_loss_scale  = torch.zeros(bs, len(self.anchors_mask[l]), in_h, in_w, requires_grad = False)
  352.         #-----------------------------------------------------#
  353.         #   batch_size, 3, 13, 13, 5 + num_classes
  354.         #-----------------------------------------------------#
  355.         y_true          = torch.zeros(bs, len(self.anchors_mask[l]), in_h, in_w, self.bbox_attrs, requires_grad = False)
  356.         for b in range(bs):            
  357.             if len(targets[b])==0:
  358.                 continue
  359.             batch_target = torch.zeros_like(targets[b])
  360.             #-------------------------------------------------------#
  361.             #   计算出正样本在特征层上的中心点
  362.             #-------------------------------------------------------#
  363.             batch_target[:, [0,2]] = targets[b][:, [0,2]] * in_w
  364.             batch_target[:, [1,3]] = targets[b][:, [1,3]] * in_h
  365.             batch_target[:, 4] = targets[b][:, 4]
  366.             batch_target = batch_target.cpu()
  367.             
  368.             #-------------------------------------------------------#
  369.             #   将真实框转换一个形式
  370.             #   num_true_box, 4
  371.             #-------------------------------------------------------#
  372.             gt_box          = torch.FloatTensor(torch.cat((torch.zeros((batch_target.size(0), 2)), batch_target[:, 2:4]), 1))
  373.             #-------------------------------------------------------#
  374.             #   将先验框转换一个形式
  375.             #   9, 4
  376.             #-------------------------------------------------------#
  377.             anchor_shapes   = torch.FloatTensor(torch.cat((torch.zeros((len(anchors), 2)), torch.FloatTensor(anchors)), 1))
  378.             #-------------------------------------------------------#
  379.             #   计算交并比
  380.             #   self.calculate_iou(gt_box, anchor_shapes) = [num_true_box, 9]每一个真实框和9个先验框的重合情况
  381.             #   best_ns:
  382.             #   [每个真实框最大的重合度max_iou, 每一个真实框最重合的先验框的序号]
  383.             #-------------------------------------------------------#
  384.             best_ns = torch.argmax(self.calculate_iou(gt_box, anchor_shapes), dim=-1)

  385.             for t, best_n in enumerate(best_ns):
  386.                 if best_n not in self.anchors_mask[l]:
  387.                     continue
  388.                 #----------------------------------------#
  389.                 #   判断这个先验框是当前特征点的哪一个先验框
  390.                 #----------------------------------------#
  391.                 k = self.anchors_mask[l].index(best_n)
  392.                 #----------------------------------------#
  393.                 #   获得真实框属于哪个网格点
  394.                 #----------------------------------------#
  395.                 i = torch.floor(batch_target[t, 0]).long()
  396.                 j = torch.floor(batch_target[t, 1]).long()
  397.                 #----------------------------------------#
  398.                 #   取出真实框的种类
  399.                 #----------------------------------------#
  400.                 c = batch_target[t, 4].long()

  401.                 #----------------------------------------#
  402.                 #   noobj_mask代表无目标的特征点
  403.                 #----------------------------------------#
  404.                 noobj_mask[b, k, j, i] = 0
  405.                 #----------------------------------------#
  406.                 #   tx、ty代表中心调整参数的真实值
  407.                 #----------------------------------------#
  408.                 if not self.giou:
  409.                     #----------------------------------------#
  410.                     #   tx、ty代表中心调整参数的真实值
  411.                     #----------------------------------------#
  412.                     y_true[b, k, j, i, 0] = batch_target[t, 0] - i.float()
  413.                     y_true[b, k, j, i, 1] = batch_target[t, 1] - j.float()
  414.                     y_true[b, k, j, i, 2] = math.log(batch_target[t, 2] / anchors[best_n][0])
  415.                     y_true[b, k, j, i, 3] = math.log(batch_target[t, 3] / anchors[best_n][1])
  416.                     y_true[b, k, j, i, 4] = 1
  417.                     y_true[b, k, j, i, c + 5] = 1
  418.                 else:
  419.                     #----------------------------------------#
  420.                     #   tx、ty代表中心调整参数的真实值
  421.                     #----------------------------------------#
  422.                     y_true[b, k, j, i, 0] = batch_target[t, 0]
  423.                     y_true[b, k, j, i, 1] = batch_target[t, 1]
  424.                     y_true[b, k, j, i, 2] = batch_target[t, 2]
  425.                     y_true[b, k, j, i, 3] = batch_target[t, 3]
  426.                     y_true[b, k, j, i, 4] = 1
  427.                     y_true[b, k, j, i, c + 5] = 1
  428.                 #----------------------------------------#
  429.                 #   用于获得xywh的比例
  430.                 #   大目标loss权重小,小目标loss权重大
  431.                 #----------------------------------------#
  432.                 box_loss_scale[b, k, j, i] = batch_target[t, 2] * batch_target[t, 3] / in_w / in_h
  433.         return y_true, noobj_mask, box_loss_scale

  434.     def get_ignore(self, l, x, y, h, w, targets, scaled_anchors, in_h, in_w, noobj_mask):
  435.         #-----------------------------------------------------#
  436.         #   计算一共有多少张图片
  437.         #-----------------------------------------------------#
  438.         bs = len(targets)

  439.         #-----------------------------------------------------#
  440.         #   生成网格,先验框中心,网格左上角
  441.         #-----------------------------------------------------#
  442.         grid_x = torch.linspace(0, in_w - 1, in_w).repeat(in_h, 1).repeat(
  443.             int(bs * len(self.anchors_mask[l])), 1, 1).view(x.shape).type_as(x)
  444.         grid_y = torch.linspace(0, in_h - 1, in_h).repeat(in_w, 1).t().repeat(
  445.             int(bs * len(self.anchors_mask[l])), 1, 1).view(y.shape).type_as(x)

  446.         # 生成先验框的宽高
  447.         scaled_anchors_l = np.array(scaled_anchors)[self.anchors_mask[l]]
  448.         anchor_w = torch.Tensor(scaled_anchors_l).index_select(1, torch.LongTensor([0])).type_as(x)
  449.         anchor_h = torch.Tensor(scaled_anchors_l).index_select(1, torch.LongTensor([1])).type_as(x)
  450.         
  451.         anchor_w = anchor_w.repeat(bs, 1).repeat(1, 1, in_h * in_w).view(w.shape)
  452.         anchor_h = anchor_h.repeat(bs, 1).repeat(1, 1, in_h * in_w).view(h.shape)
  453.         #-------------------------------------------------------#
  454.         #   计算调整后的先验框中心与宽高
  455.         #-------------------------------------------------------#
  456.         pred_boxes_x    = torch.unsqueeze(x + grid_x, -1)
  457.         pred_boxes_y    = torch.unsqueeze(y + grid_y, -1)
  458.         pred_boxes_w    = torch.unsqueeze(torch.exp(w) * anchor_w, -1)
  459.         pred_boxes_h    = torch.unsqueeze(torch.exp(h) * anchor_h, -1)
  460.         pred_boxes      = torch.cat([pred_boxes_x, pred_boxes_y, pred_boxes_w, pred_boxes_h], dim = -1)
  461.         
  462.         for b in range(bs):           
  463.             #-------------------------------------------------------#
  464.             #   将预测结果转换一个形式
  465.             #   pred_boxes_for_ignore      num_anchors, 4
  466.             #-------------------------------------------------------#
  467.             pred_boxes_for_ignore = pred_boxes[b].view(-1, 4)
  468.             #-------------------------------------------------------#
  469.             #   计算真实框,并把真实框转换成相对于特征层的大小
  470.             #   gt_box      num_true_box, 4
  471.             #-------------------------------------------------------#
  472.             if len(targets[b]) > 0:
  473.                 batch_target = torch.zeros_like(targets[b])
  474.                 #-------------------------------------------------------#
  475.                 #   计算出正样本在特征层上的中心点
  476.                 #-------------------------------------------------------#
  477.                 batch_target[:, [0,2]] = targets[b][:, [0,2]] * in_w
  478.                 batch_target[:, [1,3]] = targets[b][:, [1,3]] * in_h
  479.                 batch_target = batch_target[:, :4].type_as(x)
  480.                 #-------------------------------------------------------#
  481.                 #   计算交并比
  482.                 #   anch_ious       num_true_box, num_anchors
  483.                 #-------------------------------------------------------#
  484.                 anch_ious = self.calculate_iou(batch_target, pred_boxes_for_ignore)
  485.                 #-------------------------------------------------------#
  486.                 #   每个先验框对应真实框的最大重合度
  487.                 #   anch_ious_max   num_anchors
  488.                 #-------------------------------------------------------#
  489.                 anch_ious_max, _    = torch.max(anch_ious, dim = 0)
  490.                 anch_ious_max       = anch_ious_max.view(pred_boxes[b].size()[:3])
  491.                 noobj_mask[b][anch_ious_max > self.ignore_threshold] = 0
  492.         return noobj_mask, pred_boxes
  493.    
  494. #---------------------------------------------------------#
  495. #   将图像转换成RGB图像,防止灰度图在预测时报错。
  496. #   代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
  497. #---------------------------------------------------------#
  498. def cvtColor(image):
  499.     if len(np.shape(image)) == 3 and np.shape(image)[2] == 3:
  500.         return image
  501.     else:
  502.         image = image.convert('RGB')
  503.         return image
  504.    
  505. #---------------------------------------------------#
  506. #   对输入图像进行resize
  507. #---------------------------------------------------#
  508. def resize_image(image, size, letterbox_image):
  509.     iw, ih  = image.size
  510.     w, h    = size
  511.     if letterbox_image:
  512.         scale   = min(w/iw, h/ih)
  513.         nw      = int(iw*scale)
  514.         nh      = int(ih*scale)

  515.         image   = image.resize((nw,nh), Image.BICUBIC)
  516.         new_image = Image.new('RGB', size, (128,128,128))
  517.         new_image.paste(image, ((w-nw)//2, (h-nh)//2))
  518.     else:
  519.         new_image = image.resize((w, h), Image.BICUBIC)
  520.     return new_image

  521. #---------------------------------------------------#
  522. #   获得类
  523. #---------------------------------------------------#
  524. def get_classes(classes_path):
  525.     with open(classes_path, encoding='utf-8') as f:
  526.         class_names = f.readlines()
  527.     class_names = [c.strip() for c in class_names]
  528.     return class_names, len(class_names)

  529. #---------------------------------------------------#
  530. #   获得先验框
  531. #---------------------------------------------------#
  532. def get_anchors(anchors_path):
  533.     '''loads the anchors from a file'''
  534.     with open(anchors_path, encoding='utf-8') as f:
  535.         anchors = f.readline()
  536.     anchors = [float(x) for x in anchors.split(',')]
  537.     anchors = np.array(anchors).reshape(-1, 2)
  538.     return anchors, len(anchors)

  539. #---------------------------------------------------#
  540. #   获得学习率
  541. #---------------------------------------------------#
  542. def get_lr(optimizer):
  543.     for param_group in optimizer.param_groups:
  544.         return param_group['lr']

  545. def preprocess_input(image):
  546.     image /= 255.0
  547.     return image

  548. class YoloDataset(Dataset):
  549.     def __init__(self, annotation_lines, input_shape, num_classes, train):
  550.         super(YoloDataset, self).__init__()
  551.         self.annotation_lines   = annotation_lines
  552.         self.input_shape        = input_shape
  553.         self.num_classes        = num_classes
  554.         self.length             = len(self.annotation_lines)
  555.         self.train              = train

  556.     def __len__(self):
  557.         return self.length

  558.     def __getitem__(self, index):
  559.         index       = index % self.length
  560.         #---------------------------------------------------#
  561.         #   训练时进行数据的随机增强
  562.         #   验证时不进行数据的随机增强
  563.         #---------------------------------------------------#
  564.         image, box  = self.get_random_data(self.annotation_lines[index], self.input_shape[0:2], random = self.train)
  565.         image       = np.transpose(preprocess_input(np.array(image, dtype=np.float32)), (2, 0, 1))
  566.         box         = np.array(box, dtype=np.float32)
  567.         if len(box) != 0:
  568.             box[:, [0, 2]] = box[:, [0, 2]] / self.input_shape[1]
  569.             box[:, [1, 3]] = box[:, [1, 3]] / self.input_shape[0]

  570.             box[:, 2:4] = box[:, 2:4] - box[:, 0:2]
  571.             box[:, 0:2] = box[:, 0:2] + box[:, 2:4] / 2
  572.         return image, box

  573.     def rand(self, a=0, b=1):
  574.         return np.random.rand()*(b-a) + a

  575.     def get_random_data(self, annotation_line, input_shape, jitter=.3, hue=.1, sat=0.7, val=0.4, random=True):
  576.         line    = annotation_line.split()
  577.         #------------------------------#
  578.         #   读取图像并转换成RGB图像
  579.         #------------------------------#
  580.         image   = Image.open(line[0])
  581.         image   = cvtColor(image)
  582.         #------------------------------#
  583.         #   获得图像的高宽与目标高宽
  584.         #------------------------------#
  585.         iw, ih  = image.size
  586.         h, w    = input_shape
  587.         #------------------------------#
  588.         #   获得预测框
  589.         #------------------------------#
  590.         box     = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]])

  591.         if not random:
  592.             scale = min(w/iw, h/ih)
  593.             nw = int(iw*scale)
  594.             nh = int(ih*scale)
  595.             dx = (w-nw)//2
  596.             dy = (h-nh)//2

  597.             #---------------------------------#
  598.             #   将图像多余的部分加上灰条
  599.             #---------------------------------#
  600.             image       = image.resize((nw,nh), Image.BICUBIC)
  601.             new_image   = Image.new('RGB', (w,h), (128,128,128))
  602.             new_image.paste(image, (dx, dy))
  603.             image_data  = np.array(new_image, np.float32)

  604.             #---------------------------------#
  605.             #   对真实框进行调整
  606.             #---------------------------------#
  607.             if len(box)>0:
  608.                 np.random.shuffle(box)
  609.                 box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
  610.                 box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
  611.                 box[:, 0:2][box[:, 0:2]<0] = 0
  612.                 box[:, 2][box[:, 2]>w] = w
  613.                 box[:, 3][box[:, 3]>h] = h
  614.                 box_w = box[:, 2] - box[:, 0]
  615.                 box_h = box[:, 3] - box[:, 1]
  616.                 box = box[np.logical_and(box_w>1, box_h>1)] # discard invalid box

  617.             return image_data, box
  618.                
  619.         #------------------------------------------#
  620.         #   对图像进行缩放并且进行长和宽的扭曲
  621.         #------------------------------------------#
  622.         new_ar = iw/ih * self.rand(1-jitter,1+jitter) / self.rand(1-jitter,1+jitter)
  623.         scale = self.rand(.25, 2)
  624.         if new_ar < 1:
  625.             nh = int(scale*h)
  626.             nw = int(nh*new_ar)
  627.         else:
  628.             nw = int(scale*w)
  629.             nh = int(nw/new_ar)
  630.         image = image.resize((nw,nh), Image.BICUBIC)

  631.         #------------------------------------------#
  632.         #   将图像多余的部分加上灰条
  633.         #------------------------------------------#
  634.         dx = int(self.rand(0, w-nw))
  635.         dy = int(self.rand(0, h-nh))
  636.         new_image = Image.new('RGB', (w,h), (128,128,128))
  637.         new_image.paste(image, (dx, dy))
  638.         image = new_image

  639.         #------------------------------------------#
  640.         #   翻转图像
  641.         #------------------------------------------#
  642.         flip = self.rand()<.5
  643.         if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT)

  644.         image_data      = np.array(image, np.uint8)
  645.         #---------------------------------#
  646.         #   对图像进行色域变换
  647.         #   计算色域变换的参数
  648.         #---------------------------------#
  649.         r               = np.random.uniform(-1, 1, 3) * [hue, sat, val] + 1
  650.         #---------------------------------#
  651.         #   将图像转到HSV上
  652.         #---------------------------------#
  653.         hue, sat, val   = cv2.split(cv2.cvtColor(image_data, cv2.COLOR_RGB2HSV))
  654.         dtype           = image_data.dtype
  655.         #---------------------------------#
  656.         #   应用变换
  657.         #---------------------------------#
  658.         x       = np.arange(0, 256, dtype=r.dtype)
  659.         lut_hue = ((x * r[0]) % 180).astype(dtype)
  660.         lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
  661.         lut_val = np.clip(x * r[2], 0, 255).astype(dtype)

  662.         image_data = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val)))
  663.         image_data = cv2.cvtColor(image_data, cv2.COLOR_HSV2RGB)

  664.         #---------------------------------#
  665.         #   对真实框进行调整
  666.         #---------------------------------#
  667.         if len(box)>0:
  668.             np.random.shuffle(box)
  669.             box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
  670.             box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
  671.             if flip: box[:, [0,2]] = w - box[:, [2,0]]
  672.             box[:, 0:2][box[:, 0:2]<0] = 0
  673.             box[:, 2][box[:, 2]>w] = w
  674.             box[:, 3][box[:, 3]>h] = h
  675.             box_w = box[:, 2] - box[:, 0]
  676.             box_h = box[:, 3] - box[:, 1]
  677.             box = box[np.logical_and(box_w>1, box_h>1)]
  678.         
  679.         return image_data, box
  680.    
  681. # DataLoader中collate_fn使用
  682. def yolo_dataset_collate(batch):
  683.     images = []
  684.     bboxes = []
  685.     for img, box in batch:
  686.         images.append(img)
  687.         bboxes.append(box)
  688.     images = torch.from_numpy(np.array(images)).type(torch.FloatTensor)
  689.     bboxes = [torch.from_numpy(ann).type(torch.FloatTensor) for ann in bboxes]
  690.     return images, bboxes

  691. if __name__ == "__main__":
  692.     #---------------------------------#
  693.     #   input_shape     输入的shape大小,一定要是32的倍数
  694.     #------------------------------------------------------#
  695.     input_shape     = [416, 416]
  696.     img_channel = 1
  697.     img_size = 416
  698.     num_epochs      = 300
  699.     lr_init = 0.001
  700.     num_workers         = 0
  701.     #----------------------------------------------------#
  702.     #   获得图片路径和标签
  703.     #----------------------------------------------------#
  704.     train_annotation_path   = '2007_train.txt'
  705.     val_annotation_path     = '2007_val.txt'
  706.    
  707.     device          = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  708.     #----------------------------------------------------#
  709.     #   获取classes和anchor
  710.     #----------------------------------------------------#
  711.     # class_names, num_classes = get_classes(classes_path)
  712.     num_classes = 1
  713.     class_names = ["Pos"]*num_classes
  714.     # anchors, num_anchors     = get_anchors(anchors_path)
  715.     anchors = np.array([[ 10.,  13.],
  716.            [ 16.,  30.],
  717.            [ 33.,  23.],
  718.            [ 30.,  61.],
  719.            [ 62.,  45.],
  720.            [ 59., 119.],
  721.            [116.,  90.],
  722.            [156., 198.],
  723.            [373., 326.]])
  724.     num_anchors = len(anchors)
  725.     anchors_mask    = [[6, 7, 8], [3, 4, 5], [0, 1, 2]]
  726.    
  727.     #------------------------------------------------------#
  728.     #   创建yolo模型
  729.     #------------------------------------------------------#
  730.     model = YoloBody(anchors_mask, num_classes, phi=0, load_weights=False)
  731.     # if not pretrained:
  732.     #     weights_init(model)
  733.     yolo_loss    = YOLOLoss(anchors, num_classes, input_shape, True, anchors_mask)
  734.         
  735.     #------------------------------------------------------------------#
  736.     #   torch 1.2不支持amp,建议使用torch 1.7.1及以上正确使用fp16
  737.     #   因此torch1.2这里显示"could not be resolve"
  738.     #------------------------------------------------------------------#
  739.    
  740.     GPU_index = 0
  741.     # model_train = torch.nn.DataParallel(model)
  742.     cudnn.benchmark = True
  743.    
  744.     #---------------------------#
  745.     #   读取数据集对应的txt
  746.     #---------------------------#
  747.     with open(train_annotation_path) as f:
  748.         train_lines = f.readlines()
  749.     with open(val_annotation_path) as f:
  750.         val_lines   = f.readlines()
  751.     num_train   = len(train_lines)
  752.     num_val     = len(val_lines)
  753.    
  754.     #------------------------------------------------------#
  755.     #-------------------------------------------------------------------#
  756.     #   如果不冻结训练的话,直接设置batch_size为Unfreeze_batch_size
  757.     #-------------------------------------------------------------------#
  758.     batch_size = 8
  759.    
  760.     optimizer = torch.optim.Adam([dict(params=model.parameters(), lr = lr_init)])
  761.    
  762.     #---------------------------------------#
  763.     #   构建数据集加载器。
  764.     #---------------------------------------#
  765.     train_dataset   = YoloDataset(train_lines, input_shape, num_classes, train = True)
  766.     val_dataset     = YoloDataset(val_lines, input_shape, num_classes, train = False)
  767.    
  768.     train_dataloaders             = DataLoader(train_dataset, shuffle = True, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
  769.                                 drop_last=True, collate_fn=yolo_dataset_collate, sampler=None)
  770.     val_dataloaders         = DataLoader(val_dataset  , shuffle = True, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
  771.                                 drop_last=True, collate_fn=yolo_dataset_collate, sampler=None)
  772.    
  773.    
  774.     model.cuda(GPU_index)
  775.     model.train()
  776.     train_sum_loss = np.inf
  777.     val_sum_loss = np.inf
  778.     for epoch in range(num_epochs):
  779.         print('Epoch {}/{}'.format(epoch, num_epochs - 1))
  780.         # dt_size = len(train_dataloaders.dataset)
  781.         step = 0
  782.         epoch_loss = 0
  783.         model.train()
  784.         for x, y in train_dataloaders:
  785.             step += 1
  786.             inputs = x.to(device)
  787.             labels = y.to(device)
  788.             labels = [ann.cuda(0) for ann in labels]
  789.             
  790.             optimizer.zero_grad()
  791.             outputs = model(inputs)
  792.             
  793.             loss_value_all  = 0
  794.             for l in range(len(outputs)):
  795.                 loss_item = yolo_loss(l, outputs[l], outputs)
  796.                 loss_value_all  += loss_item
  797.             loss = loss_value_all
  798.             
  799.             loss.backward()
  800.             optimizer.step()
  801.             epoch_loss += loss.item()
  802.    
  803.             print("%d/%d,train_loss:%0.3f,train_metric:%0.3f" % (step, (dt_size - 1) // train_dataloaders.batch_size + 1, loss.item(), epoch_metric))
  804.             del x, y, inputs, labels, outputs
  805.             torch.cuda.empty_cache()
  806.         # print("epoch %d loss:%0.3f" % (epoch, epoch_loss))
  807.         if(epoch_loss < train_sum_loss):
  808.             train_sum_loss = epoch_loss
  809.             model.eval()
  810.             example = torch.rand(1, img_channel, img_size, img_size).type(torch.FloatTensor).cuda(GPU_index)
  811.             pt_model = torch.jit.trace(model, example)
  812.             pt_model.save( os.path.join(train_project_path, "Model", str(epoch) +'-Loss.pt') )
复制代码


参考:
【1】https://github.com/bubbliiiing/efficientnet-yolo3-pytorch
训练所需的各类权重可以在百度云下载。
链接: https://pan.baidu.com/s/1BQ9rIyxWiMIlUgwyb8ltVw

提取码: dp7r
VOC数据集下载地址如下,里面已经包括了训练集、测试集、验证集(与测试集一样),无需再次划分:
链接: https://pan.baidu.com/s/19Mw2u_df_nBzsC2lg20fQA

提取码: j5ge

【2】COCO-2014下载
https://blog.csdn.net/boy854456187/article/details/119277637

【3】人脸检测+口罩:(3条消息) 使用YOLOv5实现人脸口罩佩戴检测(详细)_Stick_2的博客-CSDN博客

【4】人脸检测:(3条消息) 用yolov5做人脸检测_yolov5人脸识别_noob_qing的博客-CSDN博客


回复

使用道具 举报

1294

主题

1520

帖子

110

金钱

管理员

Rank: 9Rank: 9Rank: 9

积分
22633
 楼主| 发表于 2023-6-18 10:06:31 来自手机 | 显示全部楼层
【0基础上手yolov3目标定位-哔哩哔哩】https://b23.tv/DRN1k4h
  1. '''
  2.     0,0 ------> x (width)
  3.      |
  4.      |  (Left,Top)
  5.      |      *_________
  6.      |      |         |
  7.             |         |
  8.      y      |_________|
  9.   (height)            *
  10.                 (Right,Bottom)
  11. '''
复制代码
路径;Col1,Row1,Col2,Row2,Class【Class从0开始】
  1. C:/DetecrionProjects/Image/1196770001008.jpg;396,121,504,219,0
  2. C:/DetecrionProjects/Image/1196770001009.jpg;344,88,503,215,0
  3. C:/DetecrionProjects/Image/1196770001010.jpg;359,105,510,224,0
  4. C:/DetecrionProjects/Image/1196770001011.jpg;376,86,511,225,0
  5. C:/DetecrionProjects/Image/1196770001012.jpg;268,75,435,191,0
  6. C:/DetecrionProjects/Image/1196770001014.jpg;365,120,508,224,0
  7. C:/DetecrionProjects/Image/1196770001015.jpg;314,88,501,216,0
  8. C:/DetecrionProjects/Image/1196770001016.jpg;340,102,511,222,0
复制代码

回复 支持 反对

使用道具 举报

1294

主题

1520

帖子

110

金钱

管理员

Rank: 9Rank: 9Rank: 9

积分
22633
 楼主| 发表于 2023-6-22 19:19:14 | 显示全部楼层
  1. #-----------------------------------------------------------------------#
  2. #   predict.py将单张图片预测、摄像头检测、FPS测试和目录遍历检测等功能
  3. #   整合到了一个py文件中,通过指定mode进行模式的修改。
  4. #-----------------------------------------------------------------------#
  5. import time
  6. import colorsys
  7. import os
  8. import cv2
  9. import numpy as np
  10. from PIL import Image, ImageDraw, ImageFont
  11. import torch
  12. import torch.nn as nn
  13. from torchvision.ops import nms

  14. from nets.yolo import YoloBody
  15. # from utils.utils import (cvtColor, get_anchors, get_classes, preprocess_input, resize_image)
  16. # from utils.utils_bbox import DecodeBox

  17. #---------------------------------------------------------#
  18. #   将图像转换成RGB图像,防止灰度图在预测时报错。
  19. #   代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
  20. #---------------------------------------------------------#
  21. def cvtColor(image):
  22.     if len(np.shape(image)) == 3 and np.shape(image)[2] == 3:
  23.         return image
  24.     else:
  25.         image = image.convert('RGB')
  26.         return image
  27.    
  28. #---------------------------------------------------#
  29. #   对输入图像进行resize
  30. #---------------------------------------------------#
  31. def resize_image(image, size):
  32.     iw, ih  = image.size
  33.     w, h    = size
  34.     new_image = image.resize((w, h), Image.BICUBIC)
  35.     return new_image

  36. #---------------------------------------------------#
  37. #   获得类
  38. #---------------------------------------------------#
  39. def get_classes(classes_path):
  40.     with open(classes_path, encoding='utf-8') as f:
  41.         class_names = f.readlines()
  42.     class_names = [c.strip() for c in class_names]
  43.     return class_names, len(class_names)

  44. #---------------------------------------------------#
  45. #   获得先验框
  46. #---------------------------------------------------#
  47. def get_anchors(anchors_path):
  48.     '''loads the anchors from a file'''
  49.     with open(anchors_path, encoding='utf-8') as f:
  50.         anchors = f.readline()
  51.     anchors = [float(x) for x in anchors.split(',')]
  52.     anchors = np.array(anchors).reshape(-1, 2)
  53.     return anchors, len(anchors)

  54. #---------------------------------------------------#
  55. #   获得学习率
  56. #---------------------------------------------------#
  57. def get_lr(optimizer):
  58.     for param_group in optimizer.param_groups:
  59.         return param_group['lr']

  60. def preprocess_input(image):
  61.     image /= 255.0
  62.     return image

  63. class DecodeBox():
  64.     def __init__(self, anchors, num_classes, input_shape, anchors_mask = [[6,7,8], [3,4,5], [0,1,2]]):
  65.         super(DecodeBox, self).__init__()
  66.         self.anchors        = anchors
  67.         self.num_classes    = num_classes
  68.         self.bbox_attrs     = 5 + num_classes
  69.         self.input_shape    = input_shape
  70.         #-----------------------------------------------------------#
  71.         #   13x13的特征层对应的anchor是[116,90],[156,198],[373,326]
  72.         #   26x26的特征层对应的anchor是[30,61],[62,45],[59,119]
  73.         #   52x52的特征层对应的anchor是[10,13],[16,30],[33,23]
  74.         #-----------------------------------------------------------#
  75.         self.anchors_mask   = anchors_mask

  76.     def decode_box(self, inputs):
  77.         outputs = []
  78.         for i, input in enumerate(inputs):
  79.             #-----------------------------------------------#
  80.             #   输入的input一共有三个,他们的shape分别是
  81.             #   batch_size, 255, 13, 13
  82.             #   batch_size, 255, 26, 26
  83.             #   batch_size, 255, 52, 52
  84.             #-----------------------------------------------#
  85.             batch_size      = input.size(0)
  86.             input_height    = input.size(2)
  87.             input_width     = input.size(3)

  88.             #-----------------------------------------------#
  89.             #   输入为416x416时
  90.             #   stride_h = stride_w = 32、16、8
  91.             #-----------------------------------------------#
  92.             stride_h = self.input_shape[0] / input_height
  93.             stride_w = self.input_shape[1] / input_width
  94.             #-------------------------------------------------#
  95.             #   此时获得的scaled_anchors大小是相对于特征层的
  96.             #-------------------------------------------------#
  97.             scaled_anchors = [(anchor_width / stride_w, anchor_height / stride_h) for anchor_width, anchor_height in self.anchors[self.anchors_mask[i]]]

  98.             #-----------------------------------------------#
  99.             #   输入的input一共有三个,他们的shape分别是
  100.             #   batch_size, 3, 13, 13, 85
  101.             #   batch_size, 3, 26, 26, 85
  102.             #   batch_size, 3, 52, 52, 85
  103.             #-----------------------------------------------#
  104.             prediction = input.view(batch_size, len(self.anchors_mask[i]),
  105.                                     self.bbox_attrs, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous()

  106.             #-----------------------------------------------#
  107.             #   先验框的中心位置的调整参数
  108.             #-----------------------------------------------#
  109.             x = torch.sigmoid(prediction[..., 0])  
  110.             y = torch.sigmoid(prediction[..., 1])
  111.             #-----------------------------------------------#
  112.             #   先验框的宽高调整参数
  113.             #-----------------------------------------------#
  114.             w = prediction[..., 2]
  115.             h = prediction[..., 3]
  116.             #-----------------------------------------------#
  117.             #   获得置信度,是否有物体
  118.             #-----------------------------------------------#
  119.             conf        = torch.sigmoid(prediction[..., 4])
  120.             #-----------------------------------------------#
  121.             #   种类置信度
  122.             #-----------------------------------------------#
  123.             pred_cls    = torch.sigmoid(prediction[..., 5:])

  124.             FloatTensor = torch.cuda.FloatTensor if x.is_cuda else torch.FloatTensor
  125.             LongTensor  = torch.cuda.LongTensor if x.is_cuda else torch.LongTensor

  126.             #----------------------------------------------------------#
  127.             #   生成网格,先验框中心,网格左上角
  128.             #   batch_size,3,13,13
  129.             #----------------------------------------------------------#
  130.             grid_x = torch.linspace(0, input_width - 1, input_width).repeat(input_height, 1).repeat(
  131.                 batch_size * len(self.anchors_mask[i]), 1, 1).view(x.shape).type(FloatTensor)
  132.             grid_y = torch.linspace(0, input_height - 1, input_height).repeat(input_width, 1).t().repeat(
  133.                 batch_size * len(self.anchors_mask[i]), 1, 1).view(y.shape).type(FloatTensor)

  134.             #----------------------------------------------------------#
  135.             #   按照网格格式生成先验框的宽高
  136.             #   batch_size,3,13,13
  137.             #----------------------------------------------------------#
  138.             anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0]))
  139.             anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1]))
  140.             anchor_w = anchor_w.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(w.shape)
  141.             anchor_h = anchor_h.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(h.shape)

  142.             #----------------------------------------------------------#
  143.             #   利用预测结果对先验框进行调整
  144.             #   首先调整先验框的中心,从先验框中心向右下角偏移
  145.             #   再调整先验框的宽高。
  146.             #----------------------------------------------------------#
  147.             pred_boxes          = FloatTensor(prediction[..., :4].shape)
  148.             pred_boxes[..., 0]  = x.data + grid_x
  149.             pred_boxes[..., 1]  = y.data + grid_y
  150.             pred_boxes[..., 2]  = torch.exp(w.data) * anchor_w
  151.             pred_boxes[..., 3]  = torch.exp(h.data) * anchor_h

  152.             #----------------------------------------------------------#
  153.             #   将输出结果归一化成小数的形式
  154.             #----------------------------------------------------------#
  155.             _scale = torch.Tensor([input_width, input_height, input_width, input_height]).type(FloatTensor)
  156.             output = torch.cat((pred_boxes.view(batch_size, -1, 4) / _scale,
  157.                                 conf.view(batch_size, -1, 1), pred_cls.view(batch_size, -1, self.num_classes)), -1)
  158.             outputs.append(output.data)
  159.         return outputs

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

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

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

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

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

  196.             #----------------------------------------------------------#
  197.             #   根据置信度进行预测结果的筛选
  198.             #----------------------------------------------------------#
  199.             image_pred = image_pred[conf_mask]
  200.             class_conf = class_conf[conf_mask]
  201.             class_pred = class_pred[conf_mask]
  202.             if not image_pred.size(0):
  203.                 continue
  204.             #-------------------------------------------------------------------------#
  205.             #   detections  [num_anchors, 7]
  206.             #   7的内容为:x1, y1, x2, y2, obj_conf, class_conf, class_pred
  207.             #-------------------------------------------------------------------------#
  208.             detections = torch.cat((image_pred[:, :5], class_conf.float(), class_pred.float()), 1)

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

  213.             if prediction.is_cuda:
  214.                 unique_labels = unique_labels.cuda()
  215.                 detections = detections.cuda()

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

  221.                 #------------------------------------------#
  222.                 #   使用官方自带的非极大抑制会速度更快一些!
  223.                 #------------------------------------------#
  224.                 keep = nms(
  225.                     detections_class[:, :4],
  226.                     detections_class[:, 4] * detections_class[:, 5],
  227.                     nms_thres
  228.                 )
  229.                 max_detections = detections_class[keep]

  230.                 # Add max detections to outputs
  231.                 output[i] = max_detections if output[i] is None else torch.cat((output[i], max_detections))
  232.             
  233.             if output[i] is not None:
  234.                 output[i]           = output[i].cpu().numpy()
  235.                 box_xy, box_wh      = (output[i][:, 0:2] + output[i][:, 2:4])/2, output[i][:, 2:4] - output[i][:, 0:2]
  236.                 output[i][:, :4]    = self.yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape)
  237.         return output

  238. '''
  239. 训练自己的数据集必看注释!
  240. '''
  241. class YOLO(object):
  242.     _defaults = {
  243.         #--------------------------------------------------------------------------#
  244.         #   使用自己训练好的模型进行预测一定要修改model_path和classes_path!
  245.         #   model_path指向logs文件夹下的权值文件,classes_path指向model_data下的txt
  246.         #
  247.         #   训练好后logs文件夹下存在多个权值文件,选择验证集损失较低的即可。
  248.         #   验证集损失较低不代表mAP较高,仅代表该权值在验证集上泛化性能较好。
  249.         #   如果出现shape不匹配,同时要注意训练时的model_path和classes_path参数的修改
  250.         #--------------------------------------------------------------------------#
  251.         "model_path"        : 'model_data/Valnet.pt',
  252.         "classes_path"      : 'model_data/user.txt',
  253.         #---------------------------------------------------------------------#
  254.         #   anchors_path代表先验框对应的txt文件,一般不修改。
  255.         #   anchors_mask用于帮助代码找到对应的先验框,一般不修改。
  256.         #---------------------------------------------------------------------#
  257.         "anchors_path"      : 'model_data/yolo_anchors.txt',
  258.         "anchors_mask"      : [[6, 7, 8], [3, 4, 5], [0, 1, 2]],
  259.         #---------------------------------------------------------------------#
  260.         #   输入图片的大小,必须为32的倍数。
  261.         #---------------------------------------------------------------------#
  262.         "input_shape"       : [512, 512],
  263.         #---------------------------------------------------------------------#
  264.         #   只有得分大于置信度的预测框会被保留下来
  265.         #---------------------------------------------------------------------#
  266.         "confidence"        : 0.5,
  267.         #---------------------------------------------------------------------#
  268.         #   非极大抑制所用到的nms_iou大小
  269.         #---------------------------------------------------------------------#
  270.         "nms_iou"           : 0.3,
  271.         #-------------------------------#
  272.         #   是否使用Cuda
  273.         #   没有GPU可以设置成False
  274.         #-------------------------------#
  275.         "cuda"              : True,
  276.     }

  277.     @classmethod
  278.     def get_defaults(cls, n):
  279.         if n in cls._defaults:
  280.             return cls._defaults[n]
  281.         else:
  282.             return "Unrecognized attribute name '" + n + "'"

  283.     #---------------------------------------------------#
  284.     #   初始化YOLO
  285.     #---------------------------------------------------#
  286.     def __init__(self, **kwargs):
  287.         self.__dict__.update(self._defaults)
  288.         for name, value in kwargs.items():
  289.             setattr(self, name, value)
  290.             self._defaults[name] = value
  291.             
  292.         #---------------------------------------------------#
  293.         #   获得种类和先验框的数量
  294.         #---------------------------------------------------#
  295.         self.class_names, self.num_classes  = get_classes(self.classes_path)
  296.         self.anchors, self.num_anchors      = get_anchors(self.anchors_path)
  297.         self.bbox_util                      = DecodeBox(self.anchors, self.num_classes, (self.input_shape[0], self.input_shape[1]), self.anchors_mask)

  298.         #---------------------------------------------------#
  299.         #   画框设置不同的颜色
  300.         #---------------------------------------------------#
  301.         hsv_tuples = [(x / self.num_classes, 1., 1.) for x in range(self.num_classes)]
  302.         self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
  303.         self.colors = list(map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), self.colors))
  304.         self.generate()
  305.         
  306.         # show_config(**self._defaults)

  307.     #---------------------------------------------------#
  308.     #   生成模型
  309.     #---------------------------------------------------#
  310.     def generate(self, onnx=False):
  311.         #---------------------------------------------------#
  312.         #   建立yolov3模型,载入yolov3模型的权重
  313.         #---------------------------------------------------#
  314.         self.net    = YoloBody(self.anchors_mask, self.num_classes)
  315.         device      = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  316.         # self.net.load_state_dict(torch.load(self.model_path, map_location=device))
  317.         # self.net    = self.net.eval()
  318.         print('{} model, anchors, and classes loaded.'.format(self.model_path))
  319.         # if not onnx:
  320.         #     if self.cuda:
  321.         #         self.net = nn.DataParallel(self.net)
  322.         #         self.net = self.net.cuda()
  323.         self.net = torch.jit.load(self.model_path, map_location=device)
  324.         self.net    = self.net.eval()

  325.     #---------------------------------------------------#
  326.     #   检测图片
  327.     #---------------------------------------------------#
  328.     def detect_image(self, image, crop = False, count = False):
  329.         image_shape = np.array(np.shape(image)[0:2])
  330.         #---------------------------------------------------------#
  331.         #   在这里将图像转换成RGB图像,防止灰度图在预测时报错。
  332.         #   代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
  333.         #---------------------------------------------------------#
  334.         image       = cvtColor(image)
  335.         #---------------------------------------------------------#
  336.         #   给图像增加灰条,实现不失真的resize
  337.         #   也可以直接resize进行识别
  338.         #---------------------------------------------------------#
  339.         image_data  = resize_image(image, (self.input_shape[1],self.input_shape[0]))
  340.         #---------------------------------------------------------#
  341.         #   添加上batch_size维度
  342.         #---------------------------------------------------------#
  343.         image_data  = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)

  344.         with torch.no_grad():
  345.             images = torch.from_numpy(image_data)
  346.             if self.cuda:
  347.                 images = images.cuda()
  348.             #---------------------------------------------------------#
  349.             #   将图像输入网络当中进行预测!
  350.             #---------------------------------------------------------#
  351.             outputs = self.net(images)
  352.             outputs = self.bbox_util.decode_box(outputs)
  353.             #---------------------------------------------------------#
  354.             #   将预测框进行堆叠,然后进行非极大抑制
  355.             #---------------------------------------------------------#
  356.             results = self.bbox_util.non_max_suppression(torch.cat(outputs, 1), self.num_classes, self.input_shape,
  357.                         image_shape, conf_thres = self.confidence, nms_thres = self.nms_iou)
  358.                                                    
  359.             if results[0] is None:
  360.                 return image

  361.             top_label   = np.array(results[0][:, 6], dtype = 'int32')
  362.             top_conf    = results[0][:, 4] * results[0][:, 5]
  363.             top_boxes   = results[0][:, :4]
  364.         #---------------------------------------------------------#
  365.         #   设置字体与边框厚度
  366.         #---------------------------------------------------------#
  367.         font        = ImageFont.truetype(font='model_data/simhei.ttf', size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
  368.         thickness   = int(max((image.size[0] + image.size[1]) // np.mean(self.input_shape), 1))
  369.         #---------------------------------------------------------#
  370.         #   图像绘制
  371.         #---------------------------------------------------------#
  372.         for i, c in list(enumerate(top_label)):
  373.             predicted_class = self.class_names[int(c)]
  374.             box             = top_boxes[i]
  375.             score           = top_conf[i]

  376.             top, left, bottom, right = box

  377.             top     = max(0, np.floor(top).astype('int32'))
  378.             left    = max(0, np.floor(left).astype('int32'))
  379.             bottom  = min(image.size[1], np.floor(bottom).astype('int32'))
  380.             right   = min(image.size[0], np.floor(right).astype('int32'))

  381.             label = '{} {:.2f}'.format(predicted_class, score)
  382.             draw = ImageDraw.Draw(image)
  383.             label_size = draw.textsize(label, font)
  384.             label = label.encode('utf-8')
  385.             print(label, top, left, bottom, right)
  386.             
  387.             if top - label_size[1] >= 0:
  388.                 text_origin = np.array([left, top - label_size[1]])
  389.             else:
  390.                 text_origin = np.array([left, top + 1])

  391.             for i in range(thickness):
  392.                 draw.rectangle([left + i, top + i, right - i, bottom - i], outline=self.colors[c])
  393.             draw.rectangle([tuple(text_origin), tuple(text_origin + label_size)], fill=self.colors[c])
  394.             draw.text(text_origin, str(label,'UTF-8'), fill=(0, 0, 0), font=font)
  395.             del draw

  396.         return image

  397. if __name__ == "__main__":
  398.     yolo = YOLO()
  399.     #----------------------------------------------------------------------------------------------------------#
  400.     #   mode用于指定测试的模式:
  401.     #   'predict'           表示单张图片预测,如果想对预测过程进行修改,如保存图片,截取对象等,可以先看下方详细的注释
  402.     #-------------------------------------------------------------------------#
  403.     #   crop                指定了是否在单张图片预测后对目标进行截取
  404.     #   count               指定了是否进行目标的计数
  405.     #   crop、count仅在mode='predict'时有效
  406.     #-------------------------------------------------------------------------#
  407.     crop            = False
  408.     count           = False

  409.     '''
  410.     1、如果想要进行检测完的图片的保存,利用r_image.save("img.jpg")即可保存,直接在predict.py里进行修改即可。
  411.     2、如果想要获得预测框的坐标,可以进入yolo.detect_image函数,在绘图部分读取top,left,bottom,right这四个值。
  412.     3、如果想要利用预测框截取下目标,可以进入yolo.detect_image函数,在绘图部分利用获取到的top,left,bottom,right这四个值
  413.     在原图上利用矩阵的方式进行截取。
  414.     4、如果想要在预测图上写额外的字,比如检测到的特定目标的数量,可以进入yolo.detect_image函数,在绘图部分对predicted_class进行判断,
  415.     比如判断if predicted_class == 'car': 即可判断当前目标是否为车,然后记录数量即可。利用draw.text即可写字。
  416.     '''
  417.     img = r'./img/street.jpg'
  418.     img = r'./img/1.jpg'
  419.     image = Image.open(img)

  420.     r_image = yolo.detect_image(image, crop = crop, count=count)
  421.     r_image.show()

  422.    
复制代码
算法QQ  3283892722
群智能算法链接http://halcom.cn/forum.php?mod=forumdisplay&fid=73
回复 支持 反对

使用道具 举报

1294

主题

1520

帖子

110

金钱

管理员

Rank: 9Rank: 9Rank: 9

积分
22633
 楼主| 发表于 2023-6-26 14:28:49 | 显示全部楼层
cpp,torch::jit::IValue = module.forward(input);
此时是tuple类型,而不是tensor。yolo特有的吧
  1. // 输入初始化
  2. std::vector<torch::jit::IValue> input;
  3. input.emplace_back(tensor_image);

  4. auto outputs = model.forward(input).toTuple();
  5. // 提取三个head
  6. std::vector<at::Tensor> output(3);
  7. output[0] = outputs->elements()[0].toTensor().to(at::kCPU);
  8. output[1] = outputs->elements()[1].toTensor().to(at::kCPU);
  9. output[2] = outputs->elements()[2].toTensor().to(at::kCPU);
复制代码



算法QQ  3283892722
群智能算法链接http://halcom.cn/forum.php?mod=forumdisplay&fid=73
回复 支持 反对

使用道具 举报

1294

主题

1520

帖子

110

金钱

管理员

Rank: 9Rank: 9Rank: 9

积分
22633
 楼主| 发表于 2023-6-26 15:50:08 来自手机 | 显示全部楼层
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Python|Opencv|MATLAB|Halcom.cn ( 蜀ICP备16027072号 )

GMT+8, 2024-4-24 02:42 , Processed in 0.241712 second(s), 24 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表