精华内容
下载资源
问答
  • 睿智的目标检测26——Pytorch搭建yolo3目标检测平台

    万次阅读 多人点赞 2020-04-13 15:15:43
    睿智的目标检测26——Pytorch搭建yolo3目标检测平台学习前言源码下载yolo3实现思路一、预测部分1、主题网络darknet53介绍2、从特征获取预测结果3、预测结果的解码4、在原图上进行绘制二、训练部分1、计算loss所需...

    注意事项

    yolov3网络结构图中,特征高宽最小的特征层的通道数量不对,正确的输出特征层shape为[batch_size, 13, 13, 512]。代码是正确的。

    学习前言

    一起来看看yolo3的Pytorch实现吧,顺便训练一下自己的数据。
    在这里插入图片描述

    源码下载

    https://github.com/bubbliiiing/yolo3-pytorch
    喜欢的可以点个star噢。

    yolo3实现思路

    一、预测部分

    1、主题网络darknet53介绍

    在这里插入图片描述
    YOLOv3相比于之前的yolo1和yolo2,改进较大,主要改进方向有:

    1、主干网络修改为darknet53,其重要特点是使用了残差网络Residual,darknet53中的残差卷积就是进行一次3X3、步长为2的卷积,然后保存该卷积layer,再进行一次1X1的卷积和一次3X3的卷积,并把这个结果加上layer作为最后的结果, 残差网络的特点是容易优化,并且能够通过增加相当的深度来提高准确率。其内部的残差块使用了跳跃连接,缓解了在深度神经网络中增加深度带来的梯度消失问题。

    2、darknet53的每一个卷积部分使用了特有的DarknetConv2D结构,每一次卷积的时候进行l2正则化,完成卷积后进行BatchNormalization标准化与LeakyReLU。普通的ReLU是将所有的负值都设为零,Leaky ReLU则是给所有负值赋予一个非零斜率。以数学的方式我们可以表示为
    在这里插入图片描述
    实现代码为:

    import torch
    import torch.nn as nn
    import math
    from collections import OrderedDict
    
    # 基本的darknet块
    class BasicBlock(nn.Module):
        def __init__(self, inplanes, planes):
            super(BasicBlock, self).__init__()
            self.conv1 = nn.Conv2d(inplanes, planes[0], kernel_size=1,
                                   stride=1, padding=0, bias=False)
            self.bn1 = nn.BatchNorm2d(planes[0])
            self.relu1 = nn.LeakyReLU(0.1)
            
            self.conv2 = nn.Conv2d(planes[0], planes[1], kernel_size=3,
                                   stride=1, padding=1, bias=False)
            self.bn2 = nn.BatchNorm2d(planes[1])
            self.relu2 = nn.LeakyReLU(0.1)
    
        def forward(self, x):
            residual = x
    
            out = self.conv1(x)
            out = self.bn1(out)
            out = self.relu1(out)
    
            out = self.conv2(out)
            out = self.bn2(out)
            out = self.relu2(out)
    
            out += residual
            return out
    
    
    class DarkNet(nn.Module):
        def __init__(self, layers):
            super(DarkNet, self).__init__()
            self.inplanes = 32
            self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False)
            self.bn1 = nn.BatchNorm2d(self.inplanes)
            self.relu1 = nn.LeakyReLU(0.1)
    
            self.layer1 = self._make_layer([32, 64], layers[0])
            self.layer2 = self._make_layer([64, 128], layers[1])
            self.layer3 = self._make_layer([128, 256], layers[2])
            self.layer4 = self._make_layer([256, 512], layers[3])
            self.layer5 = self._make_layer([512, 1024], layers[4])
    
            self.layers_out_filters = [64, 128, 256, 512, 1024]
    
            # 进行权值初始化
            for m in self.modules():
                if isinstance(m, nn.Conv2d):
                    n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                    m.weight.data.normal_(0, math.sqrt(2. / n))
                elif isinstance(m, nn.BatchNorm2d):
                    m.weight.data.fill_(1)
                    m.bias.data.zero_()
    
        def _make_layer(self, planes, blocks):
            layers = []
            # 下采样,步长为2,卷积核大小为3
            layers.append(("ds_conv", nn.Conv2d(self.inplanes, planes[1], kernel_size=3,
                                    stride=2, padding=1, bias=False)))
            layers.append(("ds_bn", nn.BatchNorm2d(planes[1])))
            layers.append(("ds_relu", nn.LeakyReLU(0.1)))
            # 加入darknet模块   
            self.inplanes = planes[1]
            for i in range(0, blocks):
                layers.append(("residual_{}".format(i), BasicBlock(self.inplanes, planes)))
            return nn.Sequential(OrderedDict(layers))
    
        def forward(self, x):
            x = self.conv1(x)
            x = self.bn1(x)
            x = self.relu1(x)
    
            x = self.layer1(x)
            x = self.layer2(x)
            out3 = self.layer3(x)
            out4 = self.layer4(out3)
            out5 = self.layer5(out4)
    
            return out3, out4, out5
    
    def darknet53(pretrained, **kwargs):
        model = DarkNet([1, 2, 8, 8, 4])
        if pretrained:
            if isinstance(pretrained, str):
                model.load_state_dict(torch.load(pretrained))
            else:
                raise Exception("darknet request a pretrained path. got [{}]".format(pretrained))
        return model
    

    2、从特征获取预测结果

    在这里插入图片描述
    1、在特征利用部分,yolo3提取多特征层进行目标检测,一共提取三个特征层,三个特征层位于主干部分darknet53的不同位置,分别位于中间层,中下层,底层,三个特征层的shape分别为(52,52,256)、(26,26,512)、(13,13,1024)。

    2、三个特征层进行5次卷积处理,处理完后一部分用于输出该特征层对应的预测结果一部分用于进行反卷积UmSampling2d后与其它特征层进行结合

    3、输出层的shape分别为(13,13,75),(26,26,75),(52,52,75),最后一个维度为75是因为该图是基于voc数据集的,它的类为20种,yolo3只有针对每一个特征层存在3个先验框,所以最后维度为3x25;
    如果使用的是coco训练集,类则为80种,最后的维度应该为255 = 3x85
    ,三个特征层的shape为(13,13,255),(26,26,255),(52,52,255)

    其实际情况就是,由于我们使用得是Pytorch,它的通道数默认在第一位,输入N张416x416的图片,在经过多层的运算后,会输出三个shape分别为(N,255,13,13),(N,255,26,26),(N,255,52,52)的数据,对应每个图分为13x13、26x26、52x52的网格上3个先验框的位置。
    实现代码如下:

    import torch
    import torch.nn as nn
    from collections import OrderedDict
    from nets.darknet import darknet53
    
    def conv2d(filter_in, filter_out, kernel_size):
        pad = (kernel_size - 1) // 2 if kernel_size else 0
        return nn.Sequential(OrderedDict([
            ("conv", nn.Conv2d(filter_in, filter_out, kernel_size=kernel_size, stride=1, padding=pad, bias=False)),
            ("bn", nn.BatchNorm2d(filter_out)),
            ("relu", nn.LeakyReLU(0.1)),
        ]))
    
    def make_last_layers(filters_list, in_filters, out_filter):
        m = nn.ModuleList([
            conv2d(in_filters, filters_list[0], 1),
            conv2d(filters_list[0], filters_list[1], 3),
            conv2d(filters_list[1], filters_list[0], 1),
            conv2d(filters_list[0], filters_list[1], 3),
            conv2d(filters_list[1], filters_list[0], 1),
            conv2d(filters_list[0], filters_list[1], 3),
            nn.Conv2d(filters_list[1], out_filter, kernel_size=1,
                                            stride=1, padding=0, bias=True)
        ])
        return m
    
    class YoloBody(nn.Module):
        def __init__(self, config):
            super(YoloBody, self).__init__()
            self.config = config
            #  backbone
            self.backbone = darknet53(None)
    
            out_filters = self.backbone.layers_out_filters
            #  last_layer0
            final_out_filter0 = len(config["yolo"]["anchors"][0]) * (5 + config["yolo"]["classes"])
            self.last_layer0 = make_last_layers([512, 1024], out_filters[-1], final_out_filter0)
    
            #  embedding1
            final_out_filter1 = len(config["yolo"]["anchors"][1]) * (5 + config["yolo"]["classes"])
            self.last_layer1_conv = conv2d(512, 256, 1)
            self.last_layer1_upsample = nn.Upsample(scale_factor=2, mode='nearest')
            self.last_layer1 = make_last_layers([256, 512], out_filters[-2] + 256, final_out_filter1)
    
            #  embedding2
            final_out_filter2 = len(config["yolo"]["anchors"][2]) * (5 + config["yolo"]["classes"])
            self.last_layer2_conv = conv2d(256, 128, 1)
            self.last_layer2_upsample = nn.Upsample(scale_factor=2, mode='nearest')
            self.last_layer2 = make_last_layers([128, 256], out_filters[-3] + 128, final_out_filter2)
    
    
        def forward(self, x):
            def _branch(last_layer, layer_in):
                for i, e in enumerate(last_layer):
                    layer_in = e(layer_in)
                    if i == 4:
                        out_branch = layer_in
                return layer_in, out_branch
            #  backbone
            x2, x1, x0 = self.backbone(x)
            #  yolo branch 0
            out0, out0_branch = _branch(self.last_layer0, x0)
    
            #  yolo branch 1
            x1_in = self.last_layer1_conv(out0_branch)
            x1_in = self.last_layer1_upsample(x1_in)
            x1_in = torch.cat([x1_in, x1], 1)
            out1, out1_branch = _branch(self.last_layer1, x1_in)
    
            #  yolo branch 2
            x2_in = self.last_layer2_conv(out1_branch)
            x2_in = self.last_layer2_upsample(x2_in)
            x2_in = torch.cat([x2_in, x2], 1)
            out2, _ = _branch(self.last_layer2, x2_in)
            return out0, out1, out2
    

    3、预测结果的解码

    由第二步我们可以获得三个特征层的预测结果,shape分别为(N,255,13,13),(N,255,26,26),(N,255,52,52)的数据,对应每个图分为13x13、26x26、52x52的网格上3个预测框的位置。

    但是这个预测结果并不对应着最终的预测框在图片上的位置,还需要解码才可以完成。

    此处要讲一下yolo3的预测原理,yolo3的3个特征层分别将整幅图分为13x13、26x26、52x52的网格,每个网络点负责一个区域的检测。

    我们知道特征层的预测结果对应着三个预测框的位置,我们先将其reshape一下,其结果为(N,3,85,13,13,3,85),(N,3,85,26,26),(N,3,85,52,52)。

    维度中的85包含了4+1+80,分别代表x_offset、y_offset、h和w、置信度、分类结果。

    yolo3的解码过程就是将每个网格点加上它对应的x_offset和y_offset,加完后的结果就是预测框的中心,然后再利用 先验框和h、w结合 计算出预测框的长和宽。这样就能得到整个预测框的位置了。

    在这里插入图片描述
    当然得到最终的预测结构后还要进行得分排序与非极大抑制筛选
    这一部分基本上是所有目标检测通用的部分。不过该项目的处理方式与其它项目不同。其对于每一个类进行判别。
    1、取出每一类得分大于self.obj_threshold的框和得分。
    2、利用框的位置和得分进行非极大抑制。

    实现代码如下

    class DecodeBox(nn.Module):
        def __init__(self, anchors, num_classes, img_size):
            super(DecodeBox, self).__init__()
            self.anchors = anchors
            self.num_anchors = len(anchors)
            self.num_classes = num_classes
            self.bbox_attrs = 5 + num_classes
            self.img_size = img_size
    
        def forward(self, input):
            batch_size = input.size(0)
            input_height = input.size(2)
            input_width = input.size(3)
    
            # 计算步长
            stride_h = self.img_size[1] / input_height
            stride_w = self.img_size[0] / input_width
            # 归一到特征层上
            scaled_anchors = [(anchor_width / stride_w, anchor_height / stride_h) for anchor_width, anchor_height in self.anchors]
    
            # 对预测结果进行resize
            prediction = input.view(batch_size, self.num_anchors,
                                    self.bbox_attrs, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous()
    
            # 先验框的中心位置的调整参数
            x = torch.sigmoid(prediction[..., 0])  
            y = torch.sigmoid(prediction[..., 1])
            # 先验框的宽高调整参数
            w = prediction[..., 2]  # Width
            h = prediction[..., 3]  # Height
    
            # 获得置信度,是否有物体
            conf = torch.sigmoid(prediction[..., 4])
            # 种类置信度
            pred_cls = torch.sigmoid(prediction[..., 5:])  # Cls pred.
    
            FloatTensor = torch.cuda.FloatTensor if x.is_cuda else torch.FloatTensor
            LongTensor = torch.cuda.LongTensor if x.is_cuda else torch.LongTensor
    
            # 生成网格,先验框中心,网格左上角
            grid_x = torch.linspace(0, input_width - 1, input_width).repeat(input_width, 1).repeat(
                batch_size * self.num_anchors, 1, 1).view(x.shape).type(FloatTensor)
            grid_y = torch.linspace(0, input_height - 1, input_height).repeat(input_height, 1).t().repeat(
                batch_size * self.num_anchors, 1, 1).view(y.shape).type(FloatTensor)
    
            # 生成先验框的宽高
            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
    
            # 用于将输出调整为相对于416x416的大小
            _scale = torch.Tensor([stride_w, stride_h] * 2).type(FloatTensor)
            output = torch.cat((pred_boxes.view(batch_size, -1, 4) * _scale,
                                conf.view(batch_size, -1, 1), pred_cls.view(batch_size, -1, self.num_classes)), -1)
            return output.data
    

    4、在原图上进行绘制

    通过第三步,我们可以获得预测框在原图上的位置,而且这些预测框都是经过筛选的。这些筛选后的框可以直接绘制在图片上,就可以获得结果了。

    二、训练部分

    1、计算loss所需参数

    在计算loss的时候,实际上是pred和target之间的对比:
    pred就是网络的预测结果。
    target就是网络的真实框情况。

    2、pred是什么

    对于yolo3的模型来说,网络最后输出的内容就是三个特征层每个网格点对应的预测框及其种类,即三个特征层分别对应着图片被分为不同size的网格后,每个网格点上三个先验框对应的位置、置信度及其种类。

    输出层的shape分别为(13,13,75),(26,26,75),(52,52,75),最后一个维度为75是因为是基于voc数据集的,它的类为20种,yolo3只有针对每一个特征层存在3个先验框,所以最后维度为3x25;
    如果使用的是coco训练集,类则为80种,最后的维度应该为255 = 3x85
    ,三个特征层的shape为(13,13,255),(26,26,255),(52,52,255)

    现在的y_pre还是没有解码的,解码了之后才是真实图像上的情况。

    3、target是什么。

    target就是一个真实图像中,真实框的情况。
    第一个维度是batch_size,第二个维度是每一张图片里面真实框的数量,第三个维度内部是真实框的信息,包括位置以及种类。

    4、loss的计算过程

    拿到pred和target后,不可以简单的减一下作为对比,需要进行如下步骤。

    1. 判断真实框在图片中的位置,判断其属于哪一个网格点去检测。
    2. 判断真实框和哪个先验框重合程度最高。
    3. 计算该网格点应该有怎么样的预测结果才能获得真实框
    4. 对所有真实框进行如上处理。
    5. 获得网络应该有的预测结果,将其与实际的预测结果对比。
    from random import shuffle
    import numpy as np
    import torch
    import torch.nn as nn
    import math
    import torch.nn.functional as F
    from matplotlib.colors import rgb_to_hsv, hsv_to_rgb
    from PIL import Image
    from utils.utils import bbox_iou
    
    def clip_by_tensor(t,t_min,t_max):
        t=t.float()
     
        result = (t >= t_min).float() * t + (t < t_min).float() * t_min
        result = (result <= t_max).float() * result + (result > t_max).float() * t_max
        return result
    
    def MSELoss(pred,target):
        return (pred-target)**2
    
    def BCELoss(pred,target):
        epsilon = 1e-7
        pred = clip_by_tensor(pred, epsilon, 1.0 - epsilon)
        output = -target * torch.log(pred) - (1.0 - target) * torch.log(1.0 - pred)
        return output
    
    class YOLOLoss(nn.Module):
        def __init__(self, anchors, num_classes, img_size):
            super(YOLOLoss, self).__init__()
            self.anchors = anchors
            self.num_anchors = len(anchors)
            self.num_classes = num_classes
            self.bbox_attrs = 5 + num_classes
            self.img_size = img_size
    
            self.ignore_threshold = 0.5
            self.lambda_xy = 1.0
            self.lambda_wh = 1.0
            self.lambda_conf = 1.0
            self.lambda_cls = 1.0
    
        def forward(self, input, targets=None):
            # 一共多少张图片
            bs = input.size(0)
            # 特征层的高
            in_h = input.size(2)
            # 特征层的宽
            in_w = input.size(3)
            # 计算步长
            stride_h = self.img_size[1] / in_h
            stride_w = self.img_size[0] / in_w
            # 把先验框的尺寸调整成特征层大小的形式
            scaled_anchors = [(a_w / stride_w, a_h / stride_h) for a_w, a_h in self.anchors]
            # reshape
            prediction = input.view(bs, int(self.num_anchors/3),
                                    self.bbox_attrs, in_h, in_w).permute(0, 1, 3, 4, 2).contiguous()
            
            # 对prediction预测进行调整
            x = torch.sigmoid(prediction[..., 0])  # Center x
            y = torch.sigmoid(prediction[..., 1])  # Center y
            w = prediction[..., 2]  # Width
            h = prediction[..., 3]  # Height
            conf = torch.sigmoid(prediction[..., 4])  # Conf
            pred_cls = torch.sigmoid(prediction[..., 5:])  # Cls pred.
    
            # 找到哪些先验框内部包含物体
            mask, noobj_mask, tx, ty, tw, th, tconf, tcls, box_loss_scale_x, box_loss_scale_y =\
                                                                                self.get_target(targets, scaled_anchors,
                                                                                                in_w, in_h,
                                                                                                self.ignore_threshold)
    
            noobj_mask = self.get_ignore(prediction, targets, scaled_anchors, in_w, in_h, noobj_mask)
    
            box_loss_scale_x = (2-box_loss_scale_x).cuda()
            box_loss_scale_y = (2-box_loss_scale_y).cuda()
            box_loss_scale = box_loss_scale_x*box_loss_scale_y
            mask, noobj_mask = mask.cuda(), noobj_mask.cuda()
            tx, ty, tw, th = tx.cuda(), ty.cuda(), tw.cuda(), th.cuda()
            tconf, tcls = tconf.cuda(), tcls.cuda()
            #  losses.
            loss_x = torch.sum(BCELoss(x, tx) / bs * box_loss_scale * mask)
            loss_y = torch.sum(BCELoss(y, ty) / bs * box_loss_scale * mask)
            loss_w = torch.sum(MSELoss(w, tw) / bs * 0.5 * box_loss_scale * mask)
            loss_h = torch.sum(MSELoss(h, th) / bs * 0.5 * box_loss_scale * mask)
    
            loss_conf = torch.sum(BCELoss(conf, mask) * mask / bs) + \
                        torch.sum(BCELoss(conf, mask) * noobj_mask / bs)
                        
            loss_cls = torch.sum(BCELoss(pred_cls[mask == 1], tcls[mask == 1])/bs)
    
            loss = loss_x * self.lambda_xy + loss_y * self.lambda_xy + \
                    loss_w * self.lambda_wh + loss_h * self.lambda_wh + \
                    loss_conf * self.lambda_conf + loss_cls * self.lambda_cls
            # print(loss, loss_x.item() + loss_y.item(), loss_w.item() + loss_h.item(), 
            #         loss_conf.item(), loss_cls.item(), \
            #         torch.sum(mask),torch.sum(noobj_mask))
            return loss, loss_x.item(), loss_y.item(), loss_w.item(), \
                    loss_h.item(), loss_conf.item(), loss_cls.item()
    
        def get_target(self, target, anchors, in_w, in_h, ignore_threshold):
            # 计算一共有多少张图片
            bs = len(target)
            # 获得先验框
            anchor_index = [[0,1,2],[3,4,5],[6,7,8]][[13,26,52].index(in_w)]
            subtract_index = [0,3,6][[13,26,52].index(in_w)]
            # 创建全是0或者全是1的阵列
            mask = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            noobj_mask = torch.ones(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
    
            tx = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            ty = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            tw = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            th = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            tconf = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            tcls = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, self.num_classes, requires_grad=False)
    
            box_loss_scale_x = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            box_loss_scale_y = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            for b in range(bs):
                for t in range(target[b].shape[0]):
                    # 计算出在特征层上的点位
                    gx = target[b][t, 0] * in_w
                    gy = target[b][t, 1] * in_h
                    gw = target[b][t, 2] * in_w
                    gh = target[b][t, 3] * in_h
    
                    # 计算出属于哪个网格
                    gi = int(gx)
                    gj = int(gy)
    
                    # 计算真实框的位置
                    gt_box = torch.FloatTensor(np.array([0, 0, gw, gh])).unsqueeze(0)
                    
                    # 计算出所有先验框的位置
                    anchor_shapes = torch.FloatTensor(np.concatenate((np.zeros((self.num_anchors, 2)),
                                                                      np.array(anchors)), 1))
                    # 计算重合程度
                    anch_ious = bbox_iou(gt_box, anchor_shapes)
                   
                    # Find the best matching anchor box
                    best_n = np.argmax(anch_ious)
                    if best_n not in anchor_index:
                        continue
                    # Masks
                    if (gj < in_h) and (gi < in_w):
                        best_n = best_n - subtract_index
                        # 判定哪些先验框内部真实的存在物体
                        noobj_mask[b, best_n, gj, gi] = 0
                        mask[b, best_n, gj, gi] = 1
                        # 计算先验框中心调整参数
                        tx[b, best_n, gj, gi] = gx - gi
                        ty[b, best_n, gj, gi] = gy - gj
                        # 计算先验框宽高调整参数
                        tw[b, best_n, gj, gi] = math.log(gw / anchors[best_n+subtract_index][0])
                        th[b, best_n, gj, gi] = math.log(gh / anchors[best_n+subtract_index][1])
                        # 用于获得xywh的比例
                        box_loss_scale_x[b, best_n, gj, gi] = target[b][t, 2]
                        box_loss_scale_y[b, best_n, gj, gi] = target[b][t, 3]
                        # 物体置信度
                        tconf[b, best_n, gj, gi] = 1
                        # 种类
                        tcls[b, best_n, gj, gi, int(target[b][t, 4])] = 1
                    else:
                        print('Step {0} out of bound'.format(b))
                        print('gj: {0}, height: {1} | gi: {2}, width: {3}'.format(gj, in_h, gi, in_w))
                        continue
    
            return mask, noobj_mask, tx, ty, tw, th, tconf, tcls, box_loss_scale_x, box_loss_scale_y
    
        def get_ignore(self,prediction,target,scaled_anchors,in_w, in_h,noobj_mask):
            bs = len(target)
            anchor_index = [[0,1,2],[3,4,5],[6,7,8]][[13,26,52].index(in_w)]
            scaled_anchors = np.array(scaled_anchors)[anchor_index]
            # print(scaled_anchors)
            # 先验框的中心位置的调整参数
            x_all = torch.sigmoid(prediction[..., 0])  
            y_all = torch.sigmoid(prediction[..., 1])
            # 先验框的宽高调整参数
            w_all = prediction[..., 2]  # Width
            h_all = prediction[..., 3]  # Height
            for i in range(bs):
                x = x_all[i]
                y = y_all[i]
                w = w_all[i]
                h = h_all[i]
    
                FloatTensor = torch.cuda.FloatTensor if x.is_cuda else torch.FloatTensor
                LongTensor = torch.cuda.LongTensor if x.is_cuda else torch.LongTensor
    
                # 生成网格,先验框中心,网格左上角
                grid_x = torch.linspace(0, in_w - 1, in_w).repeat(in_w, 1).repeat(
                    int(self.num_anchors/3), 1, 1).view(x.shape).type(FloatTensor)
                grid_y = torch.linspace(0, in_h - 1, in_h).repeat(in_h, 1).t().repeat(
                    int(self.num_anchors/3), 1, 1).view(y.shape).type(FloatTensor)
    
                # 生成先验框的宽高
                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(1, 1, in_h * in_w).view(w.shape)
                anchor_h = anchor_h.repeat(1, 1, in_h * in_w).view(h.shape)
                
                # 计算调整后的先验框中心与宽高
                pred_boxes = torch.FloatTensor(prediction[0][..., :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
    
                pred_boxes = pred_boxes.view(-1, 4)
    
                for t in range(target[i].shape[0]):
                    gx = target[i][t, 0] * in_w
                    gy = target[i][t, 1] * in_h
                    gw = target[i][t, 2] * in_w
                    gh = target[i][t, 3] * in_h
                    gt_box = torch.FloatTensor(np.array([gx, gy, gw, gh])).unsqueeze(0)
    
                    anch_ious = bbox_iou(gt_box, pred_boxes, x1y1x2y2=False)
                    anch_ious = anch_ious.view(x.size())
                    noobj_mask[i][anch_ious>self.ignore_threshold] = 0
                    # print(torch.max(anch_ious))
            return noobj_mask
    

    训练自己的yolo3模型

    yolo3整体的文件夹构架如下:
    在这里插入图片描述
    本文使用VOC格式进行训练。
    训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。
    在这里插入图片描述
    训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。
    在这里插入图片描述
    在训练前利用voc2yolo3.py文件生成对应的txt。
    在这里插入图片描述
    再运行根目录下的voc_annotation.py,运行前需要将classes改成你自己的classes。

    classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
    

    在这里插入图片描述
    就会生成对应的2007_train.txt,每一行对应其图片位置及其真实框的位置。
    在这里插入图片描述
    在训练前需要修改model_data里面的voc_classes.txt文件,需要将classes改成你自己的classes。同时还需要修改utils/config.py文件,修改内部的Num_Classes变成所分的种类的数量。
    在这里插入图片描述
    运行train.py即可开始训练。
    在这里插入图片描述

    展开全文
  • 睿智的目标检测11——Keras搭建yolo3目标检测平台

    万次阅读 多人点赞 2019-11-27 17:33:51
    睿智的目标检测11——Keras搭建yolo3目标检测平台学习前言yolo3实现思路一、预测部分1、主题网络darknet53介绍2、从特征获取预测结果3、预测结果的解码4、在原图上进行绘制二、训练部分计算loss所需参数1、y_pre2、y...

    学习前言

    一起来看看yolo3的keras实现吧,顺便训练一下自己的数据。
    在这里插入图片描述

    源码下载

    https://github.com/bubbliiiing/yolo3-keras
    喜欢的可以点个star噢。

    yolo3实现思路

    一、预测部分

    1、主题网络darknet53介绍

    在这里插入图片描述
    YOLOv3相比于之前的yolo1和yolo2,改进较大,主要改进方向有:

    1、主干网络修改为darknet53,其重要特点是使用了残差网络Residual,darknet53中的残差卷积就是进行一次3X3、步长为2的卷积,然后保存该卷积layer,再进行一次1X1的卷积和一次3X3的卷积,并把这个结果加上layer作为最后的结果, 残差网络的特点是容易优化,并且能够通过增加相当的深度来提高准确率。其内部的残差块使用了跳跃连接,缓解了在深度神经网络中增加深度带来的梯度消失问题。

    2、darknet53的每一个卷积部分使用了特有的DarknetConv2D结构,每一次卷积的时候进行l2正则化,完成卷积后进行BatchNormalization标准化与LeakyReLU。普通的ReLU是将所有的负值都设为零,Leaky ReLU则是给所有负值赋予一个非零斜率。以数学的方式我们可以表示为
    在这里插入图片描述
    实现代码为:

    from functools import wraps
    from keras.layers import Conv2D, Add, ZeroPadding2D, UpSampling2D, Concatenate, MaxPooling2D
    from keras.layers.advanced_activations import LeakyReLU
    from keras.layers.normalization import BatchNormalization
    from keras.regularizers import l2
    from utils.utils import compose
    
    
    #--------------------------------------------------#
    #   单次卷积
    #--------------------------------------------------#
    @wraps(Conv2D)
    def DarknetConv2D(*args, **kwargs):
        darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)}
        darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same'
        darknet_conv_kwargs.update(kwargs)
        return Conv2D(*args, **darknet_conv_kwargs)
    
    #---------------------------------------------------#
    #   卷积块
    #   DarknetConv2D + BatchNormalization + LeakyReLU
    #---------------------------------------------------#
    def DarknetConv2D_BN_Leaky(*args, **kwargs):
        no_bias_kwargs = {'use_bias': False}
        no_bias_kwargs.update(kwargs)
        return compose( 
            DarknetConv2D(*args, **no_bias_kwargs),
            BatchNormalization(),
            LeakyReLU(alpha=0.1))
    
    #---------------------------------------------------#
    #   卷积块
    #   DarknetConv2D + BatchNormalization + LeakyReLU
    #---------------------------------------------------#
    def resblock_body(x, num_filters, num_blocks):
        x = ZeroPadding2D(((1,0),(1,0)))(x)
        x = DarknetConv2D_BN_Leaky(num_filters, (3,3), strides=(2,2))(x)
        for i in range(num_blocks):
            y = DarknetConv2D_BN_Leaky(num_filters//2, (1,1))(x)
            y = DarknetConv2D_BN_Leaky(num_filters, (3,3))(y)
            x = Add()([x,y])
        return x
    
    #---------------------------------------------------#
    #   darknet53 的主体部分
    #---------------------------------------------------#
    def darknet_body(x):
        x = DarknetConv2D_BN_Leaky(32, (3,3))(x)
        x = resblock_body(x, 64, 1)
        x = resblock_body(x, 128, 2)
        x = resblock_body(x, 256, 8)
        feat1 = x
        x = resblock_body(x, 512, 8)
        feat2 = x
        x = resblock_body(x, 1024, 4)
        feat3 = x
        return feat1,feat2,feat3
    

    2、从特征获取预测结果

    在这里插入图片描述
    1、在特征利用部分,yolo3提取多特征层进行目标检测,一共提取三个特征层,三个特征层位于主干部分darknet53的不同位置,分别位于中间层,中下层,底层,三个特征层的shape分别为(52,52,256)、(26,26,512)、(13,13,1024)。

    2、三个特征层进行5次卷积处理,处理完后一部分用于输出该特征层对应的预测结果一部分用于进行反卷积UmSampling2d后与其它特征层进行结合

    3、输出层的shape分别为(13,13,75),(26,26,75),(52,52,75),最后一个维度为75是因为该图是基于voc数据集的,它的类为20种,yolo3只有针对每一个特征层存在3个先验框,所以最后维度为3x25;
    如果使用的是coco训练集,类则为80种,最后的维度应该为255 = 3x85
    ,三个特征层的shape为(13,13,255),(26,26,255),(52,52,255)

    其实际情况就是,输入N张416x416的图片,在经过多层的运算后,会输出三个shape分别为(N,13,13,255),(N,26,26,255),(N,52,52,255)的数据,对应每个图分为13x13、26x26、52x52的网格上3个先验框的位置。
    实现代码如下:

    from functools import wraps
    
    import numpy as np
    import tensorflow as tf
    from keras import backend as K
    from keras.layers import Conv2D, Add, ZeroPadding2D, UpSampling2D, Concatenate, MaxPooling2D
    from keras.layers.advanced_activations import LeakyReLU
    from keras.layers.normalization import BatchNormalization
    from keras.models import Model
    from keras.regularizers import l2
    from nets.darknet53 import darknet_body
    from utils.utils import compose
    
    
    #--------------------------------------------------#
    #   单次卷积
    #--------------------------------------------------#
    @wraps(Conv2D)
    def DarknetConv2D(*args, **kwargs):
        darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)}
        darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same'
        darknet_conv_kwargs.update(kwargs)
        return Conv2D(*args, **darknet_conv_kwargs)
    
    #---------------------------------------------------#
    #   卷积块
    #   DarknetConv2D + BatchNormalization + LeakyReLU
    #---------------------------------------------------#
    def DarknetConv2D_BN_Leaky(*args, **kwargs):
        no_bias_kwargs = {'use_bias': False}
        no_bias_kwargs.update(kwargs)
        return compose( 
            DarknetConv2D(*args, **no_bias_kwargs),
            BatchNormalization(),
            LeakyReLU(alpha=0.1))
    
    #---------------------------------------------------#
    #   特征层->最后的输出
    #---------------------------------------------------#
    def make_last_layers(x, num_filters, out_filters):
        # 五次卷积
        x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)
        x = DarknetConv2D_BN_Leaky(num_filters*2, (3,3))(x)
        x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)
        x = DarknetConv2D_BN_Leaky(num_filters*2, (3,3))(x)
        x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)
    
        # 将最后的通道数调整为outfilter
        y = DarknetConv2D_BN_Leaky(num_filters*2, (3,3))(x)
        y = DarknetConv2D(out_filters, (1,1))(y)
                
        return x, y
    
    #---------------------------------------------------#
    #   特征层->最后的输出
    #---------------------------------------------------#
    def yolo_body(inputs, num_anchors, num_classes):
        # 生成darknet53的主干模型
        feat1,feat2,feat3 = darknet_body(inputs)
        darknet = Model(inputs, feat3)
    
        # 第一个特征层
        # y1=(batch_size,13,13,3,85)
        x, y1 = make_last_layers(darknet.output, 512, num_anchors*(num_classes+5))
    
        x = compose(
                DarknetConv2D_BN_Leaky(256, (1,1)),
                UpSampling2D(2))(x)
        x = Concatenate()([x,feat2])
        # 第二个特征层
        # y2=(batch_size,26,26,3,85)
        x, y2 = make_last_layers(x, 256, num_anchors*(num_classes+5))
    
        x = compose(
                DarknetConv2D_BN_Leaky(128, (1,1)),
                UpSampling2D(2))(x)
        x = Concatenate()([x,feat1])
        # 第三个特征层
        # y3=(batch_size,52,52,3,85)
        x, y3 = make_last_layers(x, 128, num_anchors*(num_classes+5))
    
        return Model(inputs, [y1,y2,y3])
    

    3、预测结果的解码

    由第二步我们可以获得三个特征层的预测结果,shape分别为(N,13,13,255),(N,26,26,255),(N,52,52,255)的数据,对应每个图分为13x13、26x26、52x52的网格上3个预测框的位置。

    但是这个预测结果并不对应着最终的预测框在图片上的位置,还需要解码才可以完成。

    此处要讲一下yolo3的预测原理,yolo3的3个特征层分别将整幅图分为13x13、26x26、52x52的网格,每个网络点负责一个区域的检测。

    我们知道特征层的预测结果对应着三个预测框的位置,我们先将其reshape一下,其结果为(N,13,13,3,85),(N,26,26,3,85),(N,52,52,3,85)。

    最后一个维度中的85包含了4+1+80,分别代表x_offset、y_offset、h和w、置信度、分类结果。

    yolo3的解码过程就是将每个网格点加上它对应的x_offset和y_offset,加完后的结果就是预测框的中心,然后再利用 先验框和h、w结合 计算出预测框的长和宽。这样就能得到整个预测框的位置了。

    在这里插入图片描述
    当然得到最终的预测结构后还要进行得分排序与非极大抑制筛选
    这一部分基本上是所有目标检测通用的部分。不过该项目的处理方式与其它项目不同。其对于每一个类进行判别。
    1、取出每一类得分大于self.obj_threshold的框和得分。
    2、利用框的位置和得分进行非极大抑制。

    实现代码如下,当调用yolo_eval时,就会对每个特征层进行解码:

    #---------------------------------------------------#
    #   将预测值的每个特征层调成真实值
    #---------------------------------------------------#
    def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
        num_anchors = len(anchors)
        # [1, 1, 1, num_anchors, 2]
        anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])
    
        # 获得x,y的网格
        # (13, 13, 1, 2)
        grid_shape = K.shape(feats)[1:3] # height, width
        grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),
            [1, grid_shape[1], 1, 1])
        grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),
            [grid_shape[0], 1, 1, 1])
        grid = K.concatenate([grid_x, grid_y])
        grid = K.cast(grid, K.dtype(feats))
    
        # (batch_size,13,13,3,85)
        feats = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])
    
        # 将预测值调成真实值
        # box_xy对应框的中心点
        # box_wh对应框的宽和高
        box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
        box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
        box_confidence = K.sigmoid(feats[..., 4:5])
        box_class_probs = K.sigmoid(feats[..., 5:])
    
        # 在计算loss的时候返回如下参数
        if calc_loss == True:
            return grid, feats, box_xy, box_wh
        return box_xy, box_wh, box_confidence, box_class_probs
    
    #---------------------------------------------------#
    #   对box进行调整,使其符合真实图片的样子
    #---------------------------------------------------#
    def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape):
        box_yx = box_xy[..., ::-1]
        box_hw = box_wh[..., ::-1]
        
        input_shape = K.cast(input_shape, K.dtype(box_yx))
        image_shape = K.cast(image_shape, K.dtype(box_yx))
    
        new_shape = K.round(image_shape * K.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 =  K.concatenate([
            box_mins[..., 0:1],  # y_min
            box_mins[..., 1:2],  # x_min
            box_maxes[..., 0:1],  # y_max
            box_maxes[..., 1:2]  # x_max
        ])
    
        boxes *= K.concatenate([image_shape, image_shape])
        return boxes
    
    #---------------------------------------------------#
    #   获取每个box和它的得分
    #---------------------------------------------------#
    def yolo_boxes_and_scores(feats, anchors, num_classes, input_shape, image_shape):
        # 将预测值调成真实值
        # box_xy对应框的中心点
        # box_wh对应框的宽和高
        # -1,13,13,3,2; -1,13,13,3,2; -1,13,13,3,1; -1,13,13,3,80
        box_xy, box_wh, box_confidence, box_class_probs = yolo_head(feats, anchors, num_classes, input_shape)
        # 将box_xy、和box_wh调节成y_min,y_max,xmin,xmax
        boxes = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape)
        # 获得得分和box
        boxes = K.reshape(boxes, [-1, 4])
        box_scores = box_confidence * box_class_probs
        box_scores = K.reshape(box_scores, [-1, num_classes])
        return boxes, box_scores
    
    #---------------------------------------------------#
    #   图片预测
    #---------------------------------------------------#
    def yolo_eval(yolo_outputs,
                  anchors,
                  num_classes,
                  image_shape,
                  max_boxes=20,
                  score_threshold=.6,
                  iou_threshold=.5):
        # 获得特征层的数量
        num_layers = len(yolo_outputs)
        # 特征层1对应的anchor是678
        # 特征层2对应的anchor是345
        # 特征层3对应的anchor是012
        anchor_mask = [[6,7,8], [3,4,5], [0,1,2]]
        
        input_shape = K.shape(yolo_outputs[0])[1:3] * 32
        boxes = []
        box_scores = []
        # 对每个特征层进行处理
        for l in range(num_layers):
            _boxes, _box_scores = yolo_boxes_and_scores(yolo_outputs[l], anchors[anchor_mask[l]], num_classes, input_shape, image_shape)
            boxes.append(_boxes)
            box_scores.append(_box_scores)
        # 将每个特征层的结果进行堆叠
        boxes = K.concatenate(boxes, axis=0)
        box_scores = K.concatenate(box_scores, axis=0)
    
        mask = box_scores >= score_threshold
        max_boxes_tensor = K.constant(max_boxes, dtype='int32')
        boxes_ = []
        scores_ = []
        classes_ = []
        for c in range(num_classes):
            # 取出所有box_scores >= score_threshold的框,和成绩
            class_boxes = tf.boolean_mask(boxes, mask[:, c])
            class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c])
    
            # 非极大抑制,去掉box重合程度高的那一些
            nms_index = tf.image.non_max_suppression(
                class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold)
    
            # 获取非极大抑制后的结果
            # 下列三个分别是
            # 框的位置,得分与种类
            class_boxes = K.gather(class_boxes, nms_index)
            class_box_scores = K.gather(class_box_scores, nms_index)
            classes = K.ones_like(class_box_scores, 'int32') * c
            boxes_.append(class_boxes)
            scores_.append(class_box_scores)
            classes_.append(classes)
        boxes_ = K.concatenate(boxes_, axis=0)
        scores_ = K.concatenate(scores_, axis=0)
        classes_ = K.concatenate(classes_, axis=0)
    
        return boxes_, scores_, classes_
    

    4、在原图上进行绘制

    通过第三步,我们可以获得预测框在原图上的位置,而且这些预测框都是经过筛选的。这些筛选后的框可以直接绘制在图片上,就可以获得结果了。

    二、训练部分

    1、计算loss所需参数

    在计算loss的时候,实际上是y_pre和y_true之间的对比:
    y_pre就是一幅图像经过网络之后的输出,内部含有三个特征层的内容;其需要解码才能够在图上作画
    y_true就是一个真实图像中,它的每个真实框对应的(13,13)、(26,26)、(52,52)网格上的偏移位置、长宽与种类。其仍需要编码才能与y_pred的结构一致
    实际上y_pre和y_true内容的shape都是
    (batch_size,13,13,3,85)
    (batch_size,26,26,3,85)
    (batch_size,52,52,3,85)

    2、y_pre是什么

    对于yolo3的模型来说,网络最后输出的内容就是三个特征层每个网格点对应的预测框及其种类,即三个特征层分别对应着图片被分为不同size的网格后,每个网格点上三个先验框对应的位置、置信度及其种类。
    对于输出的y1、y2、y3而言,[…, : 2]指的是相对于每个网格点的偏移量,[…, 2: 4]指的是宽和高,[…, 4: 5]指的是该框的置信度,[…, 5: ]指的是每个种类的预测概率。
    现在的y_pre还是没有解码的,解码了之后才是真实图像上的情况。

    3、y_true是什么。

    y_true就是一个真实图像中,它的每个真实框对应的(13,13)、(26,26)、(52,52)网格上的偏移位置、长宽与种类。其仍需要编码才能与y_pred的结构一致
    在yolo3中,其使用了一个专门的函数用于处理读取进来的图片的框的真实情况。

    def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
    

    其输入为:
    true_boxes:shape为(m, T, 5)代表m张图T个框的x_min、y_min、x_max、y_max、class_id。
    input_shape:输入的形状,此处为416、416
    anchors:代表9个先验框的大小
    num_classes:种类的数量。

    其实对真实框的处理是将真实框转化成图片中相对网格的xyhw,步骤如下:
    1、取框的真实值,获取其框的中心及其宽高,除去input_shape变成比例的模式。
    2、建立全为0的y_true,y_true是一个列表,包含三个特征层,shape分别为(m,13,13,3,85),(m,26,26,3,85),(m,52,52,3,85)。
    3、对每一张图片处理,将每一张图片中的真实框的wh和先验框的wh对比,计算IOU值,选取其中IOU最高的一个,得到其所属特征层及其网格点的位置,在对应的y_true中将内容进行保存。

    for t, n in enumerate(best_anchor):
        for l in range(num_layers):
            if n in anchor_mask[l]:
    
                # 计算该目标在第l个特征层所处网格的位置
                i = np.floor(true_boxes[b,t,0]*grid_shapes[l][1]).astype('int32')
                j = np.floor(true_boxes[b,t,1]*grid_shapes[l][0]).astype('int32')
    
                # 找到best_anchor索引的索引
                k = anchor_mask[l].index(n)
                c = true_boxes[b,t, 4].astype('int32')
                
                # 保存到y_true中
                y_true[l][b, j, i, k, 0:4] = true_boxes[b,t, 0:4]
                y_true[l][b, j, i, k, 4] = 1
                y_true[l][b, j, i, k, 5+c] = 1
    

    对于最后输出的y_true而言,只有每个图里每个框最对应的位置有数据,其它的地方都为0。
    preprocess_true_boxes全部的代码如下:

    #---------------------------------------------------#
    #   读入xml文件,并输出y_true
    #---------------------------------------------------#
    def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
    
        assert (true_boxes[..., 4]<num_classes).all(), 'class id must be less than num_classes'
        # 一共有三个特征层数
        num_layers = len(anchors)//3
        # 先验框
        # 678为116,90,  156,198,  373,326
        # 345为30,61,  62,45,  59,119
        # 012为10,13,  16,30,  33,23,  
        anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]]
    
        true_boxes = np.array(true_boxes, dtype='float32')
        input_shape = np.array(input_shape, dtype='int32') # 416,416
        # 读出xy轴,读出长宽
        # 中心点(m,n,2)
        boxes_xy = (true_boxes[..., 0:2] + true_boxes[..., 2:4]) // 2
        boxes_wh = true_boxes[..., 2:4] - true_boxes[..., 0:2]
        # 计算比例
        true_boxes[..., 0:2] = boxes_xy/input_shape[:]
        true_boxes[..., 2:4] = boxes_wh/input_shape[:]
    
        # m张图
        m = true_boxes.shape[0]
        # 得到网格的shape为13,13;26,26;52,52
        grid_shapes = [input_shape//{0:32, 1:16, 2:8}[l] for l in range(num_layers)]
        # y_true的格式为(m,13,13,3,85)(m,26,26,3,85)(m,52,52,3,85)
        y_true = [np.zeros((m,grid_shapes[l][0],grid_shapes[l][1],len(anchor_mask[l]),5+num_classes),
            dtype='float32') for l in range(num_layers)]
        # [1,9,2]
        anchors = np.expand_dims(anchors, 0)
        anchor_maxes = anchors / 2.
        anchor_mins = -anchor_maxes
        # 长宽要大于0才有效
        valid_mask = boxes_wh[..., 0]>0
    
        for b in range(m):
            # 对每一张图进行处理
            wh = boxes_wh[b, valid_mask[b]]
            if len(wh)==0: continue
            # [n,1,2]
            wh = np.expand_dims(wh, -2)
            box_maxes = wh / 2.
            box_mins = -box_maxes
    
            # 计算真实框和哪个先验框最契合
            intersect_mins = np.maximum(box_mins, anchor_mins)
            intersect_maxes = np.minimum(box_maxes, anchor_maxes)
            intersect_wh = np.maximum(intersect_maxes - intersect_mins, 0.)
            intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
            box_area = wh[..., 0] * wh[..., 1]
            anchor_area = anchors[..., 0] * anchors[..., 1]
            iou = intersect_area / (box_area + anchor_area - intersect_area)
            # (n) 感谢 消尽不死鸟 的提醒
            best_anchor = np.argmax(iou, axis=-1)
    
            for t, n in enumerate(best_anchor):
                for l in range(num_layers):
                    if n in anchor_mask[l]:
                        # floor用于向下取整
                        i = np.floor(true_boxes[b,t,0]*grid_shapes[l][1]).astype('int32')
                        j = np.floor(true_boxes[b,t,1]*grid_shapes[l][0]).astype('int32')
                        # 找到真实框在特征层l中第b副图像对应的位置
                        k = anchor_mask[l].index(n)
                        c = true_boxes[b,t, 4].astype('int32')
                        y_true[l][b, j, i, k, 0:4] = true_boxes[b,t, 0:4]
                        y_true[l][b, j, i, k, 4] = 1
                        y_true[l][b, j, i, k, 5+c] = 1
    
        return y_true
    

    4、loss的计算过程

    在得到了y_pre和y_true后怎么对比呢?不是简单的减一下就可以的呢。
    loss值需要对三个特征层进行处理,这里以最小的特征层为例。
    1、利用y_true取出该特征层中真实存在目标的点的位置(m,13,13,3,1)及其对应的种类(m,13,13,3,80)。
    2、将yolo_outputs的预测值输出进行处理,得到reshape后的预测值y_pre,shape分别为(m,13,13,3,85),(m,26,26,3,85),(m,52,52,3,85)。还有解码后的xy,wh。
    3、获取真实框编码后的值,后面用于计算loss,编码后的值其含义与y_pre相同,可用于计算loss。
    4、对于每一幅图,计算其中所有真实框与预测框的IOU,取出每个网络点中IOU最大的先验框,如果这个最大的IOU都小于ignore_thresh,则保留,一般来说ignore_thresh取0.5,该步的目的是为了平衡负样本。
    5、计算xy和wh上的loss,其计算的是实际上存在目标的,利用第三步真实框编码后的的结果和未处理的预测结果进行对比得到loss。
    6、计算置信度的loss,其有两部分构成,第一部分是实际上存在目标的,预测结果中置信度的值与1对比;第二部分是实际上不存在目标的,在第四步中得到其最大IOU的值与0对比。
    7、计算预测种类的loss,其计算的是实际上存在目标的,预测类与真实类的差距。

    其实际上计算的总的loss是三个loss的和,这三个loss分别是:

    • 实际存在的框,编码后的长宽与xy轴偏移量与预测值的差距
    • 实际存在的框,预测结果中置信度的值与1对比;实际不存在的框,在上述步骤中,在第四步中得到其最大IOU的值与0对比
    • 实际存在的框,种类预测结果与实际结果的对比

    其实际代码如下,使用yolo_loss就可以获得loss值:

    import numpy as np
    import tensorflow as tf
    from keras import backend as K
    
    
    #---------------------------------------------------#
    #   将预测值的每个特征层调成真实值
    #---------------------------------------------------#
    def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
        num_anchors = len(anchors)
        # [1, 1, 1, num_anchors, 2]
        anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])
    
        # 获得x,y的网格
        # (13, 13, 1, 2)
        grid_shape = K.shape(feats)[1:3] # height, width
        grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),
            [1, grid_shape[1], 1, 1])
        grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),
            [grid_shape[0], 1, 1, 1])
        grid = K.concatenate([grid_x, grid_y])
        grid = K.cast(grid, K.dtype(feats))
    
        # (batch_size,13,13,3,85)
        feats = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])
    
        # 将预测值调成真实值
        # box_xy对应框的中心点
        # box_wh对应框的宽和高
        box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
        box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
        box_confidence = K.sigmoid(feats[..., 4:5])
        box_class_probs = K.sigmoid(feats[..., 5:])
    
        # 在计算loss的时候返回如下参数
        if calc_loss == True:
            return grid, feats, box_xy, box_wh
        return box_xy, box_wh, box_confidence, box_class_probs
    
    #---------------------------------------------------#
    #   用于计算每个预测框与真实框的iou
    #---------------------------------------------------#
    def box_iou(b1, b2):
        # 13,13,3,1,4
        # 计算左上角的坐标和右下角的坐标
        b1 = K.expand_dims(b1, -2)
        b1_xy = b1[..., :2]
        b1_wh = b1[..., 2:4]
        b1_wh_half = b1_wh/2.
        b1_mins = b1_xy - b1_wh_half
        b1_maxes = b1_xy + b1_wh_half
    
        # 1,n,4
        # 计算左上角和右下角的坐标
        b2 = K.expand_dims(b2, 0)
        b2_xy = b2[..., :2]
        b2_wh = b2[..., 2:4]
        b2_wh_half = b2_wh/2.
        b2_mins = b2_xy - b2_wh_half
        b2_maxes = b2_xy + b2_wh_half
    
        # 计算重合面积
        intersect_mins = K.maximum(b1_mins, b2_mins)
        intersect_maxes = K.minimum(b1_maxes, b2_maxes)
        intersect_wh = K.maximum(intersect_maxes - intersect_mins, 0.)
        intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
        b1_area = b1_wh[..., 0] * b1_wh[..., 1]
        b2_area = b2_wh[..., 0] * b2_wh[..., 1]
        iou = intersect_area / (b1_area + b2_area - intersect_area)
    
        return iou
    
    #---------------------------------------------------#
    #   loss值计算
    #---------------------------------------------------#
    def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, print_loss=False):
    
        # 一共有三层
        num_layers = len(anchors)//3 
    
        # 将预测结果和实际ground truth分开,args是[*model_body.output, *y_true]
        # y_true是一个列表,包含三个特征层,shape分别为(m,13,13,3,85),(m,26,26,3,85),(m,52,52,3,85)。
        # yolo_outputs是一个列表,包含三个特征层,shape分别为(m,13,13,3,85),(m,26,26,3,85),(m,52,52,3,85)。
        y_true = args[num_layers:]
        yolo_outputs = args[:num_layers]
    
        # 先验框
        # 678为116,90,  156,198,  373,326
        # 345为30,61,  62,45,  59,119
        # 012为10,13,  16,30,  33,23,  
        anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]]
    
        # 得到input_shpae为416,416 
        input_shape = K.cast(K.shape(yolo_outputs[0])[1:3] * 32, K.dtype(y_true[0]))
    
        # 得到网格的shape为13,13;26,26;52,52
        grid_shapes = [K.cast(K.shape(yolo_outputs[l])[1:3], K.dtype(y_true[0])) for l in range(num_layers)]
        loss = 0
    
        # 取出每一张图片
        # m的值就是batch_size
        m = K.shape(yolo_outputs[0])[0]
        mf = K.cast(m, K.dtype(yolo_outputs[0]))
    
        # y_true是一个列表,包含三个特征层,shape分别为(m,13,13,3,85),(m,26,26,3,85),(m,52,52,3,85)。
        # yolo_outputs是一个列表,包含三个特征层,shape分别为(m,13,13,3,85),(m,26,26,3,85),(m,52,52,3,85)。
        for l in range(num_layers):
            # 以第一个特征层(m,13,13,3,85)为例子
            # 取出该特征层中存在目标的点的位置。(m,13,13,3,1)
            object_mask = y_true[l][..., 4:5]
            # 取出其对应的种类(m,13,13,3,80)
            true_class_probs = y_true[l][..., 5:]
    
            # 将yolo_outputs的特征层输出进行处理
            # grid为网格结构(13,13,1,2),raw_pred为尚未处理的预测结果(m,13,13,3,85)
            # 还有解码后的xy,wh,(m,13,13,3,2)
            grid, raw_pred, pred_xy, pred_wh = yolo_head(yolo_outputs[l],
                 anchors[anchor_mask[l]], num_classes, input_shape, calc_loss=True)
            
            # 这个是解码后的预测的box的位置
            # (m,13,13,3,4)
            pred_box = K.concatenate([pred_xy, pred_wh])
    
            # 找到负样本群组,第一步是创建一个数组,[]
            ignore_mask = tf.TensorArray(K.dtype(y_true[0]), size=1, dynamic_size=True)
            object_mask_bool = K.cast(object_mask, 'bool')
            
            # 对每一张图片计算ignore_mask
            def loop_body(b, ignore_mask):
                # 取出第b副图内,真实存在的所有的box的参数
                # n,4
                true_box = tf.boolean_mask(y_true[l][b,...,0:4], object_mask_bool[b,...,0])
                # 计算预测结果与真实情况的iou
                # pred_box为13,13,3,4
                # 计算的结果是每个pred_box和其它所有真实框的iou
                # 13,13,3,n
                iou = box_iou(pred_box[b], true_box)
    
                # 13,13,3,1
                best_iou = K.max(iou, axis=-1)
    
                # 判断预测框的iou小于ignore_thresh则认为该预测框没有与之对应的真实框
                # 则被认为是这幅图的负样本
                ignore_mask = ignore_mask.write(b, K.cast(best_iou<ignore_thresh, K.dtype(true_box)))
                return b+1, ignore_mask
    
            # 遍历所有的图片
            _, ignore_mask = K.control_flow_ops.while_loop(lambda b,*args: b<m, loop_body, [0, ignore_mask])
    
            # 将每幅图的内容压缩,进行处理
            ignore_mask = ignore_mask.stack()
            #(m,13,13,3,1,1)
            ignore_mask = K.expand_dims(ignore_mask, -1)
    
            # 将真实框进行编码,使其格式与预测的相同,后面用于计算loss
            raw_true_xy = y_true[l][..., :2]*grid_shapes[l][:] - grid
            raw_true_wh = K.log(y_true[l][..., 2:4] / anchors[anchor_mask[l]] * input_shape[::-1])
    
            # object_mask如果真实存在目标则保存其wh值
            # switch接口,就是一个if/else条件判断语句
            raw_true_wh = K.switch(object_mask, raw_true_wh, K.zeros_like(raw_true_wh))
            box_loss_scale = 2 - y_true[l][...,2:3]*y_true[l][...,3:4]
    
            xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[...,0:2], from_logits=True)
            wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh-raw_pred[...,2:4])
            
            # 如果该位置本来有框,那么计算1与置信度的交叉熵
            # 如果该位置本来没有框,而且满足best_iou<ignore_thresh,则被认定为负样本
            # best_iou<ignore_thresh用于限制负样本数量
            confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits=True)+ \
                (1-object_mask) * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits=True) * ignore_mask
            
            class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[...,5:], from_logits=True)
    
            xy_loss = K.sum(xy_loss) / mf
            wh_loss = K.sum(wh_loss) / mf
            confidence_loss = K.sum(confidence_loss) / mf
            class_loss = K.sum(class_loss) / mf
            loss += xy_loss + wh_loss + confidence_loss + class_loss
            if print_loss:
                loss = tf.Print(loss, [loss, xy_loss, wh_loss, confidence_loss, class_loss, K.sum(ignore_mask)], message='loss: ')
        return loss
    

    训练自己的yolo3模型

    yolo3整体的文件夹构架如下:
    在这里插入图片描述
    本文使用VOC格式进行训练。
    训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。
    在这里插入图片描述
    训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。
    在这里插入图片描述
    在训练前利用voc2yolo3.py文件生成对应的txt。
    在这里插入图片描述
    再运行根目录下的voc_annotation.py,运行前需要将classes改成你自己的classes。

    classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
    

    在这里插入图片描述
    就会生成对应的2007_train.txt,每一行对应其图片位置及其真实框的位置。
    在这里插入图片描述
    在训练前需要修改model_data里面的voc_classes.txt文件,需要将classes改成你自己的classes。
    在这里插入图片描述
    运行train.py即可开始训练。
    在这里插入图片描述

    展开全文
  • yolo3

    2020-05-29 18:06:25
    1.官网地址:https://pjreddie.com/darknet/yolo/ 2.下载darknet并且安装 git clone https://github.com/pjreddie/darknet cd darknet make 3.下载yolov3.weights权重文件 wget ...

    1官网下载yolov3下载安装并使用

    1.1官网地址:https://pjreddie.com/darknet/yolo/

    1.2下载darknet并且安装

    git clone https://github.com/pjreddie/darknet
    cd darknet
    make

    1.3.下载yolov3.weights权重文件

    wget https://pjreddie.com/media/files/yolov3.weights

    1.4测试效果

    ./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg

    2利用tensorflow2.0 根据yolov3模型实现图像识别和检测

    2.1链接地址七天有效https://pan.baidu.com/s/1Hmbzr5Tx9PHRybF_W525nQ 验证密码:jpuh

     

    展开全文
  • YOLO3

    千次阅读 2018-05-05 15:42:43
    论文:YOLOv3: An Incremental Improvement 论文地址:...这篇博客就来介绍YOLO v3算法的内容,因为涉及到v1和v2的一些思想,所以可以先熟悉下:YOLO v1算法详解,YOLO v2算法详解。YO...

    论文:YOLOv3: An Incremental Improvement
    论文地址:https://pjreddie.com/media/files/papers/YOLOv3.pdf

    YOLO系列的目标检测算法真的非常赞!这篇博客就来介绍YOLO v3算法的内容,因为涉及到v1和v2的一些思想,所以可以先熟悉下:YOLO v1算法详解YOLO v2算法详解

    YOLO算法的基本思想是:首先通过特征提取网络对输入图像提取特征,得到一定size的feature map,比如13*13,然后将输入图像分成13*13个grid cell,接着如果ground truth中某个object的中心坐标落在哪个grid cell中,那么就由该grid cell来预测该object,因为每个grid cell都会预测固定数量的bounding box(YOLO v1和v2中是2个,YOLO v3中是3个,这几个bounding box的初始size是不一样的),那么这几个bounding box中最终是由哪一个来预测该object?答案是:这几个bounding box中只有和ground truth的IOU最大的bounding box才是用来预测该object的。可以看出预测得到的输出feature map有两个维度是提取到的特征的维度,比如13*13,还有一个维度(深度)是B*(5+C),注:YOLO v1中是(B*5+C),其中B表示每个grid cell预测的bounding box的数量,比如YOLO v1中是2个,YOLO v2中是5个,YOLO v3中是3个,C表示bounding box的类别数(没有背景类,所以对于VOC数据集是20),5表示4个坐标信息和一个置信度(objectness score)。

    算法在速度和精度上的提升可以看Figure1。
    这里写图片描述

    bounding box的坐标预测方式还是延续了YOLO v2的做法,简单讲就是下面这个截图的公式,tx、ty、tw、th就是模型的预测输出。cx和cy表示grid cell的坐标,比如某层的feature map大小是13*13,那么grid cell就有13*13个,第0行第1列的grid cell的坐标cx就是0,cy就是1。pw和ph表示预测前bounding box的size。bx、by。bw和bh就是预测得到的bounding box的中心的坐标和size。坐标的损失采用的是平方误差损失。
    这里写图片描述

    类别预测方面主要是将原来的单标签分类改进为多标签分类,因此网络结构上就将原来用于单标签多分类的softmax层换成用于多标签多分类的逻辑回归层。首先说明一下为什么要做这样的修改,原来分类网络中的softmax层都是假设一张图像或一个object只属于一个类别,但是在一些复杂场景下,一个object可能属于多个类,比如你的类别中有woman和person这两个类,那么如果一张图像中有一个woman,那么你检测的结果中类别标签就要同时有woman和person两个类,这就是多标签分类,需要用逻辑回归层来对每个类别做二分类。逻辑回归层主要用到sigmoid函数,该函数可以将输入约束在0到1的范围内,因此当一张图像经过特征提取后的某一类输出经过sigmoid函数约束后如果大于0.5,就表示属于该类。

    YOLO v3采用多个scale融合的方式做预测。原来的YOLO v2有一个层叫:passthrough layer,假设最后提取的feature map的size是13*13,那么这个层的作用就是将前面一层的26*26的feature map和本层的13*13的feature map进行连接,有点像ResNet。当时这么操作也是为了加强YOLO算法对小目标检测的精确度。这个思想在YOLO v3中得到了进一步加强,在YOLO v3中采用类似FPN的upsample和融合做法(最后融合了3个scale,其他两个scale的大小分别是26*26和52*52),在多个scale的feature map上做检测,对于小目标的检测效果提升还是比较明显的。前面提到过在YOLO v3中每个grid cell预测3个bounding box,看起来比YOLO v2中每个grid cell预测5个bounding box要少,其实不是!因为YOLO v3采用了多个scale的特征融合,所以boundign box的数量要比之前多很多,以输入图像为416*416为例:(13*13+26*26+52*52)*3和13*13*5相比哪个更多应该很清晰了。

    关于bounding box的初始尺寸还是采用YOLO v2中的k-means聚类的方式来做,这种先验知识对于bounding box的初始化帮助还是很大的,毕竟过多的bounding box虽然对于效果来说有保障,但是对于算法速度影响还是比较大的。作者在COCO数据集上得到的9种聚类结果:(10*13); (16*30); (33*23); (30*61); (62*45); (59*119); (116*90); (156*198); (373*326),这应该是按照输入图像的尺寸是416*416计算得到的。

    网络结构(Darknet-53)一方面基本采用全卷积(YOLO v2中采用pooling层做feature map的sample,这里都换成卷积层来做了),另一方面引入了residual结构(YOLO v2中还是类似VGG那样直筒型的网络结构,层数太多训起来会有梯度问题,所以Darknet-19也就19层,因此得益于ResNet的residual结构,训深层网络难度大大减小,因此这里可以将网络做到53层,精度提升比较明显)。Darknet-53只是特征提取层,源码中只使用了pooling层前面的卷积层来提取特征,因此multi-scale的特征融合和预测支路并没有在该网络结构中体现,具体信息可以看源码:https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg。预测支路采用的也是全卷积的结构,其中最后一个卷积层的卷积核个数是255,是针对COCO数据集的80类:3*(80+4+1)=255,3表示一个grid cell包含3个bounding box,4表示框的4个坐标信息,1表示objectness score。模型训练方面还是采用原来YOLO v2中的multi-scale training。
    这里写图片描述

    Table2是几个网络在ImageNet数据集上的性能和精度对比。可以看出Darknet-53的性能还是非常不错的。
    这里写图片描述

    YOLO v3的实验结果对比可以看Table3。原来YOLO v2对于小目标的检测效果是比较差的,通过引入多尺度特征融合的方式,可以看出YOLO v3的APS要比YOLO v2的APS高出不少。
    这里写图片描述

    最后这张图非常有意思,直接用All the other slow ones来代表其他算法,实实在在展现了本篇文章随性的风格。
    这里写图片描述

    最后贴一份参考资料:https://blog.paperspace.com/tag/series-yolo/,其中part1是介绍YOLO算法相关的基础知识,part2到part5是介绍如何用PyTorch实现YOLO v3算法,非常推荐。

    展开全文
  • Keras/Tensorflow+python+yolo3训练自己的数据集

    万次阅读 多人点赞 2018-06-07 21:37:35
    代码:https://github.com/qqwweee/keras-yolo3修改yolov3.cfg文件:https://blog.csdn.net/lilai619/article/details/79695109本文介绍如何制作数据集、修改代码、不加载预权重从头跑自己的训练数据一、简单回顾...
  • pytorch_yolo3

    2021-02-21 11:32:48
    pytorch_yolo3
  • 3、yolo3 4、SSD 总结 学习前言 ……最近在学习yolo1、yolo2和yolo3,事实上它们和SSD网络有一定的相似性,我准备汇总一下,看看有什么差别。 各个网络的结构图与其实现代码 1、yolo1 在这里插入图片描述 由图...
  • YOLO3(检测,培训和评估) 数据集和模型 数据集 地图 演示版 设定档 模型 袋鼠检测(1类)( ) 95% 检查动物园 车牌检测(罗马尼亚语中的欧洲)(1类)( ) 90% 检查动物园 浣熊检测(1类)( ) 98% ...
  • autoware1.14的YOLO2、YOLO3权重文件
  • 在训练前利用voc2yolo3.py文件生成对应的txt。 再运行根目录下的voc_annotation.py,运行前需要将classes改成你自己的classes。注意不要使用中文标签,文件夹中不要有空格! classes = ["aeroplane", "bicycle", ...
  • YOLO1、YOLO2、YOLO3对比

    千次阅读 2019-08-27 17:40:26
    YOLO详解参见地址: YOLO1:https://blog.csdn.net/qq_32172681/article/details/99418000 YOLO2:... YOLO3:https://blog.csdn.net/qq_32172681/article/details...
  • 简述yolo1-yolo3Object detection is a computer vision task that involves predicting the presence of one or more objects, along with their classes and bounding boxes. YOLO (You Only Look Once) is a ...
  • yolo3_params

    2018-11-27 13:24:36
    the params for yolo3,object detection for you only look once.
  • keras-yolo3 Introduction A Keras implementation of YOLOv3 (Tensorflow backend) inspired by allanzelener/YAD2K. Quick Start Download YOLOv3 weights from YOLO website. Convert the Darknet YOLO ...
  • 详解 YOLO3

    2019-10-14 13:38:35
    YOLOv3没有太多的创新,主要是借鉴一些好的方案融合到YOLO里面。不过效果还是不错的,在保持速度优势的前提下,提升了预测精度,尤其是加强...YOLO3主要的改进有:调整了网络结构;利用多尺度特征进行对象检测;对象...
  • pytorch yolo yolo3 眼睛 左眼 右眼 检测环境预测结果1.下载pytorch yolo3 源码2.标注左右眼数据集(过程省略)3.下载预训练权重4.修改目录结构5.转换数据集6.训练数据7.预测结果 环境 硬件:GPU 16G 软件:使用pip...
  • tf2-keras-yolo3 这是对qqwweee/keras-yolo3的fork和修改,目的是使它支持TensorFlow 2.2。 主要修改内容如下: 以tf.keras为主导,替换掉独立的keras库 修改部分基于TensorFlow 1.x版本的接口和逻辑,使项目支持...
  • 上篇文章讲了darknet的网络构建,这次讲整个yolo3网络的构建 YOLO代码解释 import torch import torch.nn as nn import torch.nn.functional as F from torch.autograd import Variable import numpy as np from ut ...
  • YOLO3个人理解

    2020-12-23 14:12:34
    YOLO3个人理解训练过程一.得到真实标签二.得到预测标签三.计算损失函数训练预测过程 训练过程 yolo3的模型框架别的博客里很多可以参考,这里不解析网络,有兴趣的同学可以参考以下链接: ...
  • keras-yolo3-recognize.rar

    2020-03-30 14:28:27
    hand-keras-yolo3-recognize 模型训练参考:https://gitee.com/cungudafa/keras-yolo3 yolo3识别这里参考于:https://github.com/AaronJny/tf2-keras-yolo3
  • YOLO3C++版

    2018-10-07 16:44:11
    yolo3C++版,版本不一定是你所需要的,可以仿照着改,改的方式也不复杂,
  • Blood_detecton_YOLO3-源码

    2021-03-09 04:56:29
    Blood_detecton_YOLO3
  • yolo3使用文档

    2020-07-02 15:43:56
    yolo3从零开始训练自己的模型
  • yolo3总结

    千次阅读 2019-05-24 17:14:26
    代码网址:https://github.com/marvis/pytorch-yolo3 参考的博客:https://blog.csdn.net/leviopku/article/details/82660381 1.backbone 使用Darknet-53,这种网络使用了残差结构,feature map的改变通过步长的...
  • yolo3-pytorch-rose.zip

    2021-03-08 10:00:45
    yolo3-pytorch-rose.zip
  • yolo3-pytorch-master.zip

    2021-02-01 14:22:07
    yolo3-pytorch-master.zip
  • 上一节构建完了主干网络darknet53(backbone),这一节构建完整的yolo3网络。 还记得吗?arknet # 输出三路分支 out3 = self.layer3(x) out4 = self.layer4(out3) out5 = self.layer5(out4) return out3, out4, ...
  • keras-yolo3-master.rar

    2020-09-08 22:25:45
    keras-yolo3-master.rar

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,056
精华内容 2,422
关键字:

yolo3