精华内容
下载资源
问答
  • SSH2框架搭建实例源码

    万次下载 热门讨论 2013-04-25 09:53:29
    SSH2框架搭建实例(spring3.2+strust2.3.4+hibernate4.2)全部采用最新版本.演示了用户登录和用户管理
  • 睿智的目标检测30——Pytorch搭建YoloV4目标检测平台

    万次阅读 多人点赞 2020-05-24 14:14:13
    睿智的目标检测30——Pytorch搭建YoloV4目标检测平台学习前言什么是YOLOV4代码下载YOLOV4改进的部分(不完全)YOLOV4结构解析1、主干特征提取网络Backbone2、特征金字塔3、YoloHead利用获得到的特征进行预测4、预测...

    学习前言

    也做了一下pytorch版本的。
    在这里插入图片描述

    什么是YOLOV4

    在这里插入图片描述
    YOLOV4是YOLOV3的改进版,在YOLOV3的基础上结合了非常多的小Tricks。
    尽管没有目标检测上革命性的改变,但是YOLOV4依然很好的结合了速度与精度。
    根据上图也可以看出来,YOLOV4在YOLOV3的基础上,在FPS不下降的情况下,mAP达到了44,提高非常明显。

    YOLOV4整体上的检测思路和YOLOV3相比相差并不大,都是使用三个特征层进行分类与回归预测。

    请注意!

    强烈建议在学习YOLOV4之前学习YOLOV3,因为YOLOV4确实可以看作是YOLOV3结合一系列改进的版本!

    强烈建议在学习YOLOV4之前学习YOLOV3,因为YOLOV4确实可以看作是YOLOV3结合一系列改进的版本!

    强烈建议在学习YOLOV4之前学习YOLOV3,因为YOLOV4确实可以看作是YOLOV3结合一系列改进的版本!

    (重要的事情说三遍!)

    YOLOV3可参考该博客:
    https://blog.csdn.net/weixin_44791964/article/details/105310627

    代码下载

    https://github.com/bubbliiiing/yolov4-pytorch
    喜欢的可以给个star噢!

    YOLOV4改进的部分(不完全)

    1、主干特征提取网络:DarkNet53 => CSPDarkNet53

    2、特征金字塔:SPP,PAN

    3、分类回归层:YOLOv3(未改变)

    4、训练用到的小技巧:Mosaic数据增强、Label Smoothing平滑、CIOU、学习率余弦退火衰减

    5、激活函数:使用Mish激活函数

    以上并非全部的改进部分,还存在一些其它的改进,由于YOLOV4使用的改进实在太多了,很难完全实现与列出来,这里只列出来了一些我比较感兴趣,而且非常有效的改进。

    还有一个重要的事情:
    论文中提到的SAM,作者自己的源码也没有使用。

    还有其它很多的tricks,不是所有的tricks都有提升,我也没法实现全部的tricks

    整篇BLOG会结合YOLOV3与YOLOV4的差别进行解析

    YOLOV4结构解析

    为方便理解,本文将所有通道数都放到了最后一维度。
    为方便理解,本文将所有通道数都放到了最后一维度。
    为方便理解,本文将所有通道数都放到了最后一维度。

    1、主干特征提取网络Backbone

    当输入是416x416时,特征结构如下:
    在这里插入图片描述
    当输入是608x608时,特征结构如下:
    在这里插入图片描述
    主干特征提取网络Backbone的改进点有两个:
    a).主干特征提取网络:DarkNet53 => CSPDarkNet53
    b).激活函数:使用Mish激活函数

    如果大家对YOLOV3比较熟悉的话,应该知道Darknet53的结构,其由一系列残差网络结构构成。在Darknet53中,其存在resblock_body模块,其由一次下采样多次残差结构的堆叠构成,Darknet53便是由resblock_body模块组合而成

    而在YOLOV4中,其对该部分进行了一定的修改。
    1、其一是将DarknetConv2D的激活函数由LeakyReLU修改成了Mish,卷积块由DarknetConv2D_BN_Leaky变成了DarknetConv2D_BN_Mish
    Mish函数的公式与图像如下:
    Mish=x×tanh(ln(1+ex)) Mish=x \times tanh(ln(1+e^x))
    在这里插入图片描述
    2、其二是将resblock_body的结构进行修改,使用了CSPnet结构。此时YOLOV4当中的Darknet53被修改成了CSPDarknet53
    在这里插入图片描述
    CSPnet结构并不算复杂,就是将原来的残差块的堆叠进行了一个拆分,拆成左右两部分:
    主干部分继续进行原来的残差块的堆叠
    另一部分则像一个残差边一样,经过少量处理直接连接到最后。
    因此可以认为CSP中存在一个大的残差边。

    #---------------------------------------------------#
    #   CSPdarknet的结构块
    #   存在一个大残差边
    #   这个大残差边绕过了很多的残差结构
    #---------------------------------------------------#
    class Resblock_body(nn.Module):
        def __init__(self, in_channels, out_channels, num_blocks, first):
            super(Resblock_body, self).__init__()
    
            self.downsample_conv = BasicConv(in_channels, out_channels, 3, stride=2)
    
            if first:
                self.split_conv0 = BasicConv(out_channels, out_channels, 1)
                self.split_conv1 = BasicConv(out_channels, out_channels, 1)  
                self.blocks_conv = nn.Sequential(
                    Resblock(channels=out_channels, hidden_channels=out_channels//2),
                    BasicConv(out_channels, out_channels, 1)
                )
                self.concat_conv = BasicConv(out_channels*2, out_channels, 1)
            else:
                self.split_conv0 = BasicConv(out_channels, out_channels//2, 1)
                self.split_conv1 = BasicConv(out_channels, out_channels//2, 1)
    
                self.blocks_conv = nn.Sequential(
                    *[Resblock(out_channels//2) for _ in range(num_blocks)],
                    BasicConv(out_channels//2, out_channels//2, 1)
                )
                self.concat_conv = BasicConv(out_channels, out_channels, 1)
    
        def forward(self, x):
            x = self.downsample_conv(x)
    
            x0 = self.split_conv0(x)
    
            x1 = self.split_conv1(x)
            x1 = self.blocks_conv(x1)
    
            x = torch.cat([x1, x0], dim=1)
            x = self.concat_conv(x)
    
            return x
    

    全部实现代码为:

    import torch
    import torch.nn.functional as F
    import torch.nn as nn
    import math
    from collections import OrderedDict
    
    #-------------------------------------------------#
    #   MISH激活函数
    #-------------------------------------------------#
    class Mish(nn.Module):
        def __init__(self):
            super(Mish, self).__init__()
    
        def forward(self, x):
            return x * torch.tanh(F.softplus(x))
    
    #-------------------------------------------------#
    #   卷积块
    #   CONV+BATCHNORM+MISH
    #-------------------------------------------------#
    class BasicConv(nn.Module):
        def __init__(self, in_channels, out_channels, kernel_size, stride=1):
            super(BasicConv, self).__init__()
    
            self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, kernel_size//2, bias=False)
            self.bn = nn.BatchNorm2d(out_channels)
            self.activation = Mish()
    
        def forward(self, x):
            x = self.conv(x)
            x = self.bn(x)
            x = self.activation(x)
            return x
    
    #---------------------------------------------------#
    #   CSPdarknet的结构块的组成部分
    #   内部堆叠的残差块
    #---------------------------------------------------#
    class Resblock(nn.Module):
        def __init__(self, channels, hidden_channels=None, residual_activation=nn.Identity()):
            super(Resblock, self).__init__()
    
            if hidden_channels is None:
                hidden_channels = channels
    
            self.block = nn.Sequential(
                BasicConv(channels, hidden_channels, 1),
                BasicConv(hidden_channels, channels, 3)
            )
    
        def forward(self, x):
            return x+self.block(x)
    
    #---------------------------------------------------#
    #   CSPdarknet的结构块
    #   存在一个大残差边
    #   这个大残差边绕过了很多的残差结构
    #---------------------------------------------------#
    class Resblock_body(nn.Module):
        def __init__(self, in_channels, out_channels, num_blocks, first):
            super(Resblock_body, self).__init__()
    
            self.downsample_conv = BasicConv(in_channels, out_channels, 3, stride=2)
    
            if first:
                self.split_conv0 = BasicConv(out_channels, out_channels, 1)
                self.split_conv1 = BasicConv(out_channels, out_channels, 1)  
                self.blocks_conv = nn.Sequential(
                    Resblock(channels=out_channels, hidden_channels=out_channels//2),
                    BasicConv(out_channels, out_channels, 1)
                )
                self.concat_conv = BasicConv(out_channels*2, out_channels, 1)
            else:
                self.split_conv0 = BasicConv(out_channels, out_channels//2, 1)
                self.split_conv1 = BasicConv(out_channels, out_channels//2, 1)
    
                self.blocks_conv = nn.Sequential(
                    *[Resblock(out_channels//2) for _ in range(num_blocks)],
                    BasicConv(out_channels//2, out_channels//2, 1)
                )
                self.concat_conv = BasicConv(out_channels, out_channels, 1)
    
        def forward(self, x):
            x = self.downsample_conv(x)
    
            x0 = self.split_conv0(x)
    
            x1 = self.split_conv1(x)
            x1 = self.blocks_conv(x1)
    
            x = torch.cat([x1, x0], dim=1)
            x = self.concat_conv(x)
    
            return x
    
    class CSPDarkNet(nn.Module):
        def __init__(self, layers):
            super(CSPDarkNet, self).__init__()
            self.inplanes = 32
            self.conv1 = BasicConv(3, self.inplanes, kernel_size=3, stride=1)
            self.feature_channels = [64, 128, 256, 512, 1024]
    
            self.stages = nn.ModuleList([
                Resblock_body(self.inplanes, self.feature_channels[0], layers[0], first=True),
                Resblock_body(self.feature_channels[0], self.feature_channels[1], layers[1], first=False),
                Resblock_body(self.feature_channels[1], self.feature_channels[2], layers[2], first=False),
                Resblock_body(self.feature_channels[2], self.feature_channels[3], layers[3], first=False),
                Resblock_body(self.feature_channels[3], self.feature_channels[4], layers[4], first=False)
            ])
    
            self.num_features = 1
            # 进行权值初始化
            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 forward(self, x):
            x = self.conv1(x)
    
            x = self.stages[0](x)
            x = self.stages[1](x)
            out3 = self.stages[2](x)
            out4 = self.stages[3](out3)
            out5 = self.stages[4](out4)
    
            return out3, out4, out5
    
    def darknet53(pretrained, **kwargs):
        model = CSPDarkNet([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、特征金字塔

    当输入是416x416时,特征结构如下:
    在这里插入图片描述
    当输入是608x608时,特征结构如下:
    在这里插入图片描述
    在特征金字塔部分,YOLOV4结合了两种改进:
    a).使用了SPP结构。
    b).使用了PANet结构。

    如上图所示,除去CSPDarknet53和Yolo Head的结构外,都是特征金字塔的结构。
    1、SPP结构参杂在对CSPdarknet53的最后一个特征层的卷积里,在对CSPdarknet53的最后一个特征层进行三次DarknetConv2D_BN_Leaky卷积后分别利用四个不同尺度的最大池化进行处理,最大池化的池化核大小分别为13x13、9x9、5x5、1x1(1x1即无处理)

    #---------------------------------------------------#
    #   SPP结构,利用不同大小的池化核进行池化
    #   池化后堆叠
    #---------------------------------------------------#
    class SpatialPyramidPooling(nn.Module):
        def __init__(self, pool_sizes=[5, 9, 13]):
            super(SpatialPyramidPooling, self).__init__()
    
            self.maxpools = nn.ModuleList([nn.MaxPool2d(pool_size, 1, pool_size//2) for pool_size in pool_sizes])
    
        def forward(self, x):
            features = [maxpool(x) for maxpool in self.maxpools[::-1]]
            features = torch.cat(features + [x], dim=1)
    
            return features
    

    其可以它能够极大地增加感受野,分离出最显著的上下文特征
    在这里插入图片描述
    2、PANet是2018的一种实例分割算法,其具体结构由反复提升特征的意思。
    在这里插入图片描述
    上图为原始的PANet的结构,可以看出来其具有一个非常重要的特点就是特征的反复提取
    在(a)里面是传统的特征金字塔结构,在完成特征金字塔从下到上的特征提取后,还需要实现(b)中从上到下的特征提取。

    而在YOLOV4当中,其主要是在三个有效特征层上使用了PANet结构。
    在这里插入图片描述
    实现代码如下:

    #---------------------------------------------------#
    #   yolo_body
    #---------------------------------------------------#
    class YoloBody(nn.Module):
        def __init__(self, config):
            super(YoloBody, self).__init__()
            self.config = config
            #  backbone
            self.backbone = darknet53(None)
    
            self.conv1 = make_three_conv([512,1024],1024)
            self.SPP = SpatialPyramidPooling()
            self.conv2 = make_three_conv([512,1024],2048)
    
            self.upsample1 = Upsample(512,256)
            self.conv_for_P4 = conv2d(512,256,1)
            self.make_five_conv1 = make_five_conv([256, 512],512)
    
            self.upsample2 = Upsample(256,128)
            self.conv_for_P3 = conv2d(256,128,1)
            self.make_five_conv2 = make_five_conv([128, 256],256)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter2 = len(config["yolo"]["anchors"][2]) * (5 + config["yolo"]["classes"])
            self.yolo_head3 = yolo_head([256, final_out_filter2],128)
    
            self.down_sample1 = conv2d(128,256,3,stride=2)
            self.make_five_conv3 = make_five_conv([256, 512],512)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter1 = len(config["yolo"]["anchors"][1]) * (5 + config["yolo"]["classes"])
            self.yolo_head2 = yolo_head([512, final_out_filter1],256)
    
    
            self.down_sample2 = conv2d(256,512,3,stride=2)
            self.make_five_conv4 = make_five_conv([512, 1024],1024)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter0 = len(config["yolo"]["anchors"][0]) * (5 + config["yolo"]["classes"])
            self.yolo_head1 = yolo_head([1024, final_out_filter0],512)
    
    
        def forward(self, x):
            #  backbone
            x2, x1, x0 = self.backbone(x)
    
            P5 = self.conv1(x0)
            P5 = self.SPP(P5)
            P5 = self.conv2(P5)
    
            P5_upsample = self.upsample1(P5)
            P4 = self.conv_for_P4(x1)
            P4 = torch.cat([P4,P5_upsample],axis=1)
            P4 = self.make_five_conv1(P4)
    
            P4_upsample = self.upsample2(P4)
            P3 = self.conv_for_P3(x2)
            P3 = torch.cat([P3,P4_upsample],axis=1)
            P3 = self.make_five_conv2(P3)
    
            P3_downsample = self.down_sample1(P3)
            P4 = torch.cat([P3_downsample,P4],axis=1)
            P4 = self.make_five_conv3(P4)
    
            P4_downsample = self.down_sample2(P4)
            P5 = torch.cat([P4_downsample,P5],axis=1)
            P5 = self.make_five_conv4(P5)
    
            out2 = self.yolo_head3(P3)
            out1 = self.yolo_head2(P4)
            out0 = self.yolo_head1(P5)
    
            return out0, out1, out2
    

    3、YoloHead利用获得到的特征进行预测

    当输入是416x416时,特征结构如下:
    在这里插入图片描述
    当输入是608x608时,特征结构如下:
    在这里插入图片描述
    1、在特征利用部分,YoloV4提取多特征层进行目标检测,一共提取三个特征层,分别位于中间层,中下层,底层,三个特征层的shape分别为(76,76,256)、(38,38,512)、(19,19,1024)。

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

    实现代码如下:

    #---------------------------------------------------#
    #   最后获得yolov4的输出
    #---------------------------------------------------#
    def yolo_head(filters_list, in_filters):
        m = nn.Sequential(
            conv2d(in_filters, filters_list[0], 3),
            nn.Conv2d(filters_list[0], filters_list[1], 1),
        )
        return m
    
    #---------------------------------------------------#
    #   yolo_body
    #---------------------------------------------------#
    class YoloBody(nn.Module):
        def __init__(self, config):
            super(YoloBody, self).__init__()
            self.config = config
            #  backbone
            self.backbone = darknet53(None)
    
            self.conv1 = make_three_conv([512,1024],1024)
            self.SPP = SpatialPyramidPooling()
            self.conv2 = make_three_conv([512,1024],2048)
    
            self.upsample1 = Upsample(512,256)
            self.conv_for_P4 = conv2d(512,256,1)
            self.make_five_conv1 = make_five_conv([256, 512],512)
    
            self.upsample2 = Upsample(256,128)
            self.conv_for_P3 = conv2d(256,128,1)
            self.make_five_conv2 = make_five_conv([128, 256],256)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter2 = len(config["yolo"]["anchors"][2]) * (5 + config["yolo"]["classes"])
            self.yolo_head3 = yolo_head([256, final_out_filter2],128)
    
            self.down_sample1 = conv2d(128,256,3,stride=2)
            self.make_five_conv3 = make_five_conv([256, 512],512)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter1 = len(config["yolo"]["anchors"][1]) * (5 + config["yolo"]["classes"])
            self.yolo_head2 = yolo_head([512, final_out_filter1],256)
    
    
            self.down_sample2 = conv2d(256,512,3,stride=2)
            self.make_five_conv4 = make_five_conv([512, 1024],1024)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter0 = len(config["yolo"]["anchors"][0]) * (5 + config["yolo"]["classes"])
            self.yolo_head1 = yolo_head([1024, final_out_filter0],512)
    
    
        def forward(self, x):
            #  backbone
            x2, x1, x0 = self.backbone(x)
    
            P5 = self.conv1(x0)
            P5 = self.SPP(P5)
            P5 = self.conv2(P5)
    
            P5_upsample = self.upsample1(P5)
            P4 = self.conv_for_P4(x1)
            P4 = torch.cat([P4,P5_upsample],axis=1)
            P4 = self.make_five_conv1(P4)
    
            P4_upsample = self.upsample2(P4)
            P3 = self.conv_for_P3(x2)
            P3 = torch.cat([P3,P4_upsample],axis=1)
            P3 = self.make_five_conv2(P3)
    
            P3_downsample = self.down_sample1(P3)
            P4 = torch.cat([P3_downsample,P4],axis=1)
            P4 = self.make_five_conv3(P4)
    
            P4_downsample = self.down_sample2(P4)
            P5 = torch.cat([P4_downsample,P5],axis=1)
            P5 = self.make_five_conv4(P5)
    
            out2 = self.yolo_head3(P3)
            out1 = self.yolo_head2(P4)
            out0 = self.yolo_head1(P5)
    
            return out0, out1, out2
    

    4、预测结果的解码

    由第二步我们可以获得三个特征层的预测结果,shape分别为(N,19,19,255),(N,38,38,255),(N,76,76,255)的数据,对应每个图分为19x19、38x38、76x76的网格上3个预测框的位置。

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

    此处要讲一下yolo3的预测原理,yolo3的3个特征层分别将整幅图分为19x19、38x38、76x76的网格,每个网络点负责一个区域的检测。

    我们知道特征层的预测结果对应着三个预测框的位置,我们先将其reshape一下,其结果为(N,19,19,3,85),(N,38,38,3,85),(N,76,76,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时,就会对每个特征层进行解码:

    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):
            # input为bs,3*(1+4+num_classes),13,13
    
            # 一共多少张图片
            batch_size = input.size(0)
            # 13,13
            input_height = input.size(2)
            input_width = input.size(3)
    
            # 计算步长
            # 每一个特征点对应原来的图片上多少个像素点
            # 如果特征层为13x13的话,一个特征点就对应原来的图片上的32个像素点
            # 416/13 = 32
            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]
    
            # bs,3*(5+num_classes),13,13 -> bs,3,13,13,(5+num_classes)
            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
    
            # 生成网格,先验框中心,网格左上角 batch_size,3,13,13
            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
    

    5、在原图上进行绘制

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

    YOLOV4的训练

    1、YOLOV4的改进训练技巧

    a)、Mosaic数据增强

    Yolov4的mosaic数据增强参考了CutMix数据增强方式,理论上具有一定的相似性!
    CutMix数据增强方式利用两张图片进行拼接。
    在这里插入图片描述
    但是mosaic利用了四张图片,根据论文所说其拥有一个巨大的优点是丰富检测物体的背景!且在BN计算的时候一下子会计算四张图片的数据!
    就像下图这样:
    在这里插入图片描述
    实现思路如下:
    1、每次读取四张图片。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    2、分别对四张图片进行翻转、缩放、色域变化等,并且按照四个方向位置摆好。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    3、进行图片的组合和框的组合
    在这里插入图片描述

    def rand(a=0, b=1):
        return np.random.rand()*(b-a) + a
    
    def merge_bboxes(bboxes, cutx, cuty):
    
        merge_bbox = []
        for i in range(len(bboxes)):
            for box in bboxes[i]:
                tmp_box = []
                x1,y1,x2,y2 = box[0], box[1], box[2], box[3]
    
                if i == 0:
                    if y1 > cuty or x1 > cutx:
                        continue
                    if y2 >= cuty and y1 <= cuty:
                        y2 = cuty
                        if y2-y1 < 5:
                            continue
                    if x2 >= cutx and x1 <= cutx:
                        x2 = cutx
                        if x2-x1 < 5:
                            continue
                    
                if i == 1:
                    if y2 < cuty or x1 > cutx:
                        continue
    
                    if y2 >= cuty and y1 <= cuty:
                        y1 = cuty
                        if y2-y1 < 5:
                            continue
                    
                    if x2 >= cutx and x1 <= cutx:
                        x2 = cutx
                        if x2-x1 < 5:
                            continue
    
                if i == 2:
                    if y2 < cuty or x2 < cutx:
                        continue
    
                    if y2 >= cuty and y1 <= cuty:
                        y1 = cuty
                        if y2-y1 < 5:
                            continue
    
                    if x2 >= cutx and x1 <= cutx:
                        x1 = cutx
                        if x2-x1 < 5:
                            continue
    
                if i == 3:
                    if y1 > cuty or x2 < cutx:
                        continue
    
                    if y2 >= cuty and y1 <= cuty:
                        y2 = cuty
                        if y2-y1 < 5:
                            continue
    
                    if x2 >= cutx and x1 <= cutx:
                        x1 = cutx
                        if x2-x1 < 5:
                            continue
    
                tmp_box.append(x1)
                tmp_box.append(y1)
                tmp_box.append(x2)
                tmp_box.append(y2)
                tmp_box.append(box[-1])
                merge_bbox.append(tmp_box)
        return merge_bbox
    
    def get_random_data(annotation_line, input_shape, random=True, hue=.1, sat=1.5, val=1.5, proc_img=True):
        '''random preprocessing for real-time data augmentation'''
        h, w = input_shape
        min_offset_x = 0.4
        min_offset_y = 0.4
        scale_low = 1-min(min_offset_x,min_offset_y)
        scale_high = scale_low+0.2
    
        image_datas = [] 
        box_datas = []
        index = 0
    
        place_x = [0,0,int(w*min_offset_x),int(w*min_offset_x)]
        place_y = [0,int(h*min_offset_y),int(w*min_offset_y),0]
        for line in annotation_line:
            # 每一行进行分割
            line_content = line.split()
            # 打开图片
            image = Image.open(line_content[0])
            image = image.convert("RGB") 
            # 图片的大小
            iw, ih = image.size
            # 保存框的位置
            box = np.array([np.array(list(map(int,box.split(',')))) for box in line_content[1:]])
            
            # image.save(str(index)+".jpg")
            # 是否翻转图片
            flip = rand()<.5
            if flip and len(box)>0:
                image = image.transpose(Image.FLIP_LEFT_RIGHT)
                box[:, [0,2]] = iw - box[:, [2,0]]
    
            # 对输入进来的图片进行缩放
            new_ar = w/h
            scale = rand(scale_low, scale_high)
            if new_ar < 1:
                nh = int(scale*h)
                nw = int(nh*new_ar)
            else:
                nw = int(scale*w)
                nh = int(nw/new_ar)
            image = image.resize((nw,nh), Image.BICUBIC)
    
            # 进行色域变换
            hue = rand(-hue, hue)
            sat = rand(1, sat) if rand()<.5 else 1/rand(1, sat)
            val = rand(1, val) if rand()<.5 else 1/rand(1, val)
            x = rgb_to_hsv(np.array(image)/255.)
            x[..., 0] += hue
            x[..., 0][x[..., 0]>1] -= 1
            x[..., 0][x[..., 0]<0] += 1
            x[..., 1] *= sat
            x[..., 2] *= val
            x[x>1] = 1
            x[x<0] = 0
            image = hsv_to_rgb(x)
    
            image = Image.fromarray((image*255).astype(np.uint8))
            # 将图片进行放置,分别对应四张分割图片的位置
            dx = place_x[index]
            dy = place_y[index]
            new_image = Image.new('RGB', (w,h), (128,128,128))
            new_image.paste(image, (dx, dy))
            image_data = np.array(new_image)/255
    
            # Image.fromarray((image_data*255).astype(np.uint8)).save(str(index)+"distort.jpg")
            
            index = index + 1
            box_data = []
            # 对box进行重新处理
            if len(box)>0:
                np.random.shuffle(box)
                box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
                box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
                box[:, 0:2][box[:, 0:2]<0] = 0
                box[:, 2][box[:, 2]>w] = w
                box[:, 3][box[:, 3]>h] = h
                box_w = box[:, 2] - box[:, 0]
                box_h = box[:, 3] - box[:, 1]
                box = box[np.logical_and(box_w>1, box_h>1)]
                box_data = np.zeros((len(box),5))
                box_data[:len(box)] = box
            
            image_datas.append(image_data)
            box_datas.append(box_data)
    
            img = Image.fromarray((image_data*255).astype(np.uint8))
            for j in range(len(box_data)):
                thickness = 3
                left, top, right, bottom  = box_data[j][0:4]
                draw = ImageDraw.Draw(img)
                for i in range(thickness):
                    draw.rectangle([left + i, top + i, right - i, bottom - i],outline=(255,255,255))
            img.show()
    
        
        # 将图片分割,放在一起
        cutx = np.random.randint(int(w*min_offset_x), int(w*(1 - min_offset_x)))
        cuty = np.random.randint(int(h*min_offset_y), int(h*(1 - min_offset_y)))
    
        new_image = np.zeros([h,w,3])
        new_image[:cuty, :cutx, :] = image_datas[0][:cuty, :cutx, :]
        new_image[cuty:, :cutx, :] = image_datas[1][cuty:, :cutx, :]
        new_image[cuty:, cutx:, :] = image_datas[2][cuty:, cutx:, :]
        new_image[:cuty, cutx:, :] = image_datas[3][:cuty, cutx:, :]
    
        # 对框进行进一步的处理
        new_boxes = merge_bboxes(box_datas, cutx, cuty)
    
        return new_image, new_boxes
    

    b)、Label Smoothing平滑

    标签平滑的思想很简单,具体公式如下:

    new_onehot_labels = onehot_labels * (1 - label_smoothing) + label_smoothing / num_classes
    

    当label_smoothing的值为0.01得时候,公式变成如下所示:

    new_onehot_labels = y * (1 - 0.01) + 0.01 / num_classes
    

    其实Label Smoothing平滑就是将标签进行一个平滑,原始的标签是0、1,在平滑后变成0.005(如果是二分类)、0.995,也就是说对分类准确做了一点惩罚,让模型不可以分类的太准确,太准确容易过拟合。

    实现代码如下:

    #---------------------------------------------------#
    #   平滑标签
    #---------------------------------------------------#
    def smooth_labels(y_true, label_smoothing,num_classes):
        return y_true * (1.0 - label_smoothing) + label_smoothing / num_classes
    

    c)、CIOU

    IoU是比值的概念,对目标物体的scale是不敏感的。然而常用的BBox的回归损失优化和IoU优化不是完全等价的,寻常的IoU无法直接优化没有重叠的部分。

    于是有人提出直接使用IOU作为回归优化loss,CIOU是其中非常优秀的一种想法。

    CIOU将目标与anchor之间的距离,重叠率、尺度以及惩罚项都考虑进去,使得目标框回归变得更加稳定,不会像IoU和GIoU一样出现训练过程中发散等问题。而惩罚因子把预测框长宽比拟合目标框的长宽比考虑进去。

    在这里插入图片描述
    CIOU公式如下
    CIOU=IOUρ2(b,bgt)c2αv CIOU = IOU - \frac{\rho^2(b,b^{gt})}{c^2} - \alpha v
    其中,ρ2(b,bgt)\rho^2(b,b^{gt})分别代表了预测框和真实框的中心点的欧式距离。 c代表的是能够同时包含预测框和真实框的最小闭包区域的对角线距离。

    α\alphavv的公式如下
    α=v1IOU+v \alpha = \frac{v}{1-IOU+v}
    v=4π2(arctanwgthgtarctanwh)2 v = \frac{4}{\pi ^2}(arctan\frac{w^{gt}}{h^{gt}}-arctan\frac{w}{h})^2
    把1-CIOU就可以得到相应的LOSS了。
    LOSSCIOU=1IOU+ρ2(b,bgt)c2+αv LOSS_{CIOU} = 1 - IOU + \frac{\rho^2(b,b^{gt})}{c^2} + \alpha v

    def box_ciou(b1, b2):
        """
        输入为:
        ----------
        b1: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
        b2: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
    
        返回为:
        -------
        ciou: tensor, shape=(batch, feat_w, feat_h, anchor_num, 1)
        """
        # 求出预测框左上角右下角
        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
        # 求出真实框左上角右下角
        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
    
        # 求真实框和预测框所有的iou
        intersect_mins = torch.max(b1_mins, b2_mins)
        intersect_maxes = torch.min(b1_maxes, b2_maxes)
        intersect_wh = torch.max(intersect_maxes - intersect_mins, torch.zeros_like(intersect_maxes))
        intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
        b1_area = b1_wh[..., 0] * b1_wh[..., 1]
        b2_area = b2_wh[..., 0] * b2_wh[..., 1]
        union_area = b1_area + b2_area - intersect_area
        iou = intersect_area / (union_area + 1e-6)
    
        # 计算中心的差距
        center_distance = torch.sum(torch.pow((b1_xy - b2_xy), 2), axis=-1)
        # 找到包裹两个框的最小框的左上角和右下角
        enclose_mins = torch.min(b1_mins, b2_mins)
        enclose_maxes = torch.max(b1_maxes, b2_maxes)
        enclose_wh = torch.max(enclose_maxes - enclose_mins, torch.zeros_like(intersect_maxes))
        # 计算对角线距离
        enclose_diagonal = torch.sum(torch.pow(enclose_wh,2), axis=-1)
        ciou = iou - 1.0 * (center_distance) / (enclose_diagonal + 1e-7)
        
        v = (4 / (math.pi ** 2)) * torch.pow((torch.atan(b1_wh[..., 0]/b1_wh[..., 1]) - torch.atan(b2_wh[..., 0]/b2_wh[..., 1])), 2)
        alpha = v / (1.0 - iou + v)
        ciou = ciou - alpha * v
        return ciou
    

    d)、学习率余弦退火衰减

    余弦退火衰减法,学习率会先上升再下降,这是退火优化法的思想。(关于什么是退火算法可以百度。)

    上升的时候使用线性上升,下降的时候模拟cos函数下降。执行多次。

    效果如图所示:
    在这里插入图片描述
    pytorch有直接实现的函数,可直接调用。

    lr_scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=5, eta_min=1e-5)
    

    2、loss组成

    a)、计算loss所需参数

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

    b)、y_pre是什么

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

    c)、y_true是什么。

    y_true就是一个真实图像中,它的每个真实框对应的(19,19)、(38,38)、(76,76)网格上的偏移位置、长宽与种类。其仍需要编码才能与y_pred的结构一致

    d)、loss的计算过程

    在得到了y_pre和y_true后怎么对比呢?不是简单的减一下!

    loss值需要对三个特征层进行处理,这里以最小的特征层为例。
    1、利用y_true取出该特征层中真实存在目标的点的位置(m,19,19,3,1)及其对应的种类(m,19,19,3,80)。
    2、将prediction的预测值输出进行处理,得到reshape后的预测值y_pre,shape为(m,19,19,3,85)。还有解码后的xy,wh。
    3、对于每一幅图,计算其中所有真实框与预测框的IOU,如果某些预测框和真实框的重合程度大于0.5,则忽略。
    4、计算ciou作为回归的loss,这里只计算正样本的回归loss。
    5、计算置信度的loss,其有两部分构成,第一部分是实际上存在目标的,预测结果中置信度的值与1对比;第二部分是实际上不存在目标的,在第四步中得到其最大IOU的值与0对比。
    6、计算预测种类的loss,其计算的是实际上存在目标的,预测类与真实类的差距。

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

    • 实际存在的框,CIOU LOSS
    • 实际存在的框,预测结果中置信度的值与1对比;实际不存在的框,预测结果中置信度的值与0对比,该部分要去除被忽略的不包含目标的框
    • 实际存在的框,种类预测结果与实际结果的对比

    其实际代码如下:

    
    #---------------------------------------------------#
    #   平滑标签
    #---------------------------------------------------#
    def smooth_labels(y_true, label_smoothing,num_classes):
        return y_true * (1.0 - label_smoothing) + label_smoothing / num_classes
    
    def box_ciou(b1, b2):
        """
        输入为:
        ----------
        b1: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
        b2: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
    
        返回为:
        -------
        ciou: tensor, shape=(batch, feat_w, feat_h, anchor_num, 1)
        """
        # 求出预测框左上角右下角
        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
        # 求出真实框左上角右下角
        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
    
        # 求真实框和预测框所有的iou
        intersect_mins = torch.max(b1_mins, b2_mins)
        intersect_maxes = torch.min(b1_maxes, b2_maxes)
        intersect_wh = torch.max(intersect_maxes - intersect_mins, torch.zeros_like(intersect_maxes))
        intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
        b1_area = b1_wh[..., 0] * b1_wh[..., 1]
        b2_area = b2_wh[..., 0] * b2_wh[..., 1]
        union_area = b1_area + b2_area - intersect_area
        iou = intersect_area / (union_area + 1e-6)
    
        # 计算中心的差距
        center_distance = torch.sum(torch.pow((b1_xy - b2_xy), 2), axis=-1)
        # 找到包裹两个框的最小框的左上角和右下角
        enclose_mins = torch.min(b1_mins, b2_mins)
        enclose_maxes = torch.max(b1_maxes, b2_maxes)
        enclose_wh = torch.max(enclose_maxes - enclose_mins, torch.zeros_like(intersect_maxes))
        # 计算对角线距离
        enclose_diagonal = torch.sum(torch.pow(enclose_wh,2), axis=-1)
        ciou = iou - 1.0 * (center_distance) / (enclose_diagonal + 1e-7)
        
        v = (4 / (math.pi ** 2)) * torch.pow((torch.atan(b1_wh[..., 0]/b1_wh[..., 1]) - torch.atan(b2_wh[..., 0]/b2_wh[..., 1])), 2)
        alpha = v / (1.0 - iou + v)
        ciou = ciou - alpha * v
        return ciou
    
    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, label_smooth=0, cuda=True):
            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.label_smooth = label_smooth
    
            self.ignore_threshold = 0.5
            self.lambda_conf = 1.0
            self.lambda_cls = 1.0
            self.lambda_loc = 1.0
            self.cuda = cuda
    
        def forward(self, input, targets=None):
            # input为bs,3*(5+num_classes),13,13
            
            # 一共多少张图片
            bs = input.size(0)
            # 特征层的高
            in_h = input.size(2)
            # 特征层的宽
            in_w = input.size(3)
    
            # 计算步长
            # 每一个特征点对应原来的图片上多少个像素点
            # 如果特征层为13x13的话,一个特征点就对应原来的图片上的32个像素点
            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]
            # bs,3*(5+num_classes),13,13 -> bs,3,13,13,(5+num_classes)
            prediction = input.view(bs, int(self.num_anchors/3),
                                    self.bbox_attrs, in_h, in_w).permute(0, 1, 3, 4, 2).contiguous()
            
            # 对prediction预测进行调整
            conf = torch.sigmoid(prediction[..., 4])  # Conf
            pred_cls = torch.sigmoid(prediction[..., 5:])  # Cls pred.
    
            # 找到哪些先验框内部包含物体
            mask, noobj_mask, t_box, 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, pred_boxes_for_ciou = self.get_ignore(prediction, targets, scaled_anchors, in_w, in_h, noobj_mask)
    
            if self.cuda:
                mask, noobj_mask = mask.cuda(), noobj_mask.cuda()
                box_loss_scale_x, box_loss_scale_y= box_loss_scale_x.cuda(), box_loss_scale_y.cuda()
                tconf, tcls = tconf.cuda(), tcls.cuda()
                pred_boxes_for_ciou = pred_boxes_for_ciou.cuda()
                t_box = t_box.cuda()
    
            box_loss_scale = 2-box_loss_scale_x*box_loss_scale_y
            #  losses.
            ciou = (1 - box_ciou( pred_boxes_for_ciou[mask.bool()], t_box[mask.bool()]))* box_loss_scale[mask.bool()]
    
            loss_loc = torch.sum(ciou / bs)
            loss_conf = torch.sum(BCELoss(conf, mask) * mask / bs) + \
                        torch.sum(BCELoss(conf, mask) * noobj_mask / bs)
                        
            # print(smooth_labels(tcls[mask == 1],self.label_smooth,self.num_classes))
            loss_cls = torch.sum(BCELoss(pred_cls[mask == 1], smooth_labels(tcls[mask == 1],self.label_smooth,self.num_classes))/bs)
            # print(loss_loc,loss_conf,loss_cls)
            loss = loss_conf * self.lambda_conf + loss_cls * self.lambda_cls + loss_loc * self.lambda_loc
            return loss, loss_conf.item(), loss_cls.item(), loss_loc.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)
            t_box = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, 4, 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
                        ty[b, best_n, gj, gi] = gy
                        # 计算先验框宽高调整参数
                        tw[b, best_n, gj, gi] = gw
                        th[b, best_n, gj, gi] = gh
                        # 用于获得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
            t_box[...,0] = tx
            t_box[...,1] = ty
            t_box[...,2] = tw
            t_box[...,3] = th
            return mask, noobj_mask, t_box, 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]
            # 先验框的中心位置的调整参数
            x = torch.sigmoid(prediction[..., 0])  
            y = torch.sigmoid(prediction[..., 1])
            # 先验框的宽高调整参数
            w = prediction[..., 2]  # Width
            h = prediction[..., 3]  # Height
    
            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(bs*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(bs*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(bs, 1).repeat(1, 1, in_h * in_w).view(w.shape)
            anchor_h = anchor_h.repeat(bs, 1).repeat(1, 1, in_h * in_w).view(h.shape)
            
            # 计算调整后的先验框中心与宽高
            pred_boxes = FloatTensor(prediction[..., :4].shape)
            pred_boxes[..., 0] = x + grid_x
            pred_boxes[..., 1] = y + grid_y
            pred_boxes[..., 2] = torch.exp(w) * anchor_w
            pred_boxes[..., 3] = torch.exp(h) * anchor_h
            for i in range(bs):
                pred_boxes_for_ignore = pred_boxes[i]
                pred_boxes_for_ignore = pred_boxes_for_ignore.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).type(FloatTensor)
    
                    anch_ious = bbox_iou(gt_box, pred_boxes_for_ignore, x1y1x2y2=False)
                    anch_ious = anch_ious.view(pred_boxes[i].size()[:3])
                    noobj_mask[i][anch_ious>self.ignore_threshold] = 0
            return noobj_mask, pred_boxes
    

    训练自己的YOLOV4模型

    yolo4整体的文件夹构架如下:
    在这里插入图片描述
    本文使用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即可开始训练。
    在这里插入图片描述

    展开全文
  • 前言:写这篇文章主要是介绍一下python的环境搭建和pycharm的安装配置,适合零基础的同学观看。这篇文章你会学到python的环境搭建和python比较好用的IDE pycharm的安装与基础配置。 运行环境:window 64位操作系统...

    前言:写这篇文章主要是介绍一下python的环境搭建和pycharm的安装配置,适合零基础的同学观看。这篇文章你会学到python的环境搭建和python比较好用的IDE pycharm的安装与基础配置。

    运行环境:window 64位操作系统。

    没想到这么多的人看这篇文章,并且接连不断给我发邮箱,问问题,这篇文章不是用markdown写的,不好改,我重新写了一份,放在我的博客里面,这里是地址:https://yaoguangju.github.io/2018/12/27/python%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E5%92%8Cpycharm%E7%9A%84%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE%E5%8F%8A%E6%B1%89%E5%8C%96%EF%BC%88%E9%9B%B6%E5%9F%BA%E7%A1%80%E5%B0%8F%E7%99%BD%E7%89%88%EF%BC%89/#more

    直接看这个就好了

    一.python环境的搭建

     

    1.下载python(这里以python3.6为例)

        step 1:打开下载网址:https://www.python.org/downloads/windows/

        step 2:我这里选着python3的版本

                    

        step 3:选择python的可执行文件安装包

                      

                       

     

    2.安装python(这里以python3.6为例)

        step 1:下载完成后就可以安装了,基本傻瓜式安装,不过要强调几点,如下图(python3.6已经可以自动添加环境变量                     如果你要安装python2那么你手动配置环境变量,具体方法自行百度,这里不做赘述!)

                       

                        

        step 2:安装完成后,我们来检查一下python是否安装成功,打开cmd命令,输入python,回车,若显示类似下图,证明环                   境搭建完成。

                      

        step 3:测试输出hello world,从开始菜单打开python自带的IDE,输入如下代码,然后回车。

               print('hello world')

                      

     至此,我们的python环境搭建完成。

    二.pycharm的安装与配置

    1.pycharm的下载

        step 1:打开下载网址:http://www.jetbrains.com/pycharm/

        step 2:点击下载按钮。

                      

        step 3:专业版是收费的,社区版是免费的,并且社区版新手使用足够,我们这里下载社区版。

                      

    2.pycharm的安装

        pycharm的安装依然是傻瓜化安装,基本一路next,这里只强调一点

                    

     

     

    3.pycharm的入门基础配置

        step 1:打开pycharm,直接点击OK。

                      

        step 2:接下来是一些按照顺序摆放的配置图片。完成pycharm的基本配置

                     

                      

                          

                      

     

    4.pycharm的python解释器的搭建

        同样,接下来是一些按照顺序摆放的配置图片。完成pycharm的python解释器的搭建

                      

                      

     

    5.pycharm的建立一个新项目

                        首先打开pycharm,接下来是一些按照顺序摆放的配置图片。完成pycharm的一个项目创建。

                         

                          

                          

                           

                           

                            

                            

    至此,pycharm已经全部安装完成。

    6.pycharm汉化

        step 1:下载pycharm汉化包 链接:https://github.com/yaoguangju/shared_documents   

        step 2:将pycharm安装目录下的lib文件夹内下的resources_en.jar文件复制出来,并更名为resources_cn.jar

        step 3:双击打开resources_cn.jar(注意是打开而不是解压出来),将下载的汉化包zh_CN目录下的所有文件拖到刚才打

                     开的resources_cn.jar文件内的messages目录中,并保存。

        step 4:将resources_cn.jar文件复制回lib文件夹内。

        注意:建议不要使用汉化版,会导致一些小问题,例如设置界面显示不完整等

        有问题或者是链接挂掉的话可以找我 

        email:yao_guangju@163.com

     

     

     

                      

     

                      

     

     

     

     

                     

    展开全文
  • redis集群搭建(非常详细,适合新手)

    万次阅读 多人点赞 2018-10-01 16:33:03
    redis集群搭建 在开始redis集群搭建之前,我们先简单回顾一下redis单机版的搭建过程 下载redis压缩包,然后解压压缩文件; 进入到解压缩后的redis文件目录(此时可以看到Makefile文件),编译redis源文件; 把编译...

    redis集群搭建

    在开始redis集群搭建之前,我们先简单回顾一下redis单机版的搭建过程

    1. 下载redis压缩包,然后解压压缩文件;
    2. 进入到解压缩后的redis文件目录(此时可以看到Makefile文件),编译redis源文件;
    3. 把编译好的redis源文件安装到/usr/local/redis目录下,如果/local目录下没有redis目录,会自动新建redis目录;
    4. 进入/usr/local/redis/bin目录,直接./redis-server启动redis(此时为前端启动redis);
    5. 将redis启动方式改为后端启动,具体做法:把解压缩的redis文件下的redis.conf文件复制到/usr/local/redis/bin目录下,然后修改该redis.conf文件->daemonize:no 改为daemonize:yse;
    6. 在/bin目录下通过./redis-server redis.conf启动redis(此时为后台启动)。
      综上redis单机版安装启动完成。
      具体详细带图步骤请参考 -> redis入门
      请原谅我的啰嗦,ok,接着咱们回到本次话题----redis集群搭建!

    一、Redis Cluster(Redis集群)简介

    • redis是一个开源的key value存储系统,受到了广大互联网公司的青睐。redis3.0版本之前只支持单例模式,在3.0版本及以后才支持集群,我这里用的是redis3.0.0版本;
    • redis集群采用P2P模式,是完全去中心化的,不存在中心节点或者代理节点;
    • redis集群是没有统一的入口的,客户端(client)连接集群的时候连接集群中的任意节点(node)即可,集群内部的节点是相互通信的(PING-PONG机制),每个节点都是一个redis实例;
    • 为了实现集群的高可用,即判断节点是否健康(能否正常使用),redis-cluster有这么一个投票容错机制:如果集群中超过半数的节点投票认为某个节点挂了,那么这个节点就挂了(fail)。这是判断节点是否挂了的方法;
    • 那么如何判断集群是否挂了呢? -> 如果集群中任意一个节点挂了,而且该节点没有从节点(备份节点),那么这个集群就挂了。这是判断集群是否挂了的方法;
    • 那么为什么任意一个节点挂了(没有从节点)这个集群就挂了呢? -> 因为集群内置了16384个slot(哈希槽),并且把所有的物理节点映射到了这16384[0-16383]个slot上,或者说把这些slot均等的分配给了各个节点。当需要在Redis集群存放一个数据(key-value)时,redis会先对这个key进行crc16算法,然后得到一个结果。再把这个结果对16384进行求余,这个余数会对应[0-16383]其中一个槽,进而决定key-value存储到哪个节点中。所以一旦某个节点挂了,该节点对应的slot就无法使用,那么就会导致集群无法正常工作。
    • 综上所述,每个Redis集群理论上最多可以有16384个节点。

    二、集群搭建需要的环境
    2.1 Redis集群至少需要3个节点,因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了,所以2个节点无法构成集群。
    2.2 要保证集群的高可用,需要每个节点都有从节点,也就是备份节点,所以Redis集群至少需要6台服务器。因为我没有那么多服务器,也启动不了那么多虚拟机,所在这里搭建的是伪分布式集群,即一台服务器虚拟运行6个redis实例,修改端口号为(7001-7006),当然实际生产环境的Redis集群搭建和这里是一样的。
    2.3 安装ruby
    三、集群搭建具体步骤如下(注意要关闭防火墙)
    3.1 在usr/local目录下新建redis-cluster目录,用于存放集群节点
    新建Redis集群目录
    3.2 把redis目录下的bin目录下的所有文件复制到/usr/local/redis-cluster/redis01目录下,不用担心这里没有redis01目录,会自动创建的。操作命令如下(注意当前所在路径):

    cp -r redis/bin/ redis-cluster/redis01
    

    在这里插入图片描述
    3.3 删除redis01目录下的快照文件dump.rdb,并且修改该目录下的redis.cnf文件,具体修改两处地方:一是端口号修改为7001,二是开启集群创建模式,打开注释即可。分别如下图所示:
    删除dump.rdb文件
    删除dump.rdb文件
    修改端口号为7001,默认是6379
    在这里插入图片描述
    将cluster-enabled yes 的注释打开
    在这里插入图片描述
    3.4 将redis-cluster/redis01文件复制5份到redis-cluster目录下(redis02-redis06),创建6个redis实例,模拟Redis集群的6个节点。然后将其余5个文件下的redis.conf里面的端口号分别修改为7002-7006。分别如下图所示:
    创建redis02-06目录
    在这里插入图片描述

    分别修改redis.conf文件端口号为7002-7006
    在这里插入图片描述
    3.5 接着启动所有redis节点,由于一个一个启动太麻烦了,所以在这里创建一个批量启动redis节点的脚本文件,命令为start-all.sh,文件内容如下:

    cd redis01
    ./redis-server redis.conf
    cd ..
    cd redis02
    ./redis-server redis.conf
    cd ..
    cd redis03
    ./redis-server redis.conf
    cd ..
    cd redis04
    ./redis-server redis.conf
    cd ..
    cd redis05
    ./redis-server redis.conf
    cd ..
    cd redis06
    ./redis-server redis.conf
    cd ..
    
    

    3.6 创建好启动脚本文件之后,需要修改该脚本的权限,使之能够执行,指令如下:

    chmod +x start-all.sh
    

    在这里插入图片描述
    3.7 执行start-all.sh脚本,启动6个redis节点
    在这里插入图片描述
    3.8 ok,至此6个redis节点启动成功,接下来正式开启搭建集群,以上都是准备条件。大家不要觉得图片多看起来冗长所以觉得麻烦,其实以上步骤也就一句话的事情:创建6个redis实例(6个节点)并启动。
    要搭建集群的话,需要使用一个工具(脚本文件),这个工具在redis解压文件的源代码里。因为这个工具是一个ruby脚本文件,所以这个工具的运行需要ruby的运行环境,就相当于java语言的运行需要在jvm上。所以需要安装ruby,指令如下:

    yum install ruby
    

    然后需要把ruby相关的包安装到服务器,我这里用的是redis-3.0.0.gem,大家需要注意的是:redis的版本和ruby包的版本最好保持一致。
    将Ruby包安装到服务器:需要先下载再安装,如图
    在这里插入图片描述
    安装命令如下:

    gem install redis-3.0.0.gem
    

    在这里插入图片描述

    3.9 上一步中已经把ruby工具所需要的运行环境和ruby包安装好了,接下来需要把这个ruby脚本工具复制到usr/local/redis-cluster目录下。那么这个ruby脚本工具在哪里呢?之前提到过,在redis解压文件的源代码里,即redis/src目录下的redis-trib.rb文件。
    在这里插入图片描述
    在这里插入图片描述
    3.10 将该ruby工具(redis-trib.rb)复制到redis-cluster目录下,指令如下:

    cp redis-trib.rb /usr/local/redis-cluster
    

    然后使用该脚本文件搭建集群,指令如下:

    ./redis-trib.rb create --replicas 1 47.106.219.251:7001 47.106.219.251:7002 47.106.219.251:7003 47.106.219.251:7004 47.106.219.251:7005 47.106.219.251:7006
    

    注意:此处大家应该根据自己的服务器ip输入对应的ip地址!
    在这里插入图片描述

    中途有个地方需要手动输入yes即可
    在这里插入图片描述
    至此,Redi集群搭建成功!大家注意最后一段文字,显示了每个节点所分配的slots(哈希槽),这里总共6个节点,其中3个是从节点,所以3个主节点分别映射了0-5460、5461-10922、10933-16383solts。

    3.11 最后连接集群节点,连接任意一个即可:

    redis01/redis-cli -p 7001 -c 
    

    注意:一定要加上-c,不然节点之间是无法自动跳转的!如下图可以看到,存储的数据(key-value)是均匀分配到不同的节点的:
    在这里插入图片描述

    四、结语
    呼~~~长舒一口气…终于搭建好了Redis集群。
    整个过程其实挺简单,本篇主要正对入门级别的小伙伴,插入了很多图片,所以显得冗长,希望大家多多理解,如果不当之处,还望及时指正~

    最后,加上两条redis集群基本命令:
    1.查看当前集群信息

    cluster info
    

    2.查看集群里有多少个节点

    cluster nodes
    
    展开全文
  • SVN服务器搭建

    万次阅读 2020-08-20 18:50:49
    Windows系统SVN服务器搭建与使用 ...

    Windows系统SVN服务器搭建与使用

  • 下载svn:https://tortoisesvn.net/downloads.zh.html
  • 下载svn服务器:https://www.visualsvn.com/server/download/
    (如若只是用svn更新提交代码可不下载服务器)
    一、下载好svn后点击鼠标右键会看到如下标识。
    在这里插入图片描述
    二、打开svn服务器,新建一个版本库
    在这里插入图片描述三、点击下一步在这里插入图片描述
    四、输入仓库名称project(名称自定义输入)
    在这里插入图片描述
    五、直接下一步
    在这里插入图片描述
    六、再下一步,直至出现了这个界面,版本库也就创建完成了,点击完成
    在这里插入图片描述
    七、然后右键点击刚刚创建的版本库,复制版本库的路径
    在这里插入图片描述
    八、新建一个空文件夹,点击右键,再点击检出,将刚刚复制的路径粘贴在版本库URL处:
    在这里插入图片描述
    九、点击确定即可。此后将项目代码放至project文件夹中进行更新和提交的操作即可。
    在这里插入图片描述
  • 展开全文
  • 【1】zookeeper集群搭建

    万次阅读 2019-12-10 14:16:45
    目录 知识点1:搭建Zookeeper集群 ...真实的集群是需要部署在不同的服务器上的,但是在我们测试时同时启动十几个虚拟机内存会吃不消,所以我们通常会搭建伪集群,也就是把所有的服务都搭建在一台虚...
  • springboot单测之mock搭建

    万次阅读 2020-08-29 23:14:00
    springboot单测之mock搭建 简述:在springboot项目中依赖了很多start组件,且组件对环境、数据库、注册中心等都有依赖,我们像mock调这种第三方接口,只让自己项目的springBena进行初始化 1、因为我们要初始化...
  • 从零使用qemu模拟器搭建arm运行环境

    万次阅读 多人点赞 2015-07-04 15:26:17
    本文从零开始介绍如何搭建qemu+arm的运行环境
  • 如何利用腾讯云服务器搭建个人网站

    万次阅读 多人点赞 2018-07-29 12:39:40
    你是否想要搭建一个网站,却苦苦找不到方法,你是否看到别人搭建的网站,自己羡慕不已,今天,就教大家来搭建一个简单的个人网站。 在这里,我采用的是腾讯云服务器搭建的。首先,需要注册腾讯云账号,登录腾讯云,...
  • 利用nginx搭建RTMP视频点播、直播、HLS服务器

    万次阅读 多人点赞 2016-03-09 23:48:32
    开发环境 Ubuntu 14.04 server ...nginx的服务器的搭建安装nginx的依赖库sudo apt-get update sudo apt-get install libpcre3 libpcre3-dev sudo apt-get install openssl libssl-dev配置并编译nginx 使用nginx的默
  • nginx学习:搭建静态资源服务器

    万次阅读 多人点赞 2018-06-12 16:03:22
    1.搭建静态资源服务器我电脑上的work文件夹下面有很多图片,我想通过nginx搭建静态资源服务器,通过在地址栏输入ip+port的方式完成目录的映射找到nginx安装目录,打开/conf/nginx.conf配置文件,添加一个虚拟主机 ...
  • Android Studio开发环境搭建

    万次阅读 多人点赞 2019-01-05 23:29:49
    目前关于AS(Android Studio)的环境搭建网上已经有很多教程了,但是实际操作起来还是会有各种意想不到的问题,对于大多数开发者来说可能还是会有困难,本教程根据笔者亲身实践,将搭建过程中的各种问题总结出来,方便...
  • Qt5.7 + VS2015 环境搭建

    万次阅读 多人点赞 2016-12-29 22:13:25
    简述之前介绍过 Qt5.x 的环境搭建,5.7 开始支持 VS2015,为了使用新的开发环境(典型的强迫症),不得不再次进行 Qt5.7 + VS2015 的环境搭建。除了之前介绍的搭建细节之外,其实中间有很多需要注意的部分。下面,...
  • 利用旧电脑搭建NAS(黑群晖)

    万次阅读 多人点赞 2018-09-30 11:38:19
    用来搭建nas 任意可用电脑1个 用户调试nas 软件 名称 描述 芯片无忧 查询u盘的PID,VID OSFMount 用于引导img映像文件 Win32DiskImager 用户将映像文件写入到u盘 系统文件 名称...
  • 如何搭建个人博客(详细图解)

    万次阅读 多人点赞 2019-07-03 21:46:35
    一目标:搭建个人博客。 二各种搭建途径。 途径1:略 途径2: 2.1利用emlog+服务器建立网站→123…→… 我的:https://justgogoal.com/ 2.2利用Gridea+GitHub pages搭建个人博客(较简单,推荐) 我的:...
  • ElasticSearch7.6集群搭建

    万次阅读 2020-03-30 16:30:42
    ElasticSearch7.6集群搭建
  • 搭建私人邮件服务器

    万次阅读 多人点赞 2019-05-24 14:26:54
    怎样使用本地服务器搭建一个邮箱,这样就可以脱离qq或者其他企业邮箱的限制,即可以做到节省成本,又可以得到收发邮件的一个保密性。 这里我们先展示一下本地搭建邮箱服务器后的成功例子: 可以看到,这里qq邮箱...
  • phpwamp是目前方便便捷的绿色软件,采用用PHPWAMP搭建本地网站运行环境,然后如何利用phpwamp配置并测试好网站,再手把手教学员如何传到网上运行。
  • 搭建OJ系统

    万次阅读 2020-07-13 04:30:40
    搭建个自己的OJ系统玩玩
  • 搭建邮件服务器,过程非常简单

    万次阅读 多人点赞 2017-11-10 15:54:59
    搭建邮件服务器,过程非常简单,只需几个步骤即可。通常在Linux搭建邮件服务器是需要安装很多软件和配置文件的,所以我们可以使用一个开源邮件服务器软件来搭建的。
  • 这是一篇有关如何使用 Github Pages 和 Hexo 搭建属于自己独立博客的详尽教程,本人是软件工程专业本科生,目前只学习了C和C++编程语言,对网站开发的有关知识几乎为零,这也是我搭建好自己的博客之后写的第一篇博客...
  • 腾讯云搭建Socks5多IP代理服务器实现游戏单窗口单IP腾讯云多IP Socks5搭建教程 配合代理工具实现 单窗口单IP1.多IP服务器选择2.服务器购买功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与...
  • PHP环境搭建

    千人学习 2017-02-15 20:53:21
    本课程旨在讲解LAMP平台的搭建与部署,学完本课程,PHP相关学习人员能够独立的搭建PHP的运行及开发环境LAMP平台,学会开发中常用的SSH工具XSHELL及FTP工具filezilla等工具的使用及作用,掌握部分linux命令。
  • 搭建公司wiki系统

    万次阅读 2017-07-13 20:21:49
    搭建公司wiki系统-confluence因技术总监要求,在公司研发部门用wiki来管理文档。作为运维,这份差事就落到我头上了。 不知道confluence是什么的可以去官网看看:https://www.atlassian.com/software/confluence
  • jeesite 框架搭建与配置

    万次阅读 多人点赞 2016-09-18 20:20:49
    一、搭建环境: 1、这是我的Eclipse、Maven等环境,此步骤略过。 注意:以前试过jdk1.6以上都可以使用jeesite 2、下载最新版本jeesite源码: 下载地址: [GitHub/jeesite] ...下载时注意jeesite的版本,以前先...
  • harbor搭建与使用

    万次阅读 热门讨论 2019-01-09 12:47:31
    harbor git 地址
  • 【MQTT】在Windows下搭建MQTT服务器

    万次阅读 多人点赞 2016-09-29 18:15:02
    最近在项目中要使用MQTT协议,需要搭建一个MQTT服务器来进行调试,在网络上找了一天,找到的大多数都是MQTT客户端,最后发现这篇博客写的教程可以使用,特此记录。
  • Python进阶(三十六)-Web框架Django项目搭建全过程

    万次阅读 多人点赞 2017-04-15 08:53:16
    Python Web框架Django项目搭建全过程   IDE说明: Win7系统 Python:3.5 Django:1.10 Pymysql:0.7.10 Mysql:5.5   Django 是由 Python 开发的一个免费的开源网站框架,可以用于快速搭建高性能,优雅的网站!...
  • 如何搭建自己的网站

    万次阅读 多人点赞 2018-01-29 15:20:18
    当你看到我这个博客的时候,我...好的现在就开始手把手教你如何搭建属于自己的个性网站... 首先,你需要购买一台自己的服务器,这个服务器的作用就是用来存放你的项目,已达到24小时随时随地进行访问。国内服务器...
  • 前端开发环境搭建

    万次阅读 多人点赞 2019-07-28 13:43:57
    前端开发环境搭建

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 868,318
精华内容 347,327
关键字:

搭建