2.1.Pytorch目标检测--Yolo V3

本文详细介绍了如何在PyTorch中实现Yolo V3目标检测,包括DarkNet-53主干网络的结构,特征层的上采样与拼接,以及残差块的工作原理。此外,还讲解了数据集制作、预测过程、训练参数解析以及模型训练与应用的步骤。
摘要由CSDN通过智能技术生成

这几天在p站上找了几个UP主学习了一下yolo v3,我会继续完善

1.Yolo V3的网络结构

 1.主干网络:(DarkNet-53)左边用虚线框起来的部分。

          主干特征提取网络主要的目的是提取图片的特征

          输入是一个416*416*3,然后经过卷积和残差网络,这个过程是一个下采样的过程。

          在下采样的过程中,图像的宽和高不断被压缩,通道数不断被扩张。可以获得一堆的特征层,可以表示输入进来的图片的特征。

2.注意主干网络的最后3个特征层,3个特征层的shape分别为(52,52,256),(26,26,512),(13,13,1024),这3个特征层用于与后面上采样的特征层堆叠拼接(Concat)。如下图

      2.1.最后一个主干特征层(13,13,1024)先进行5次卷积处理,然后其中一部分用于卷积和上采样处理,另外一部分分类预测和回归预测,其实就是进行了2次卷积和一次卷积,生成的shape是(13,13,75),(这个75可以分解为(3*25),这个3就是存在3个先验框,然而这个25还可以继续分解(20+1+4),20是因为我们使用的是VOCdevkit数据集,其中的的类别有20种,20表示的就是类别的概率,1表示框类是不是有物体,4表示的是x_offset、y_offset、height、width,所以要注意你使用的是什么数据集,数据集有着不同的类别),如下图。

 输出的预测是预测的大的特征(13,13,75)

 这5个卷积就是上图中间那5个卷积的展开

     2.2.卷积+上采样后的特征层(26,26,256)与Darknet-53网络中的特征层(26,26,512)进行拼接,生成一个shape为(26,26,768)的特征层,然后进行5次卷积,生成shape为(26,26,256)的特征层,然后其中一部分用于卷积和上采样处理,另外一部分分类预测和回归预测,其实就是进行了2次卷积和一次卷积,生成的shape是(26,26,75),此处的75解释在2.1中可以找到。如下图

输出的预测是预测的中等特征(26,26,75)

      2.3.卷积+上采样后的特征层(52,52,128)与Darknet-53中shape为(52,52,256)的特征层进行拼接。然后进行5次卷积,生成shape为(52,52,128)的特征层,接下来分类预测和回归预测,进行2次卷积和一次卷积,生成shape(52,52,75)。如下图

输出的预测是预测的小的特征(52,52,75)

2.DarkNet-53代码详解

2.1残差块

 这表示的就是一个残差块,并不是分开的,上面2个卷积层也是包含在残差块中的。

 上图是残差网络Residual,一部分进行正常的卷积,另一部分不进行任何处理,然后在卷积处理后,将与没有进行任何处理进行拼接。Darknet53中的残差卷积就是首先进行一次卷积核大小为3X3、步长为2的卷积,该卷积会压缩输入进来的特征层的宽和高,此时我们可以获得一个特征层,我们将该特征层命名为layer。之后我们再对该特征层进行一次1X1的卷积和一次3X3的卷积,并把这个结果加上layer,此时我们便构成了残差结构。通过不断的1X1卷积和3X3卷积以及残差边的叠加,我们便大幅度的加深了网络。残差网络的特点是容易优化,并且能够通过增加相当的深度来提高准确率。其内部的残差块使用了跳跃连接,缓解了在深度神经网络中增加深度带来的梯度消失问题。
2.2.DarkNet-53代码

import math
from collections import OrderedDict

import torch.nn as nn

#--------------------------------
#   残差结构
#--------------------------------
#--------主干网络--------
#首先进行一次卷积核大小为1*1,步数为1的卷积,下降通道数
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])
#加上一个LeakyReLU的激活函数,它的好处是存在一个小的正梯度,这样可以防止梯度在训练中消失。
        self.relu1  = nn.LeakyReLU(0.1)
#利用一个3*3的卷积提取特征并上升通道数        
        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


#DarkNet网络结构
#---------------------
class DarkNet(nn.Module):
    def __init__(self, layers):
        super(DarkNet, self).__init__()
        self.inplanes = 32
        # 通过卷积层将416*416*3的张量转换为416*416*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)

        # 通过残差网络将416*416*32的张量转换为208*208*64的张量
        self.layer1 = self._make_layer([32, 64], layers[0])
        # 通过残差网络将208*208*64的张量转换为104*104*128的张量
        self.layer2 = self._make_layer([64, 128], layers[1])
        # 通过残差网络将104*104*128的张量转换为52*52*256的张量
        self.layer3 = self._make_layer([128, 256], layers[2])
        # 通过残差网络将52*52*256的张量转换为26*26*512的张量
        self.layer4 = self._make_layer([256, 512], layers[3])
        # 通过残差网络将26*26*512的张量转换为13*13*1024的张量
        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_()

   
    #   在每一个layer里面,首先利用一个步长为2的3x3卷积进行下采样
     
    #---------------------------------------------------------------------#
    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])))
    #增加一个LeakyReLU的激活函数
        layers.append(("ds_relu", nn.LeakyReLU(0.1)))
        # 加入残差结构
        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)
    #out就是接下来需要处理的数
        return out3, out4, out5
    #表示每个残差块所循环的次数
