精华内容
下载资源
问答
  • 图的连通和最小生成树 四 图的连通 生成树:是一个极小连通子图,它含有图中全部顶点,但只有n-1条边。 生成森林:由若干棵生成树组成,含全部顶点,但构成这些树的边是最少的。 1:对连通图进行遍历,得到的是...

    图的连通性和最小生成树

    四 图的连通性

    生成树:是一个极小连通子图,它含有图中全部顶点,但只有n-1条边。

    生成森林:由若干棵生成树组成,含全部顶点,但构成这些树的边是最少的。

    1:对连通图进行遍历,得到的是什么?

    ——得到的将是一个极小连通子图,即图的生成树!

    由深度优先搜索得到的生成树,称为深度优先搜索生成树。

    由广度优先搜索得到的生成树,称为广度优先搜索生成树。

    2:对非连通图进行遍历,得到的是什么?

    —— 得到的将是各连通分量的生成树,即图的生成森林!

    4.1 无向图的连通分量和生成树

    在对无向图进行遍历时,对于连通图,仅需从图中任何一个顶点出发,进行深度优先搜索或广度优先搜索,便可访问到图中所有顶点。对于非连通图,则需从多个顶点出发进行搜索,而每次从一个新的起始点出发进行搜索的过程中得到的顶点访问序列恰为其各个连通分量中的顶点集。

    (a)中的图是非连通图,若从顶点a 开始进行深度优先搜索遍历,在选择未曾访问的邻接点时按照顶点在图中的位置顺序(即a , b , c , d , e , f , g , h)选择,2 次调用DFS 方法(分别从a、d 出发)得到的访问序列为a , b , c , f , e 和 d , g , h这两个顶点集加上所有依附于它们的边,便构成了非连通图的2个连通分量如图(b)、

    生成树

    设E是连通图G中所有边的集合,则从图中任意一个顶点出发遍历图时,必定将E分成两个子集:Et和Eb,其中Et是遍历图过程中经历的边的集合;Eb是剩余边的集合。显然Et和图中所有顶点一起构成连通图G的极小连通子图,即图G的生成树。并且由深度优先搜索得到的为深度优先搜索生成树;由广度优先搜索得到的为广度优先搜索生成树。

    例如图(d)和图(e)所示(不包括虚线代表的边)分别为图(a)中连通图的深度优先搜索生成树和广度优先搜索生成树,而图中虚线表示的边为集合Eb中的边。

    对于非连通图,每个连通分量中的顶点集以及在遍历时走过的边一起构成若干棵生成树,这些连通分量的生成树组成非连通图的生成树森林。例如图(c)所示为图(a)的深度优先搜索森林,它由2棵深度优先搜索生成树组成。

    求最大连通分量的方法——只能用DFS(深度优先搜索)

    4.2 有向图的强连通分量

    从有向图中某个顶点s出发进行深度优先搜索或广度优先搜索,只能得到顶点s的可达分量,不一定能够得到包含s在内的强连通分量。

    有向图中求强连通分量的算法。

    对于任何有向边e = <u,v>,称R(e)=<v,u>为e的镜像边,即R(e)的起点(终点)就是e的终点(起点)。对于任何有向图G=(V,E),我们称R(E)= {R(e)|e∈E}为E 的镜像边集,也就是说,集合R(E)是由E中各边的镜像边组成。此时,也称R(G)=(V,R(E))为G的镜像图。

    为构造图G = (V,E)中包含顶点s 的强连通分量有如下方法:求出顶点s在图G中的可达分量与顶点s 在R(G)中的可达分量的交集;

    图(a)中灰色的顶点集为a 在G 中的可达分量;图(b)中灰色的顶点为a 在R(G)中的可达分量;图(c)中灰色的顶点为顶点a 在图G 中的可达分量与a在R(G)中的可达分量的交集,它们以及与它们关联的边就构成了包含a 的强连通分量。

    如果需要确定有向图的所有强连通分量,可以从图中每个顶点出发重复上述操作,然而

    这种方法时间复杂度较高,实际上为确定有向图的所有强连通分量,只需要进行两次深度优

    先搜索即可,一次是在有向图G上进行,另一次是在R(G)上进行。

    五、最小生成树

    5.1、最小生成树问题产生

    典型用途:

    欲在n个城市间建立通信网,则n个城市应铺n-1条线路;但因为每条线路都会有对应的经济成本,而n个城市可能有n(n-1)/2条线路,那么,如何选择n–1条线路,使总费用最少?

    数学模型:

    顶点———表示城市,有n个;

    边————表示线路,有n–1条;

    边的权值—表示线路的经济代价;

    连通网——表示n个城市间通信网。

    问题抽象: n个顶点的生成树很多,需要从中选一棵代价最小的生成树,即该树各边的代价之和最小。此树便称为最小生成树MST(Minimum cost Spanning Tree)

    使用不同的遍历图的方法,可以得到不同的生成树;从不同的顶点出发,也可能得到不同的生成树。按照生成树的定义,n个顶点的连通网络的生成树有n个顶点、n-1条边。我们把代价最小(树中每条边上权值之和)的生成树称为图的最小生成树(minimum spanning tree)。

    5.2、最小生成树的求法

    目标:在网的多个生成树中,寻找一个各边权值之和最小的生成树。

    应用:n个城市建网,如何选择n–1条线路,使总费用最少?

    最小生成树的 MST 性质如下:

    若U集是V的一个非空子集,若(u0,v0)是一条最小权值的边,其中u0∈U,v0∈V-U;则(u0, v0)必在最小生成树上。

    Kruskal算法特点:将边归并,适于求稀疏网的最小生成树。

    Prime算法特点:  将顶点归并,与边数无关,适于稠密网。

    这两个算法,都是利用MST性质来构造最小生成树的。

    5.2.1 Kruskal----克鲁斯卡尔算法

    设N ={V,E}是有n个顶点的连通网,

    步骤:

    (1) 首先构造一个只有n个顶点但没有边的非连通图T={V,Φ}, 图中每个顶点自成一个连通分量。

    (2) 当在边集 E 中选到一条具有最小权值的边时,若该边的两个顶点落在T中不同的连通分量上,则将此边加入到生成树的边集合T 中;否则将此边舍去,重新选择一条权值最小的边。

    (3) 如此重复下去,直到所有顶点在同一个连通分量上为止。此时的T即为所求(最小生成树)

           

                    

    计算机实现Kruskal算法?

    算法描述:

    构造非连通图 ST=( V,{ } );

     k = i = 0;   // k计选中的边数

     while (k<n-1) {

        ++i;

       检查边集 E 中第 i 条权值最小的边(u,v);

       若(u,v)加入ST后不使ST中产生回路,则输出边(u,v);  且k++;

    }

     

     

     

    算法实现要点:构成回路的判断: 每个结点增加一个属性set,用于标识该结点所属的集合。可用于判断新的边加入后是否构成回路。

    (1)初始时,令每个顶点的set互不相同;每个边的flag为0

    (2)选出权值最小且flag为0的边

    (3)若该边依附的两个顶点的set 值不同,即非连通,则令该边的flag=1, 选中该边;再令该边依附的两顶点的集合以及两集合中所有顶点的set相同(一种简便的方法是将所有set等于终点的set值的都改成起始点的set值

         若该边依附的两个顶点的set值相同,即连通,则令该边的flag=2, 即舍去该边

    (4)重复上述步骤,直到选出n-1条边为止

    初始状态入上图所示

    5.2.2 Prim----普里姆算法

    取图中任意一个顶点v作为生成树的根,之后往生成树上添加新的顶点w。所添加的顶点应满足下列条件:在生成树的构造过程中,图中 n 个顶点分属两个集合:已落在生成树上的顶点集U 和尚未落在生成树上的顶点集V-U ,则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。

    设:N =(V,E)是个连通网,另设U为最小生成树的顶点集,TE为最小生成树的边集。

    构造步骤:

    (1)初始状态:U ={u0},(u0∈V),  TE={  },

    (2)从E中选择顶点分别属于U、V-U两个集合、且权值最小的边(u0,v0),将顶点v0归并到集合U中,边(u0, v0)归并到TE中;

    (3)直到U=V为止。此时TE中必有n-1条边,T=(V,{TE})就是最小生成树。

    Prime算法特点: 将顶点归并,与边数无关,适于稠密网。时间复杂度O(eloge),故采用邻接矩阵作为图的存储。

    展开全文
  • 睿智的目标检测32——TF2搭建YoloV4目标检测平台(tensorflow2)学习前言什么是YOLOV4代码下载YOLOV4改进的部分(不完全)YOLOV4结构解析1、主干特征提取网络Backbone2、特征金字塔3、YoloHead利用获得到的特征进行...

    学习前言

    做一个TF2……和Keras挺像的!
    在这里插入图片描述

    什么是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/103276106

    代码下载

    https://github.com/bubbliiiing/yolov4-tf2
    喜欢的可以给个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模块组合而成

    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
    

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

    #--------------------------------------------------------------------#
    #   CSPdarknet的结构块
    #   首先利用ZeroPadding2D和一个步长为2x2的卷积块进行高和宽的压缩
    #   然后建立一个大的残差边shortconv、这个大残差边绕过了很多的残差结构
    #   主干部分会对num_blocks进行循环,循环内部是残差结构。
    #   对于整个CSPdarknet的结构块,就是一个大残差块+内部多个小残差块
    #--------------------------------------------------------------------#
    def resblock_body(x, num_filters, num_blocks, all_narrow=True):
        #----------------------------------------------------------------#
        #   利用ZeroPadding2D和一个步长为2x2的卷积块进行高和宽的压缩
        #----------------------------------------------------------------#
        preconv1 = ZeroPadding2D(((1,0),(1,0)))(x)
        preconv1 = DarknetConv2D_BN_Mish(num_filters, (3,3), strides=(2,2))(preconv1)
    
        #--------------------------------------------------------------------#
        #   然后建立一个大的残差边shortconv、这个大残差边绕过了很多的残差结构
        #--------------------------------------------------------------------#
        shortconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)
    
        #----------------------------------------------------------------#
        #   主干部分会对num_blocks进行循环,循环内部是残差结构。
        #----------------------------------------------------------------#
        mainconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)
        for i in range(num_blocks):
            y = compose(
                    DarknetConv2D_BN_Mish(num_filters//2, (1,1)),
                    DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (3,3)))(mainconv)
            mainconv = Add()([mainconv,y])
        postconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(mainconv)
    
        #----------------------------------------------------------------#
        #   将大残差边再堆叠回来
        #----------------------------------------------------------------#
        route = Concatenate()([postconv, shortconv])
    
        # 最后对通道数进行整合
        return DarknetConv2D_BN_Mish(num_filters, (1,1))(route)
    

    全部实现代码为:

    from functools import wraps
    
    from tensorflow.keras import backend as K
    from tensorflow.keras.initializers import RandomNormal
    from tensorflow.keras.layers import (Add, BatchNormalization, Concatenate,
                                         Conv2D, Layer, ZeroPadding2D)
    from tensorflow.keras.regularizers import l2
    from utils.utils import compose
    
    
    class Mish(Layer):
        def __init__(self, **kwargs):
            super(Mish, self).__init__(**kwargs)
            self.supports_masking = True
    
        def call(self, inputs):
            return inputs * K.tanh(K.softplus(inputs))
    
        def get_config(self):
            config = super(Mish, self).get_config()
            return config
    
        def compute_output_shape(self, input_shape):
            return input_shape
    
    #------------------------------------------------------#
    #   单次卷积DarknetConv2D
    #   如果步长为2则自己设定padding方式。
    #------------------------------------------------------#
    @wraps(Conv2D)
    def DarknetConv2D(*args, **kwargs):
        darknet_conv_kwargs = {'kernel_initializer' : RandomNormal(stddev=0.02), '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 + Mish
    #---------------------------------------------------#
    def DarknetConv2D_BN_Mish(*args, **kwargs):
        no_bias_kwargs = {'use_bias': False}
        no_bias_kwargs.update(kwargs)
        return compose(
            DarknetConv2D(*args, **no_bias_kwargs),
            BatchNormalization(),
            Mish())
    
    #--------------------------------------------------------------------#
    #   CSPdarknet的结构块
    #   首先利用ZeroPadding2D和一个步长为2x2的卷积块进行高和宽的压缩
    #   然后建立一个大的残差边shortconv、这个大残差边绕过了很多的残差结构
    #   主干部分会对num_blocks进行循环,循环内部是残差结构。
    #   对于整个CSPdarknet的结构块,就是一个大残差块+内部多个小残差块
    #--------------------------------------------------------------------#
    def resblock_body(x, num_filters, num_blocks, all_narrow=True):
        #----------------------------------------------------------------#
        #   利用ZeroPadding2D和一个步长为2x2的卷积块进行高和宽的压缩
        #----------------------------------------------------------------#
        preconv1 = ZeroPadding2D(((1,0),(1,0)))(x)
        preconv1 = DarknetConv2D_BN_Mish(num_filters, (3,3), strides=(2,2))(preconv1)
    
        #--------------------------------------------------------------------#
        #   然后建立一个大的残差边shortconv、这个大残差边绕过了很多的残差结构
        #--------------------------------------------------------------------#
        shortconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)
    
        #----------------------------------------------------------------#
        #   主干部分会对num_blocks进行循环,循环内部是残差结构。
        #----------------------------------------------------------------#
        mainconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)
        for i in range(num_blocks):
            y = compose(
                    DarknetConv2D_BN_Mish(num_filters//2, (1,1)),
                    DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (3,3)))(mainconv)
            mainconv = Add()([mainconv,y])
        postconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(mainconv)
    
        #----------------------------------------------------------------#
        #   将大残差边再堆叠回来
        #----------------------------------------------------------------#
        route = Concatenate()([postconv, shortconv])
    
        # 最后对通道数进行整合
        return DarknetConv2D_BN_Mish(num_filters, (1,1))(route)
    
    #---------------------------------------------------#
    #   CSPdarknet53 的主体部分
    #   输入为一张416x416x3的图片
    #   输出为三个有效特征层
    #---------------------------------------------------#
    def darknet_body(x):
        x = DarknetConv2D_BN_Mish(32, (3,3))(x)
        x = resblock_body(x, 64, 1, False)
        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、特征金字塔

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

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

    # 使用了SPP结构,即不同尺度的最大池化后堆叠。
    maxpool1 = MaxPooling2D(pool_size=(13,13), strides=(1,1), padding='same')(P5)
    maxpool2 = MaxPooling2D(pool_size=(9,9), strides=(1,1), padding='same')(P5)
    maxpool3 = MaxPooling2D(pool_size=(5,5), strides=(1,1), padding='same')(P5)
    P5 = Concatenate()([maxpool1, maxpool2, maxpool3, P5])
    

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

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

    #---------------------------------------------------#
    #   Panet网络的构建,并且获得预测结果
    #---------------------------------------------------#
    def yolo_body(input_shape, anchors_mask, num_classes):
        inputs      = Input(input_shape)
        #---------------------------------------------------#   
        #   生成CSPdarknet53的主干模型
        #   获得三个有效特征层,他们的shape分别是:
        #   52,52,256
        #   26,26,512
        #   13,13,1024
        #---------------------------------------------------#
        feat1,feat2,feat3 = darknet_body(inputs)
    
        # 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512 -> 13,13,2048 -> 13,13,512 -> 13,13,1024 -> 13,13,512
        P5 = DarknetConv2D_BN_Leaky(512, (1,1))(feat3)
        P5 = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
        P5 = DarknetConv2D_BN_Leaky(512, (1,1))(P5)
        # 使用了SPP结构,即不同尺度的最大池化后堆叠。
        maxpool1 = MaxPooling2D(pool_size=(13,13), strides=(1,1), padding='same')(P5)
        maxpool2 = MaxPooling2D(pool_size=(9,9), strides=(1,1), padding='same')(P5)
        maxpool3 = MaxPooling2D(pool_size=(5,5), strides=(1,1), padding='same')(P5)
        P5 = Concatenate()([maxpool1, maxpool2, maxpool3, P5])
        P5 = DarknetConv2D_BN_Leaky(512, (1,1))(P5)
        P5 = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
        P5 = DarknetConv2D_BN_Leaky(512, (1,1))(P5)
    
        # 13,13,512 -> 13,13,256 -> 26,26,256
        P5_upsample = compose(DarknetConv2D_BN_Leaky(256, (1,1)), UpSampling2D(2))(P5)
        # 26,26,512 -> 26,26,256
        P4 = DarknetConv2D_BN_Leaky(256, (1,1))(feat2)
        # 26,26,256 + 26,26,256 -> 26,26,512
        P4 = Concatenate()([P4, P5_upsample])
        
        # 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256
        P4 = make_five_convs(P4,256)
    
        # 26,26,256 -> 26,26,128 -> 52,52,128
        P4_upsample = compose(DarknetConv2D_BN_Leaky(128, (1,1)), UpSampling2D(2))(P4)
        # 52,52,256 -> 52,52,128
        P3 = DarknetConv2D_BN_Leaky(128, (1,1))(feat1)
        # 52,52,128 + 52,52,128 -> 52,52,256
        P3 = Concatenate()([P3, P4_upsample])
    
        # 52,52,256 -> 52,52,128 -> 52,52,256 -> 52,52,128 -> 52,52,256 -> 52,52,128
        P3 = make_five_convs(P3,128)
        
        #---------------------------------------------------#
        #   第三个特征层
        #   y3=(batch_size,52,52,3,85)
        #---------------------------------------------------#
        P3_output = DarknetConv2D_BN_Leaky(256, (3,3))(P3)
        P3_output = DarknetConv2D(len(anchors_mask[0])*(num_classes+5), (1,1))(P3_output)
    
        # 52,52,128 -> 26,26,256
        P3_downsample = ZeroPadding2D(((1,0),(1,0)))(P3)
        P3_downsample = DarknetConv2D_BN_Leaky(256, (3,3), strides=(2,2))(P3_downsample)
        # 26,26,256 + 26,26,256 -> 26,26,512
        P4 = Concatenate()([P3_downsample, P4])
        # 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256
        P4 = make_five_convs(P4,256)
        
        #---------------------------------------------------#
        #   第二个特征层
        #   y2=(batch_size,26,26,3,85)
        #---------------------------------------------------#
        P4_output = DarknetConv2D_BN_Leaky(512, (3,3))(P4)
        P4_output = DarknetConv2D(len(anchors_mask[1])*(num_classes+5), (1,1))(P4_output)
        
        # 26,26,256 -> 13,13,512
        P4_downsample = ZeroPadding2D(((1,0),(1,0)))(P4)
        P4_downsample = DarknetConv2D_BN_Leaky(512, (3,3), strides=(2,2))(P4_downsample)
        # 13,13,512 + 13,13,512 -> 13,13,1024
        P5 = Concatenate()([P4_downsample, P5])
        # 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512
        P5 = make_five_convs(P5,512)
        
        #---------------------------------------------------#
        #   第一个特征层
        #   y1=(batch_size,13,13,3,85)
        #---------------------------------------------------#
        P5_output = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
        P5_output = DarknetConv2D(len(anchors_mask[2])*(num_classes+5), (1,1))(P5_output)
    
        return Model(inputs, [P5_output, P4_output, P3_output])
    

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

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

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

    实现代码如下:

    #---------------------------------------------------#
    #   特征层->最后的输出
    #---------------------------------------------------#
    def yolo_body(inputs, num_anchors, num_classes):
    # 省略了一部分,只看最后的head部分
        P3_output = DarknetConv2D_BN_Leaky(256, (3,3))(P3)
        P3_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P3_output)
    
        P4_output = DarknetConv2D_BN_Leaky(512, (3,3))(P4)
        P4_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P4_output)
    
        P5_output = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
        P5_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P5_output)
    

    4、预测结果的解码

    由第二步我们可以获得三个特征层的预测结果,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时,就会对每个特征层进行解码:

    import tensorflow as tf
    from tensorflow.keras import backend as K
    
    
    #---------------------------------------------------#
    #   对box进行调整,使其符合真实图片的样子
    #---------------------------------------------------#
    def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image):
        #-----------------------------------------------------------------#
        #   把y轴放前面是因为方便预测框和图像的宽高进行相乘
        #-----------------------------------------------------------------#
        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))
    
        if letterbox_image:
            #-----------------------------------------------------------------#
            #   这里求出来的offset是图像有效区域相对于图像左上角的偏移情况
            #   new_shape指的是宽高缩放情况
            #-----------------------------------------------------------------#
            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], box_mins[..., 1:2], box_maxes[..., 0:1], box_maxes[..., 1:2]])
        boxes *= K.concatenate([image_shape, image_shape])
        return boxes
    
    #---------------------------------------------------#
    #   将预测值的每个特征层调成真实值
    #---------------------------------------------------#
    def get_anchors_and_decode(feats, anchors, num_classes, input_shape, calc_loss=False):
        num_anchors = len(anchors)
        #------------------------------------------#
        #   grid_shape指的是特征层的高和宽
        #------------------------------------------#
        grid_shape = K.shape(feats)[1:3]
        #--------------------------------------------------------------------#
        #   获得各个特征点的坐标信息。生成的shape为(13, 13, num_anchors, 2)
        #--------------------------------------------------------------------#
        grid_x  = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]), [grid_shape[0], 1, num_anchors, 1])
        grid_y  = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]), [1, grid_shape[1], num_anchors, 1])
        grid    = K.cast(K.concatenate([grid_x, grid_y]), K.dtype(feats))
        #---------------------------------------------------------------#
        #   将先验框进行拓展,生成的shape为(13, 13, num_anchors, 2)
        #---------------------------------------------------------------#
        anchors_tensor = K.reshape(K.constant(anchors), [1, 1, num_anchors, 2])
        anchors_tensor = K.tile(anchors_tensor, [grid_shape[0], grid_shape[1], 1, 1])
    
        #---------------------------------------------------#
        #   将预测结果调整成(batch_size,13,13,3,85)
        #   85可拆分成4 + 1 + 80
        #   4代表的是中心宽高的调整参数
        #   1代表的是框的置信度
        #   80代表的是种类的置信度
        #---------------------------------------------------#
        feats           = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])
        #------------------------------------------#
        #   对先验框进行解码,并进行归一化
        #------------------------------------------#
        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的时候返回grid, feats, box_xy, box_wh
        #   在预测的时候返回box_xy, box_wh, box_confidence, box_class_probs
        #---------------------------------------------------------------------#
        if calc_loss == True:
            return grid, feats, box_xy, box_wh
        return box_xy, box_wh, box_confidence, box_class_probs
    
    #---------------------------------------------------#
    #   图片预测
    #---------------------------------------------------#
    def DecodeBox(outputs,
                anchors,
                num_classes,
                image_shape,
                input_shape,
                #-----------------------------------------------------------#
                #   13x13的特征层对应的anchor是[116,90],[156,198],[373,326]
                #   26x26的特征层对应的anchor是[30,61],[62,45],[59,119]
                #   52x52的特征层对应的anchor是[10,13],[16,30],[33,23]
                #-----------------------------------------------------------#
                anchor_mask     = [[6, 7, 8], [3, 4, 5], [0, 1, 2]],
                max_boxes       = 100,
                confidence      = 0.5,
                nms_iou         = 0.3,
                letterbox_image = True):
    
        box_xy = []
        box_wh = []
        box_confidence  = []
        box_class_probs = []
        for i in range(len(outputs)):
            sub_box_xy, sub_box_wh, sub_box_confidence, sub_box_class_probs = \
                get_anchors_and_decode(outputs[i], anchors[anchor_mask[i]], num_classes, input_shape)
            box_xy.append(K.reshape(sub_box_xy, [-1, 2]))
            box_wh.append(K.reshape(sub_box_wh, [-1, 2]))
            box_confidence.append(K.reshape(sub_box_confidence, [-1, 1]))
            box_class_probs.append(K.reshape(sub_box_class_probs, [-1, num_classes]))
        box_xy          = K.concatenate(box_xy, axis = 0)
        box_wh          = K.concatenate(box_wh, axis = 0)
        box_confidence  = K.concatenate(box_confidence, axis = 0)
        box_class_probs = K.concatenate(box_class_probs, axis = 0)
    
        #------------------------------------------------------------------------------------------------------------#
        #   在图像传入网络预测前会进行letterbox_image给图像周围添加灰条,因此生成的box_xy, box_wh是相对于有灰条的图像的
        #   我们需要对其进行修改,去除灰条的部分。 将box_xy、和box_wh调节成y_min,y_max,xmin,xmax
        #   如果没有使用letterbox_image也需要将归一化后的box_xy, box_wh调整成相对于原图大小的
        #------------------------------------------------------------------------------------------------------------#
        boxes       = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image)
        box_scores  = box_confidence * box_class_probs
    
        #-----------------------------------------------------------#
        #   判断得分是否大于score_threshold
        #-----------------------------------------------------------#
        mask             = box_scores >= confidence
        max_boxes_tensor = K.constant(max_boxes, dtype='int32')
        boxes_out   = []
        scores_out  = []
        classes_out = []
        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])
    
            #-----------------------------------------------------------#
            #   非极大抑制
            #   保留一定区域内得分最大的框
            #-----------------------------------------------------------#
            nms_index = tf.image.non_max_suppression(class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=nms_iou)
    
            #-----------------------------------------------------------#
            #   获取非极大抑制后的结果
            #   下列三个分别是:框的位置,得分与种类
            #-----------------------------------------------------------#
            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_out.append(class_boxes)
            scores_out.append(class_box_scores)
            classes_out.append(classes)
        boxes_out      = K.concatenate(boxes_out, axis=0)
        scores_out     = K.concatenate(scores_out, axis=0)
        classes_out    = K.concatenate(classes_out, axis=0)
    
        return boxes_out, scores_out, classes_out
    

    5、在原图上进行绘制

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

    YOLOV4的训练

    1、YOLOV4的改进训练技巧

    a)、Mosaic数据增强

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

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

    def merge_bboxes(self, 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 x2 >= cutx and x1 <= cutx:
                        x2 = cutx
    
                if i == 1:
                    if y2 < cuty or x1 > cutx:
                        continue
                    if y2 >= cuty and y1 <= cuty:
                        y1 = cuty
                    if x2 >= cutx and x1 <= cutx:
                        x2 = cutx
    
                if i == 2:
                    if y2 < cuty or x2 < cutx:
                        continue
                    if y2 >= cuty and y1 <= cuty:
                        y1 = cuty
                    if x2 >= cutx and x1 <= cutx:
                        x1 = cutx
    
                if i == 3:
                    if y1 > cuty or x2 < cutx:
                        continue
                    if y2 >= cuty and y1 <= cuty:
                        y2 = cuty
                    if x2 >= cutx and x1 <= cutx:
                        x1 = cutx
                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_with_Mosaic(self, annotation_line, input_shape, max_boxes=100, hue=.1, sat=1.5, val=1.5):
        h, w = input_shape
        min_offset_x = self.rand(0.25, 0.75)
        min_offset_y = self.rand(0.25, 0.75)
    
        nws     = [ int(w * self.rand(0.4, 1)), int(w * self.rand(0.4, 1)), int(w * self.rand(0.4, 1)), int(w * self.rand(0.4, 1))]
        nhs     = [ int(h * self.rand(0.4, 1)), int(h * self.rand(0.4, 1)), int(h * self.rand(0.4, 1)), int(h * self.rand(0.4, 1))]
    
        place_x = [int(w*min_offset_x) - nws[0], int(w*min_offset_x) - nws[1], int(w*min_offset_x), int(w*min_offset_x)]
        place_y = [int(h*min_offset_y) - nhs[0], int(h*min_offset_y), int(h*min_offset_y), int(h*min_offset_y) - nhs[3]]
    
        image_datas = [] 
        box_datas   = []
        index       = 0
        for line in annotation_line:
            # 每一行进行分割
            line_content = line.split()
            # 打开图片
            image = Image.open(line_content[0])
            image = cvtColor(image)
            
            # 图片的大小
            iw, ih = image.size
            # 保存框的位置
            box = np.array([np.array(list(map(int,box.split(',')))) for box in line_content[1:]])
            
            # 是否翻转图片
            flip = self.rand()<.5
            if flip and len(box)>0:
                image = image.transpose(Image.FLIP_LEFT_RIGHT)
                box[:, [0,2]] = iw - box[:, [2,0]]
    
            nw = nws[index] 
            nh = nhs[index] 
            image = image.resize((nw,nh), Image.BICUBIC)
    
            # 将图片进行放置,分别对应四张分割图片的位置
            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)
    
            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)
    
        # 将图片分割,放在一起
        cutx = int(w * min_offset_x)
        cuty = int(h * 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:, :]
    
        # 进行色域变换
        hue = self.rand(-hue, hue)
        sat = self.rand(1, sat) if self.rand()<.5 else 1/self.rand(1, sat)
        val = self.rand(1, val) if self.rand()<.5 else 1/self.rand(1, val)
        x = cv2.cvtColor(np.array(new_image/255,np.float32), cv2.COLOR_RGB2HSV)
        x[..., 0] += hue*360
        x[..., 0][x[..., 0]>1] -= 1
        x[..., 0][x[..., 0]<0] += 1
        x[..., 1] *= sat
        x[..., 2] *= val
        x[x[:, :, 0]>360, 0] = 360
        x[:, :, 1:][x[:, :, 1:]>1] = 1
        x[x<0] = 0
        new_image = cv2.cvtColor(x, cv2.COLOR_HSV2RGB)*255
    
        # 对框进行进一步的处理
        new_boxes = self.merge_bboxes(box_datas, cutx, cuty)
    
        # 将box进行调整
        box_data = np.zeros((max_boxes, 5))
        if len(new_boxes)>0:
            if len(new_boxes)>max_boxes: new_boxes = new_boxes[:max_boxes]
            box_data[:len(new_boxes)] = new_boxes
        return new_image, box_data
    

    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 = K.shape(y_true)[-1],
        label_smoothing = K.constant(label_smoothing, dtype=K.floatx())
        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公式如下
    C I O U = I O U − ρ 2 ( b , b g t ) c 2 − α v CIOU = IOU - \frac{\rho^2(b,b^{gt})}{c^2} - \alpha v CIOU=IOUc2ρ2(b,bgt)αv
    其中, ρ 2 ( b , b g t ) \rho^2(b,b^{gt}) ρ2(b,bgt)分别代表了预测框和真实框的中心点的欧式距离。 c代表的是能够同时包含预测框和真实框的最小闭包区域的对角线距离。

    α \alpha α v v v的公式如下
    α = v 1 − I O U + v \alpha = \frac{v}{1-IOU+v} α=1IOU+vv
    v = 4 π 2 ( a r c t a n w g t h g t − a r c t a n w h ) 2 v = \frac{4}{\pi ^2}(arctan\frac{w^{gt}}{h^{gt}}-arctan\frac{w}{h})^2 v=π24(arctanhgtwgtarctanhw)2
    把1-CIOU就可以得到相应的LOSS了。
    L O S S C I O U = 1 − I O U + ρ 2 ( b , b g t ) c 2 + α v LOSS_{CIOU} = 1 - IOU + \frac{\rho^2(b,b^{gt})}{c^2} + \alpha v LOSSCIOU=1IOU+c2ρ2(b,bgt)+α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_mins     (batch, feat_w, feat_h, anchor_num, 2)
        #   b1_maxes    (batch, feat_w, feat_h, anchor_num, 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
        #-----------------------------------------------------------#
        #   求出真实框左上角右下角
        #   b2_mins     (batch, feat_w, feat_h, anchor_num, 2)
        #   b2_maxes    (batch, feat_w, feat_h, anchor_num, 2)
        #-----------------------------------------------------------#
        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
        #   iou         (batch, feat_w, feat_h, anchor_num)
        #-----------------------------------------------------------#
        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]
        union_area = b1_area + b2_area - intersect_area
        iou = intersect_area / K.maximum(union_area, K.epsilon())
    
        #-----------------------------------------------------------#
        #   计算中心的差距
        #   center_distance (batch, feat_w, feat_h, anchor_num)
        #-----------------------------------------------------------#
        center_distance = K.sum(K.square(b1_xy - b2_xy), axis=-1)
        enclose_mins = K.minimum(b1_mins, b2_mins)
        enclose_maxes = K.maximum(b1_maxes, b2_maxes)
        enclose_wh = K.maximum(enclose_maxes - enclose_mins, 0.0)
        #-----------------------------------------------------------#
        #   计算对角线距离
        #   enclose_diagonal (batch, feat_w, feat_h, anchor_num)
        #-----------------------------------------------------------#
        enclose_diagonal = K.sum(K.square(enclose_wh), axis=-1)
        ciou = iou - 1.0 * (center_distance) / K.maximum(enclose_diagonal ,K.epsilon())
        
        v = 4 * K.square(tf.math.atan2(b1_wh[..., 0], K.maximum(b1_wh[..., 1], K.epsilon())) - tf.math.atan2(b2_wh[..., 0], K.maximum(b2_wh[..., 1],K.epsilon()))) / (math.pi * math.pi)
        alpha = v /  K.maximum((1.0 - iou + v), K.epsilon())
        ciou = ciou - alpha * v
    
        ciou = K.expand_dims(ciou, -1)
        return ciou
    

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

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

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

    效果如图所示:
    在这里插入图片描述

    实现方式如下,利用Callback实现,与普通的ReduceLROnPlateau调用方式类似:

    class WarmUpCosineDecayScheduler(keras.callbacks.Callback):
        def __init__(self, T_max, eta_min=0, verbose=0):
            super(WarmUpCosineDecayScheduler, self).__init__()
            self.T_max      = T_max
            self.eta_min    = eta_min
            self.verbose    = verbose
            self.init_lr    = 0
            self.last_epoch = 0
    
        def on_train_begin(self, batch, logs=None):
            self.init_lr = K.get_value(self.model.optimizer.lr)
    
        def on_epoch_end(self, batch, logs=None):
            learning_rate = self.eta_min + (self.init_lr - self.eta_min) * (1 + math.cos(math.pi * self.last_epoch / self.T_max)) / 2
            self.last_epoch += 1
    
            K.set_value(self.model.optimizer.lr, learning_rate)
            if self.verbose > 0:
                print('Setting learning rate to %s.' % (learning_rate))
    

    2、loss组成

    a)、计算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)

    b)、y_pre是什么

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

    c)、y_true是什么。

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

    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:输入的形状,此处为608、608
    anchors:代表9个先验框的大小
    num_classes:种类的数量。

    其实对真实框的处理是将真实框转化成图片中相对网格的xyhw,步骤如下:
    1、取框的真实值,获取其框的中心及其宽高,除去input_shape变成比例的模式。
    2、建立全为0的y_true,y_true是一个列表,包含三个特征层,shape分别为(batch_size,13,13,3,85)、(batch_size,26,26,3,85)、(batch_size,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全部的代码如下:

    def preprocess_true_boxes(self, true_boxes, input_shape, anchors, num_classes):
        assert (true_boxes[..., 4]<num_classes).all(), 'class id must be less than num_classes'
        #-----------------------------------------------------------#
        #   获得框的坐标和图片的大小
        #-----------------------------------------------------------#
        true_boxes  = np.array(true_boxes, dtype='float32')
        input_shape = np.array(input_shape, dtype='int32')
        
        #-----------------------------------------------------------#
        #   一共有三个特征层数
        #-----------------------------------------------------------#
        num_layers  = len(self.anchors_mask)
        #-----------------------------------------------------------#
        #   m为图片数量,grid_shapes为网格的shape
        #-----------------------------------------------------------#
        m           = true_boxes.shape[0]
        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(self.anchors_mask[l]), 5 + num_classes),
                    dtype='float32') for l in range(num_layers)]
    
        #-----------------------------------------------------------#
        #   通过计算获得真实框的中心和宽高
        #   中心点(m,n,2) 宽高(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[::-1]
        true_boxes[..., 2:4] = boxes_wh / input_shape[::-1]
    
        #-----------------------------------------------------------#
        #   [9,2] -> [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,2] -> [n,1,2]
            #-----------------------------------------------------------#
            wh          = np.expand_dims(wh, -2)
            box_maxes   = wh / 2.
            box_mins    = - box_maxes
    
            #-----------------------------------------------------------#
            #   计算所有真实框和先验框的交并比
            #   intersect_area  [n,9]
            #   box_area        [n,1]
            #   anchor_area     [1,9]
            #   iou             [n,9]
            #-----------------------------------------------------------#
            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 self.anchors_mask[l]:
                        #-----------------------------------------------------------#
                        #   floor用于向下取整,找到真实框所属的特征层对应的x、y轴坐标
                        #-----------------------------------------------------------#
                        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')
                        #-----------------------------------------------------------#
                        #   k指的的当前这个特征点的第k个先验框
                        #-----------------------------------------------------------#
                        k = self.anchors_mask[l].index(n)
                        #-----------------------------------------------------------#
                        #   c指的是当前这个真实框的种类
                        #-----------------------------------------------------------#
                        c = true_boxes[b, t, 4].astype('int32')
                        #-----------------------------------------------------------#
                        #   y_true的shape为(m,13,13,3,85)(m,26,26,3,85)(m,52,52,3,85)
                        #   最后的85可以拆分成4+1+80,4代表的是框的中心与宽高、
                        #   1代表的是置信度、80代表的是种类
                        #-----------------------------------------------------------#
                        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
    

    d)、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)。还有解码后的xy,wh。
    3、对于每一幅图,计算其中所有真实框与预测框的IOU,如果某些预测框和真实框的重合程度大于0.5,则忽略。
    4、计算ciou作为回归的loss,这里只计算正样本的回归loss。
    5、计算置信度的loss,其有两部分构成,第一部分是实际上存在目标的,预测结果中置信度的值与1对比;第二部分是实际上不存在目标的,预测结果中置信度的值与0对比。
    6、计算预测种类的loss,其计算的是实际上存在目标的,预测类与真实类的差距。

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

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

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

    import math
    
    import tensorflow as tf
    from keras import backend as K
    from utils.utils_bbox import get_anchors_and_decode
    
    
    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_mins     (batch, feat_w, feat_h, anchor_num, 2)
        #   b1_maxes    (batch, feat_w, feat_h, anchor_num, 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
        #-----------------------------------------------------------#
        #   求出真实框左上角右下角
        #   b2_mins     (batch, feat_w, feat_h, anchor_num, 2)
        #   b2_maxes    (batch, feat_w, feat_h, anchor_num, 2)
        #-----------------------------------------------------------#
        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
        #   iou         (batch, feat_w, feat_h, anchor_num)
        #-----------------------------------------------------------#
        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]
        union_area = b1_area + b2_area - intersect_area
        iou = intersect_area / K.maximum(union_area, K.epsilon())
    
        #-----------------------------------------------------------#
        #   计算中心的差距
        #   center_distance (batch, feat_w, feat_h, anchor_num)
        #-----------------------------------------------------------#
        center_distance = K.sum(K.square(b1_xy - b2_xy), axis=-1)
        enclose_mins = K.minimum(b1_mins, b2_mins)
        enclose_maxes = K.maximum(b1_maxes, b2_maxes)
        enclose_wh = K.maximum(enclose_maxes - enclose_mins, 0.0)
        #-----------------------------------------------------------#
        #   计算对角线距离
        #   enclose_diagonal (batch, feat_w, feat_h, anchor_num)
        #-----------------------------------------------------------#
        enclose_diagonal = K.sum(K.square(enclose_wh), axis=-1)
        ciou = iou - 1.0 * (center_distance) / K.maximum(enclose_diagonal ,K.epsilon())
        
        v = 4 * K.square(tf.math.atan2(b1_wh[..., 0], K.maximum(b1_wh[..., 1], K.epsilon())) - tf.math.atan2(b2_wh[..., 0], K.maximum(b2_wh[..., 1],K.epsilon()))) / (math.pi * math.pi)
        alpha = v /  K.maximum((1.0 - iou + v), K.epsilon())
        ciou = ciou - alpha * v
    
        ciou = K.expand_dims(ciou, -1)
        return ciou
    
    #---------------------------------------------------#
    #   平滑标签
    #---------------------------------------------------#
    def _smooth_labels(y_true, label_smoothing):
        num_classes = tf.cast(K.shape(y_true)[-1], dtype=K.floatx())
        label_smoothing = K.constant(label_smoothing, dtype=K.floatx())
        return y_true * (1.0 - label_smoothing) + label_smoothing / num_classes
        
    #---------------------------------------------------#
    #   用于计算每个预测框与真实框的iou
    #---------------------------------------------------#
    def box_iou(b1, b2):
        #---------------------------------------------------#
        #   num_anchor,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, input_shape, anchors, anchors_mask, num_classes, ignore_thresh=.5, label_smoothing=0.1, print_loss=False):
        num_layers = len(anchors_mask)
        #---------------------------------------------------------------------------------------------------#
        #   将预测结果和实际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]
    
        #-----------------------------------------------------------#
        #   得到input_shpae为416,416 
        #-----------------------------------------------------------#
        input_shape = K.cast(input_shape, K.dtype(y_true[0]))
    
        #-----------------------------------------------------------#
        #   取出每一张图片
        #   m的值就是batch_size
        #-----------------------------------------------------------#
        m = K.shape(yolo_outputs[0])[0]
    
        loss    = 0
        num_pos = 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:]
            if label_smoothing:
                true_class_probs = _smooth_labels(true_class_probs, label_smoothing)
    
            #-----------------------------------------------------------#
            #   将yolo_outputs的特征层输出进行处理、获得四个返回值
            #   其中:
            #   grid        (13,13,1,2) 网格坐标
            #   raw_pred    (m,13,13,3,85) 尚未处理的预测结果
            #   pred_xy     (m,13,13,3,2) 解码后的中心坐标
            #   pred_wh     (m,13,13,3,2) 解码后的宽高坐标
            #-----------------------------------------------------------#
            grid, raw_pred, pred_xy, pred_wh = get_anchors_and_decode(yolo_outputs[l],
                 anchors[anchors_mask[l]], num_classes, input_shape, calc_loss=True)
            
            #-----------------------------------------------------------#
            #   pred_box是解码后的预测的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):
                #-----------------------------------------------------------#
                #   取出n个真实框: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 预测框的坐标
                #   true_box    n,4 真实框的坐标
                #   iou         13,13,3,n 预测框和真实框的iou
                #-----------------------------------------------------------#
                iou = box_iou(pred_box[b], true_box)
    
                #-----------------------------------------------------------#
                #   best_iou    13,13,3 每个特征点与真实框的最大重合程度
                #-----------------------------------------------------------#
                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用于提取出作为负样本的特征点
            #   (m,13,13,3)
            #-----------------------------------------------------------#
            ignore_mask = ignore_mask.stack()
            #   (m,13,13,3,1)
            ignore_mask = K.expand_dims(ignore_mask, -1)
    
            #-----------------------------------------------------------#
            #   真实框越大,比重越小,小框的比重更大。
            #-----------------------------------------------------------#
            box_loss_scale = 2 - y_true[l][...,2:3]*y_true[l][...,3:4]
    
            #-----------------------------------------------------------#
            #   计算Ciou loss
            #-----------------------------------------------------------#
            raw_true_box    = y_true[l][...,0:4]
            ciou            = box_ciou(pred_box, raw_true_box)
            ciou_loss       = object_mask * box_loss_scale * (1 - ciou)
            
            #------------------------------------------------------------------------------#
            #   如果该位置本来有框,那么计算1与置信度的交叉熵
            #   如果该位置本来没有框,那么计算0与置信度的交叉熵
            #   在这其中会忽略一部分样本,这些被忽略的样本满足条件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)
    
            location_loss   = K.sum(ciou_loss)
            confidence_loss = K.sum(confidence_loss)
            class_loss      = K.sum(class_loss)
            #-----------------------------------------------------------#
            #   计算正样本数量
            #-----------------------------------------------------------#
            num_pos += tf.maximum(K.sum(K.cast(object_mask, tf.float32)), 1)
            loss    += location_loss + confidence_loss + class_loss
            # if print_loss:
            #   loss = tf.Print(loss, [loss, location_loss, confidence_loss, class_loss, K.sum(ignore_mask)], message='loss: ')
            
        loss = loss / num_pos
        return loss
    

    训练自己的YoloV4模型

    首先前往Github下载对应的仓库,下载完后利用解压软件解压,之后用编程软件打开文件夹。
    注意打开的根目录必须正确,否则相对目录不正确的情况下,代码将无法运行。

    一定要注意打开后的根目录是文件存放的目录。
    在这里插入图片描述

    一、数据集的准备

    本文使用VOC格式进行训练,训练前需要自己制作好数据集,如果没有自己的数据集,可以通过Github连接下载VOC12+07的数据集尝试下。
    训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。
    在这里插入图片描述
    训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。
    在这里插入图片描述
    此时数据集的摆放已经结束。

    二、数据集的处理

    在完成数据集的摆放之后,我们需要对数据集进行下一步的处理,目的是获得训练用的2007_train.txt以及2007_val.txt,需要用到根目录下的voc_annotation.py。

    voc_annotation.py里面有一些参数需要设置。
    分别是annotation_mode、classes_path、trainval_percent、train_percent、VOCdevkit_path,第一次训练可以仅修改classes_path

    '''
    annotation_mode用于指定该文件运行时计算的内容
    annotation_mode为0代表整个标签处理过程,包括获得VOCdevkit/VOC2007/ImageSets里面的txt以及训练用的2007_train.txt、2007_val.txt
    annotation_mode为1代表获得VOCdevkit/VOC2007/ImageSets里面的txt
    annotation_mode为2代表获得训练用的2007_train.txt、2007_val.txt
    '''
    annotation_mode     = 0
    '''
    必须要修改,用于生成2007_train.txt、2007_val.txt的目标信息
    与训练和预测所用的classes_path一致即可
    如果生成的2007_train.txt里面没有目标信息
    那么就是因为classes没有设定正确
    仅在annotation_mode为0和2的时候有效
    '''
    classes_path        = 'model_data/voc_classes.txt'
    '''
    trainval_percent用于指定(训练集+验证集)与测试集的比例,默认情况下 (训练集+验证集):测试集 = 9:1
    train_percent用于指定(训练集+验证集)中训练集与验证集的比例,默认情况下 训练集:验证集 = 9:1
    仅在annotation_mode为0和1的时候有效
    '''
    trainval_percent    = 0.9
    train_percent       = 0.9
    '''
    指向VOC数据集所在的文件夹
    默认指向根目录下的VOC数据集
    '''
    VOCdevkit_path  = 'VOCdevkit'
    

    classes_path用于指向检测类别所对应的txt,以voc数据集为例,我们用的txt为:
    在这里插入图片描述
    训练自己的数据集时,可以自己建立一个cls_classes.txt,里面写自己所需要区分的类别。

    三、开始网络训练

    通过voc_annotation.py我们已经生成了2007_train.txt以及2007_val.txt,此时我们可以开始训练了。
    训练的参数较多,大家可以在下载库后仔细看注释,其中最重要的部分依然是train.py里的classes_path。

    classes_path用于指向检测类别所对应的txt,这个txt和voc_annotation.py里面的txt一样!训练自己的数据集必须要修改!
    在这里插入图片描述
    修改完classes_path后就可以运行train.py开始训练了,在训练多个epoch后,权值会生成在logs文件夹中。
    其它参数的作用如下:

    #--------------------------------------------------------#
    #   训练前一定要修改classes_path,使其对应自己的数据集
    #--------------------------------------------------------#
    classes_path    = 'model_data/voc_classes.txt'
    #---------------------------------------------------------------------#
    #   anchors_path代表先验框对应的txt文件,一般不修改。
    #   anchors_mask用于帮助代码找到对应的先验框,一般不修改。
    #---------------------------------------------------------------------#
    anchors_path    = 'model_data/yolo_anchors.txt'
    anchors_mask    = [[6, 7, 8], [3, 4, 5], [0, 1, 2]]
    #-------------------------------------------------------------------------------------#
    #   权值文件请看README,百度网盘下载
    #   训练自己的数据集时提示维度不匹配正常,预测的东西都不一样了自然维度不匹配
    #   预训练权重对于99%的情况都必须要用,不用的话权值太过随机,特征提取效果不明显
    #   网络训练的结果也不会好,数据的预训练权重对不同数据集是通用的,因为特征是通用的
    #------------------------------------------------------------------------------------#
    model_path      = 'model_data/yolo4_weight.h5'
    #------------------------------------------------------#
    #   输入的shape大小,一定要是32的倍数
    #------------------------------------------------------#
    input_shape     = [416, 416]
    #------------------------------------------------------#
    #   Yolov4的tricks应用
    #   mosaic 马赛克数据增强 True or False 
    #   实际测试时mosaic数据增强并不稳定,所以默认为False
    #   Cosine_scheduler 余弦退火学习率 True or False
    #   label_smoothing 标签平滑 0.01以下一般 如0.01、0.005
    #------------------------------------------------------#
    mosaic              = False
    Cosine_scheduler    = False
    label_smoothing     = 0
    
    #----------------------------------------------------#
    #   训练分为两个阶段,分别是冻结阶段和解冻阶段
    #   冻结阶段训练参数
    #   此时模型的主干被冻结了,特征提取网络不发生改变
    #   占用的显存较小,仅对网络进行微调
    #----------------------------------------------------#
    Init_Epoch          = 0
    Freeze_Epoch        = 50
    Freeze_batch_size   = 4
    Freeze_lr           = 1e-3
    #----------------------------------------------------#
    #   解冻阶段训练参数
    #   此时模型的主干不被冻结了,特征提取网络会发生改变
    #   占用的显存较大,网络所有的参数都会发生改变
    #   batch不能为1
    #----------------------------------------------------#
    UnFreeze_Epoch      = 100
    Unfreeze_batch_size = 4
    Unfreeze_lr         = 1e-4
    #------------------------------------------------------#
    #   是否进行冻结训练,默认先冻结主干训练后解冻训练。
    #------------------------------------------------------#
    Freeze_Train        = True
    #------------------------------------------------------#
    #   用于设置是否使用多线程读取数据,0代表关闭多线程
    #   开启后会加快数据读取速度,但是会占用更多内存
    #   keras里开启多线程有些时候速度反而慢了许多
    #   在IO为瓶颈的时候再开启多线程,即GPU运算速度远大于读取图片的速度。
    #------------------------------------------------------#
    num_workers         = 0
    #----------------------------------------------------#
    #   获得图片路径和标签
    #----------------------------------------------------#
    train_annotation_path   = '2007_train.txt'
    val_annotation_path     = '2007_val.txt'
    

    四、训练结果预测

    训练结果预测需要用到两个文件,分别是yolo.py和predict.py。
    我们首先需要去yolo.py里面修改model_path以及classes_path,这两个参数必须要修改。

    model_path指向训练好的权值文件,在logs文件夹里。
    classes_path指向检测类别所对应的txt。

    在这里插入图片描述
    完成修改后就可以运行predict.py进行检测了。运行后输入图片路径即可检测。

    展开全文
  • cmake 生成器(generator)

    万次阅读 2015-08-07 16:53:10
    在CMake 2.8.3平台上,CMake支持下列生成器: Borland Makefiles: 生成Borland makefile。MSYS Makefiles: 生成MSYS makefile。 生成的makefile用use /bin/sh作为它的shell。在运行CMake的机器上需要安装msys。 ...

    注:cmake --help可以显示所有的选项和生成器。

    在CMake 2.8.3平台上,CMake支持下列生成器:

    • Borland Makefiles: 生成Borland makefile。
    • MSYS Makefiles: 生成MSYS makefile。

      生成的makefile用use /bin/sh作为它的shell。在运行CMake的机器上需要安装msys。

    • MinGW Makefiles: 生成供mingw32-make使用的make file。

      生成的makefile使用cmd.exe作为它的shell。生成它们不需要msys或者unix shell。

    • NMake Makefiles: 生成NMake makefile。
    • NMake Makefiles JOM: 生成JOM makefile。
    • Unix Makefiles: 生成标准的UNIX makefile。

      在构建树上生成分层的UNIX makefile。任何标准的UNIX风格的make程序都可以通过默认的make目标构建工程。生成的makefile也提供了install目标。

    • Visual Studio 10: 生成Visual Studio 10 工程文件。
    • Visual Studio 10 Win64: 生成Visual Studio 10 Win64 工程文件。
    • Visual Studio 6: 生成Visual Studio 6 工程文件。
    • Visual Studio 7: 生成Visual Studio .NET 2002 工程文件。
    • Visual Studio 7 .NET 2003: 生成Visual Studio .NET 2003工程文件。
    • Visual Studio 8 2005: 生成Visual Studio .NET 2005 工程文件。
    • Visual Studio 8 2005 Win64: 生成Visual Studio .NET 2005 Win64工程文件。
    • Visual Studio 9 2008: 生成Visual Studio 9 2008 工程文件。
    • Visual Studio 9 2008 Win64: 生成Visual Studio 9 2008 Win64工程文件。
    • Watcom WMake: 生成Watcom WMake makefiles。
    • CodeBlocks - MinGW Makefiles: 生成CodeBlock工程文件。

      在顶层目录以及每层子目录下为CodeBlocks生成工程文件,生成的CMakeList.txt的特点是都包含一个PROJECT()调用。除此之外还会在构建树上生成一套层次性的makefile。通过默认的make目标,正确的make程序可以构建这个工程。makefile还提供了install目标。

    • CodeBlocks - NMake Makefiles: 生成CodeBlocks工程文件。

      在顶层目录以及每层子目录下为CodeBlocks生成工程文件,生成的CMakeList.txt的特点是都包含一个PROJECT()调用。除此之外还会在构建树上生成一套层次性的makefile。通过默认的make目标,正确的make程序可以构建这个工程。makefile还提供了install目标。

    • CodeBlocks - Unix Makefiles: 生成CodeBlocks工程文件。

      在顶层目录以及每层子目录下为CodeBlocks生成工程文件,生成的CMakeList.txt的特点是都包含一个PROJECT()调用。除此之外还会在构建树上生成一套层次性的makefile。通过默认的make目标,正确的make程序可以构建这个工程。makefile还提供了install目标。

    • Eclipse CDT4 - MinGW Makefiles: 生成Eclipse CDT 4.0 工程文件。

      在顶层目录下为Eclipse生成工程文件。在运行源码外构建时,一个连接到顶层源码路径的资源文件会被创建。除此之外还会在构建树上生成一套层次性的makefile。通过默认的make目标,正确的make程序可以构建这个工程。makefile还提供了install目标。

    • Eclipse CDT4 - NMake Makefiles: 生成Eclipse CDT 4.0 工程文件。

      在顶层目录下为Eclipse生成工程文件。在运行源码外构建时,一个连接到顶层源码路径的资源文件会被创建。除此之外还会在构建树上生成一套层次性的makefile。通过默认的make目标,正确的make程序可以构建这个工程。makefile还提供了install目标。

    • Eclipse CDT4 - Unix Makefiles: 生成Eclipse CDT 4.0 工程文件。

      在顶层目录下为Eclipse生成工程文件。在运行源码外构建时,一个连接到顶层源码路径的资源文件会被创建。除此之外还会在构建树上生成一套层次性的makefile。通过默认的make目标,正确的make程序可以构建这个工程。makefile还提供了install目标。


    http://www.cnblogs.com/coderfenghc/archive/2012/06/18/2553964.html


    展开全文
  • LDA文档主题生成模型

    千次阅读 2015-12-08 16:39:34
    基础知识: LDA: latent dirichlet allocation ,又名潜在狄利克雷分布,是非监督机器学习技术,用于识别文档集中潜在的主题词信息。 主要思想:采用词袋方法,将每一篇文档视为...文档主题生成模型:即三层贝叶斯概

    基础知识:


    LDA: latent dirichlet allocation ,又名潜在狄利克雷分布,是非监督机器学习技术,用于识别文档集中潜在的主题词信息。

    主要思想:采用词袋方法,将每一篇文档视为词频向量,将文本信息转换为数字信息,没有考虑词与词之间的顺序。每一篇文档代表了一些主题所构成的概率分布,每一个主题又代表了很多单词所构成的概率分布。

    文档主题生成模型:即三层贝叶斯概率模型,包含词、主题和文档。

    生成模型:每个词通过“以一定的概率选择了主题,并从主题中以一定概率选择某个词语”的过程得到。

    特点:文档到主题服从多项式分布,主题到词服从多项式分布


    算法思想:

    D:文档集合,T:主题集合,词袋:一篇文章中的单词,Vocabulary:所有文档中的词的集合

    S1:统计D中每个文档d看作单词序列<w1,w2,...,wn>,wi表示第i个单词,设d有n个单词

    S2:将D中涉及的所有单词组成Vocabulary,LDA以文档集合D作为输入,训练出两个结果向量(设聚成k个topic,Vocabulary中包含m个词):

    S21:对每个D中的文档d,对应到不同主题的θd<pt1,pt2,...,ptk>,其中,pti表示d对应T中第i个主题的概率。pti=nti/n,nti是d中对应于T中第i个主题的词的数目,n是d中所有词的总数。

    S22:对于T中每个主题topict,生成不同单词的概率φt<pw1,...,pwn>,pwi:表示t生成VOC中第i个单词的概率。pwi =Nwi/N,其中Nwi表示对应到主题的Vocabulary中第i个单词的数目,

    N表示所有对应到topict的单词总数

    核心公式:p(w|d) = p(w|t)*p(t|d)=θd * φt
    ,以主题词作为中间层


    逻辑流程:

    处理逻辑主要包含生成过程和学习过程

    生成过程:

    1为每一篇文档从主题分布中抽取一个主题

    2从该主题对应的单词分布中抽取一个单词

    3重复上述过程,直至遍历文档中每一个单词

    记:文档到主题的分布为θ

    记:主题到单词的分布为φ


    学习过程:

    方法总览:对于所有的文档和主题,随机给θd和φt赋值,不断重复上述过程,直到收敛,就是LDA的输出

    1对于特定文档ds中第i个单词wi,若令该单词对应的topic为tj,把上述公式改写为

    pj(wi|ds) = p(wi|tj) * p(tj|ds)

    2枚举T中的topic,得到所有的pj(wi|ds),其中j的范围是[1,k]。根据概率值结果为ds中第i个单词wi选择一个topic.

    最简单:令pj(wi|ds)最大的tj

    3如果ds中第i个单词wi选择了与原先不同的主题,就对θd和φt有影响,反过来影响p(w|d)。对d中所有w进行一次p(w|d)的计算

    并重新选择topic看作是一次迭代。进行n次迭代后,收敛到LDA的结果。


    主题产生的过程:

    是由词语的分布定义,比如蓝色主题是:2%概率的data,%2的number构成,....

    一篇文章的产生过程:

    文章是由一些主题构成。具体地,从主题集合中按概率分布选取一些主题,从主题中按概率分布选取一些词语,词语构成了最终的文档

    我们的目标:计算这两个概率分布->得到模型,可以根据文档推断出它的主题分布,即分类。

    文档->主题 是文档生成过程的逆过程


    http://ww4.sinaimg.cn/large/6cbb8645jw1eoo8k4ft98j20pk0dogpu.jpg


    文档生成过程的描述:

    1上帝有两个坛子的骰子,第一个坛子装的是文档到主题骰子,第二个坛子装的是主题到词语的骰子

    2上帝从第二个坛子中独立抽取K个主题到词语骰子,编号为1~K

    3每次生成一篇新的文档前,上帝从第一个坛子中随机抽取文档到主题的骰子,重复下列过程,生成文档中的词

       3.1 投掷这个文档到主题的骰子,得到主题编号z

        3.2选择K个主题到词的骰子中编号为z的那个,投掷这个骰子,得到一个词


    http://ww1.sinaimg.cn/large/6cbb8645jw1eoo9wpkemgj20mc0fejst.jpg


    概率模型:

    LDA是使用联合分布来计算给定测量变量下隐藏变量的条件分布(后验分布)的

    概率模型,观测变量为词的集合,隐藏变量为主题


    联合分布:

    http://ww2.sinaimg.cn/large/6cbb8645jw1eoo900spbyj20ko02gjrv.jpg

    参数解释:

    β:主题

    θ:主题概率

    z:某个特定文档或词语的主题

    w:单词

    β1:K :主题集合

    βk :第k个主题的词的分布

    θd :第d个文档中该主题所占的比率

    θd,k :第d个文档中的第k个主题所占的比率

    zd :第d个文档的主题全体

    zd,n :第d个文档中第n个词所对应的主题

    wd :第d个文档的单词全体

    wd,n :第d个文档中第n个单词

    p(β):从主题集合中选取特定主题的概率

    p(θd):该主题在特定文档中的概率

    大括号里面前半部分:在主题确定的条件下,该文档第n个词的主题的概率

    大括号里面后半部分:该文档第n个词的主题与该词的联合分布

    连乘符号:随机变量的依赖性


    词受两个方面影响:

    1确定了主题后文档中该主题的分布θd

    2第k个主题的词的分布βk

    后验分布:

    http://ww2.sinaimg.cn/large/6cbb8645jw1eooa69fd0vj20cu021weo.jpg

    分子:联合分布,给定语料库可计算

    分母:无法暴力计算,文档集合词库达到百万


    基于采样的算法通过收集后验分布样本,以样本的分布求得后验分布的近似。

    θd :概率服从Dirichlet分布

    Zd,n:的分布服从多项式分布

    两个分布共轭,共轭:指先验分布和后验分布形式相同

    http://ww3.sinaimg.cn/large/6cbb8645jw1eoqqfhiki4j20ai01awej.jpg

    展开全文
  • 分布式ID生成器方案

    千次阅读 2019-03-29 11:24:01
    文章目录需求背景全局唯一趋势递增单调递增信息安全一 SnowFlake 算法优势缺点:生成规则算法:美团优化方案弱依赖ZooKeeper解决时钟问题二 美团方案三 UUID方法优点:缺点:四 使用数据库的 auto_increment 来...
  • 这篇文章描述了构造导航网格过程中的第五步,也就是最后一步:带有高度细节信息的三角网格的生成。 源数据类:PolyMeshField 和 OpenHeightField 构造类:DetailMeshBuilder 数据类:TriangleMesh 如果你需要...
  • 生成模型与判别模型

    万次阅读 多人点赞 2012-11-17 23:24:42
    生成模型与判别模型 zouxy09@qq.com http://blog.csdn.net/zouxy09  一直在看论文的过程中遇到这个问题,折腾了不少时间,然后是下面的一点理解,不知道正确否。若有错误,还望各位前辈不吝指正,以免小弟一错再...
  • 路由器的路由表生成算法

    千次阅读 2017-06-13 13:12:20
    Dijkstra算法执行下列步骤: (1)、路由器建立一张网络图,并且确定源节点和目的节点,在这个例子里我们设为V1和V2。然后路由器建立一个矩阵,称为“邻接矩阵”。在这个矩阵中,各矩阵元素表示权值。例如,[i,j]...
  • dll文件生成使用

    千次阅读 2013-05-23 09:11:53
    dll文件生成  vc编译器与gcc的编译原理是一样的,个别的函数像printf可能编译结果不大一样。程序生成的基本过程就是编译,链接。编译大体上也分三部分,预处理,编译,汇编,因为编译过程最复杂,所以这三步一般合...
  • CMake手册详解:(二)生成器篇

    千次阅读 2015-06-05 14:37:37
    CMake支持下列生成器: Visual Studio 6: 生成Visual Studio 6 工程文件。 Visual Studio 7: 生成Visual Studio .NET 2002 工程文件。 Visual Studio 10: 生成Visual Studio 10(2010) 工程文件。 Visual Studio 10 ...
  • 目标跟踪从两个维度来展开: 基于视觉的目标跟踪和基于多传感器融合的目标跟踪。 1.基于视觉的目标跟踪 一般将目标跟踪分为两个部分:特征提取、目标跟踪算法。目标跟踪的算法大致可以分为以下五种: 均值漂移算法...
  • 生成对抗网络(GAN)的理论与应用完整入门介绍

    万次阅读 多人点赞 2017-06-23 13:32:46
    本文包含以下内容: 1.为什么生成模型值得研究 2.生成模型的分类 3.GAN相对于其他生成模型相比有什么优势 4.GAN基本模型 5.改进的GANs 6.GAN有哪些应用 7.GAN的前沿研究
  • Tomcat生成SSL安全证书

    千次阅读 2018-07-04 19:17:01
    用Tomcat来配置SSL主要有下面这么两大步骤:生成证书 (首先我们要用KEYTOOL生成证书)Java 中的 keytool.exe (位于 JDK\Bin 目录下)可以用来创建数字证书,所有的数字证书是以一条一条(采用别名区别)的形式存入...
  • 生成特定分布随机数的方法

    千次阅读 2015-09-23 22:01:55
    生成随机数是程序设计里常见的需求。一般的编程语言都会自带一个随机数生成函数,用于生成服从均匀分布的随机数。不过有时需要生成服从其它分布的随机数,例如高斯分布或指数分布等。有些编程语言已经有比较完善的...
  • 背景基于生成对抗网络(GAN)的动漫人物生成近年来兴起的动漫产业新技术。传统的GAN模型利用反向传播算法,通过生成器和判别器动态对抗,得到一个目标生成模型。由于训练过程不稳定,网络难以收...
  • R-CNN算法学习(步骤一:候选区域生成

    万次阅读 多人点赞 2018-12-27 15:55:28
    R-CNN算法学习 ...算法整体思想 总体分为 四个步骤(下文讲逐步骤分析...1.候选区域生成: 一张图像生成1K~2K个候选区域 (采用Selective Search 方法) 2.特征提取: 对每个候选区域,使用深度卷积网络提取特征 (CNN)...
  • 生成对抗网络的tensorflow实现

    万次阅读 2017-01-08 15:51:26
    这是关于使用tensorflow来实现Goodfellow的生成对抗网络论文的教程。对抗网络是一个可以使用大约80行的python代码就可以实现的一个有趣的小深度学习练习,这将使你进入深度学习的一个活跃领域:生成式模型
  • 重排序与数据依赖

    千次阅读 多人点赞 2019-10-04 15:14:07
    上一篇博客我们了解了Java内存模型,下面我们来了解一下重排序和数据依赖的相关知识。 为什么需要重排序 现在的CPU一般采用流水线来执行指令。一个指令的执行被分成:取指、译码、访存、执行、写回、等若干个阶段...
  • 生成模型和判别模型

    千次阅读 2018-09-13 10:26:34
     生成模型(Generative Model)是相对于判别模型(Discriminative Model)定义的。他们两个都用于有监督学习。监督学习的任务就是从数据中学习一个模型(也叫分类器),应用这一模型,对给定的输入X预测相应的输出...
  • 中间代码生成_11 中间代码生成时所依据的是( )。A. 语法规则B. 词法规则C. 语义规则D. 等价变换规则 2 在编译程序中与中间代码生成无关的是( )。A. 便于目标代码的优化B. 便于存储空间的组织C. 便于编译程序的移植D...
  • 变分自编码器(VAE)与生成对抗网络(GAN)是复杂分布上无监督学习最具前景的两类方法。本文中,作者在 MNIST 上对这两类生成模型的性能进行了对比测试。本项目总结了使用变分自编码器(Variational Autoencode,VAE...
  • 哈希函数的生成方法

    千次阅读 2017-07-30 21:22:49
    设要存放的数据元素有n个,存放数据元素的内存单元有m个,设计哈希函数的目标就是要使通过哈希函数得到的n个数据元素的哈希地址尽可能均匀地分布在m个连续内存单元上,同时使计算过程尽可能简单以达到尽可能高的时间...
  • 目标跟踪概念、多目标跟踪算法SORT和deep SORT原理

    千次阅读 多人点赞 2019-05-29 19:56:45
    文章目录目标跟踪、单目标跟踪、多目标跟踪的概念在线多目标跟踪sort算法原理SORT算法过程简述估计模型(卡尔曼滤波跟踪器) 目标跟踪、单目标跟踪、多目标跟踪的概念 目标跟踪分为静态背景下的目标跟踪和动态背景下...
  • 现在我们开始定位目标的坐标。 这个过程包括几个子部分: 1. 标记检测 2. 指定世界坐标系,求相机外参 3. 确定目标的坐标 相机姿态估计 Aruco Marker是一种特殊的二维码,来源于美国中央俄克拉荷马大学(UCO),...
  • 目标检测(十三)--MultiPathNet

    千次阅读 2017-09-12 10:08:01
    以及没有选择,会为不是很有趣的图像区域生成mask 训练一个单独的深度网络来对每一个DeepMask产生的mask的物体类型进行分类(包括“无”),采用R-CNN 改进是使用DeepMask作为R-CNN的第一阶段。 对于RCNN...
  • 1)多假设多目标追踪算法(MHT,基于kalman在多目标上的拓展) 多假设跟踪算法(MHT)是非常经典的多目标跟踪算法,由Reid在对雷达信号的自动跟踪研究中提出,本质上是基于Kalman滤波跟踪算法在多目标跟踪问题中的扩展...
  • ubuntu 如何生成私钥,公钥

    千次阅读 2014-03-11 12:39:05
    SSH(SecureShell)是目前比较可靠的为远程登录会话和其他网络服务提供安全的协议。利用SSH协议可以有效防止远程管理过程中的信息泄露问题。通过SSH,可以把所有传输的数据进行加密,也能够防止DNS欺骗和IP欺骗。...
  • 深度视觉语义对齐用于生成图像描述 Deep Visual-Semantic Alignments for Generating Image DescriptionsAndrej Karpathy Li Fei-Fei 摘要本文提出了一种方法,可以用于生成图像的自然语言描述。主要包含了两个...
  • 注:本文源自本人的硕士毕业论文,未经许可,严禁转载! ...物体识别是判断是否存在目标,确定目标位置,识别目标种类的一种计算机视觉任务。人类生产生活中存在着大量的图像和视频数据,计算机...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 66,867
精华内容 26,746
关键字:

下列属于生成性目标的是