def darknet53():
    model = DarkNet([1, 2, 8, 8, 4])
    return model

 3.从特征获得预测结果

from collections import OrderedDict

import torch
import torch.nn as nn

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)),
    ]))


#   make_last_layers里面一共有七个卷积,前五个用于提取特征。
#   后两个用于获得yolo网络的预测结果
#1*1的卷积调整通道数,3*3的卷积提取特征
def make_last_layers(filters_list, in_filters, out_filter):
    m = nn.Sequential(
        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
#Yolo组成
class YoloBody(nn.Module):
    def __init__(self, anchors_mask, num_classes):
        super(YoloBody, self).__init__()
        
        #   生成darknet53的主干模型
        #   获得三个有效特征层,他们的shape分别是:(52,52,256),(26,26,512),(13,13,1024)
       
        self.backbone = darknet53()

       
        #   输出 : [64, 128, 256, 512, 1024]
        
        out_filters = self.backbone.layers_out_filters

       
        #   计算yolo_head的输出通道数,对于voc数据集而言
        #   final_out_filter0 = final_out_filter1 = final_out_filter2 = 75,每个通道数的数量都相同
       
        self.last_layer0            = make_last_layers([512, 1024], out_filters[-1], len(anchors_mask[0]) * (num_classes + 5))

        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, len(anchors_mask[1]) * (num_classes + 5))

        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, len(anchors_mask[2]) * (num_classes + 5))

    def forward(self, x):
        #   获得三个有效特征层,他们的shape分别是:
        #   52,52,256;26,26,512;13,13,1024
      
        x2, x1, x0 = self.backbone(x)

        
        #   第一个特征层
        #   out0 = (batch_size,255,13,13)
        #进行5次卷积
        # 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512
        out0_branch = self.last_layer0[:5](x0)
        out0        = self.last_layer0[5:](out0_branch)
        
        #进行2次卷积和上采样
        # 13,13,512 -> 13,13,256 -> 26,26,256
        x1_in = self.last_layer1_conv(out0_branch)
        x1_in = self.last_layer1_upsample(x1_in)
        #进行堆叠
        # 26,26,256 + 26,26,512 -> 26,26,768
        x1_in = torch.cat([x1_in, x1], 1)
       
        #   第二个特征层
        #   out1 = (batch_size,255,26,26)
        #进行5次卷积
        # 26,26,768 -> 26,26,256 -> 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256
        out1_branch = self.last_layer1[:5](x1_in)
        out1        = self.last_layer1[5:](out1_branch)
        #进行2次卷积和上采样
        # 26,26,256 -> 26,26,128 -> 52,52,128
        x2_in = self.last_layer2_conv(out1_branch)
        x2_in = self.last_layer2_upsample(x2_in)
        #进行堆叠
        # 52,52,128 + 52,52,256 -> 52,52,384
        x2_in = torch.cat([x2_in, x2], 1)
        
        #   第一个特征层
        #   out3 = (batch_size,255,52,52)
        #进行5次卷积
        # 52,52,384 -> 52,52,128 -> 52,52,256 -> 52,52,128 -> 52,52,256 -> 52,52,128
        out2 = self.last_layer2(x2_in)
        return out0, out1, out2

4. 预测过程详解

import colorsys
import os
import time

import numpy as np
import torch
import torch.nn as nn
from PIL import ImageDraw, ImageFont

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

'''
训练自己的数据集必看注释!
'''
class YOLO(object):
    _defaults = {
        #--------------------------------------------------------------------------#
        #   使用自己训练好的模型进行预测一定要修改model_path和classes_path!
        #   model_path指向logs文件夹下的权值文件,classes_path指向model_data下的txt
        #
        #   训练好后logs文件夹下存在多个权值文件,选择验证集损失较低的即可。
        #   验证集损失较低不代表mAP较高,仅代表该权值在验证集上泛化性能较好。
        #   如果出现shape不匹配,同时要注意训练时的model_path和classes_path参数的修改
        #--------------------------------------------------------------------------#
        "model_path"        : 'model_data/yolo_weights.pth',
        "classes_path"      : 'model_data/coco_classes.txt',
        #---------------------------------------------------------------------#
        #   anchors_path代表先验框对应的txt文件,一般不修改。
        #   anchors_mask用于帮助代码找到对应的先验框,一般不修改。
        #-------
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值