YOLOv3 史上最强思维导图 !!! yolo一看就懂的流程图!!! 地表最强代码解析 哈哈哈

基于TF 1.5版本的 YOLOV3

其中向下的箭头很多实在是懒得打,基本对齐的下面都有向下的箭头。代码中SPP池化层跟我了解到的标准SPP的定义不太一样,这里我按照代码写的,那个SPP最后是concat  不是简单加法!!!(一个疑问 SPP后的东西 又跟inputs concat了 那不又不是定长了...)

绝大部分卷积层的padding 为 same 只有 darknet53 中 有三个部分有 valid padding的(红快标出来了)

整个结构都是残差的结构 其中 SPP和detect_layer 的部分有些复杂 

这版本的源码 我分析了一下这里还是有很多可以简化的地方  大家也可以帮忙指出错误: 

# -*- coding: utf-8 -*-

import numpy as np
import tensorflow as tf

slim = tf.contrib.slim

_BATCH_NORM_DECAY = 0.9
_BATCH_NORM_EPSILON = 1e-05
_LEAKY_RELU = 0.1

_ANCHORS = [(10, 13), (16, 30), (33, 23),
            (30, 61), (62, 45), (59, 119),
            (116, 90), (156, 198), (373, 326)]

#darknet53模块
def darknet53(inputs):
    """
    Builds Darknet-53 model.
    """
    inputs = _conv2d_fixed_padding(inputs, 32, 3)
    inputs = _conv2d_fixed_padding(inputs, 64, 3, strides=2)
    inputs = _darknet53_block(inputs, 32)
    inputs = _conv2d_fixed_padding(inputs, 128, 3, strides=2)

    for i in range(2):
        inputs = _darknet53_block(inputs, 64)

    inputs = _conv2d_fixed_padding(inputs, 256, 3, strides=2)

    for i in range(8):
        inputs = _darknet53_block(inputs, 128)

    route_1 = inputs
    inputs = _conv2d_fixed_padding(inputs, 512, 3, strides=2)

    for i in range(8):
        inputs = _darknet53_block(inputs, 256)

    route_2 = inputs
    inputs = _conv2d_fixed_padding(inputs, 1024, 3, strides=2)

    for i in range(4):
        inputs = _darknet53_block(inputs, 512)

    return route_1, route_2, inputs

#大部分时间都是 conv2d 的same padding 很少情况是valid 
def _conv2d_fixed_padding(inputs, filters, kernel_size, strides=1):
    if strides > 1:
        inputs = _fixed_padding(inputs, kernel_size)#就是pad到原来的尺寸 省着conv2d后缩小了
    inputs = slim.conv2d(inputs, filters, kernel_size, stride=strides,
                         padding=('SAME' if strides == 1 else 'VALID'))
    return inputs

# 固定的一个模式 有残差
def _darknet53_block(inputs, filters):
    shortcut = inputs
    inputs = _conv2d_fixed_padding(inputs, filters, 1)
    inputs = _conv2d_fixed_padding(inputs, filters * 2, 3)

    inputs = inputs + shortcut
    return inputs

#金字塔池化层 用来将不同大小的经过conv层的特征 做成一个固定size 的(黑人问好这里)
def _spp_block(inputs, data_format='NCHW'):
    return tf.concat([slim.max_pool2d(inputs, 13, 1, 'SAME'),# 13 就是kernel大小 右面的1 stride
                      slim.max_pool2d(inputs, 9, 1, 'SAME'),
                      slim.max_pool2d(inputs, 5, 1, 'SAME'),
                      inputs],
                     axis=1 if data_format == 'NCHW' else 3)


@tf.contrib.framework.add_arg_scope #这里的意思是 当使用 with argscope 的时候 可以设定某些通用传入参数 相当于修改定义函数时候的默认值
def _fixed_padding(inputs, kernel_size, *args, mode='CONSTANT', **kwargs):
    """
    Pads the input along the spatial dimensions independently of input size.

    Args:
      inputs: A tensor of size [batch, channels, height_in, width_in] or
        [batch, height_in, width_in, channels] depending on data_format.
      kernel_size: The kernel to be used in the conv2d or max_pool2d operation.
                   Should be a positive integer.
      data_format: The input format ('NHWC' or 'NCHW').
      mode: The mode for tf.pad.

    Returns:
      A tensor with the same format as the input with the data either intact
      (if kernel_size == 1) or padded (if kernel_size > 1).
    """
    pad_total = kernel_size - 1
    pad_beg = pad_total // 2
    pad_end = pad_total - pad_beg

    if kwargs['data_format'] == 'NCHW':
        padded_inputs = tf.pad(inputs, [[0, 0], [0, 0],
                                        [pad_beg, pad_end],
                                        [pad_beg, pad_end]],
                               mode=mode)
    else:
        padded_inputs = tf.pad(inputs, [[0, 0], [pad_beg, pad_end],
                                        [pad_beg, pad_end], [0, 0]], mode=mode)
    return padded_inputs

#最后部分卷积运算及SPP运算
def _yolo_block(inputs, filters, data_format='NCHW', with_spp=False):
    inputs = _conv2d_fixed_padding(inputs, filters, 1)
    inputs = _conv2d_fixed_padding(inputs, filters * 2, 3)
    inputs = _conv2d_fixed_padding(inputs, filters, 1)

    if with_spp:
        inputs = _spp_block(inputs, data_format)
        inputs = _conv2d_fixed_padding(inputs, filters, 1)

    inputs = _conv2d_fixed_padding(inputs, filters * 2, 3)
    inputs = _conv2d_fixed_padding(inputs, filters, 1)
    route = inputs
    inputs = _conv2d_fixed_padding(inputs, filters * 2, 3)
    return route, inputs

#返回 channel H W 这三个值
def _get_size(shape, data_format):
    if len(shape) == 4:
        shape = shape[1:]
    return shape[1:3] if data_format == 'NCHW' else shape[0:2]

#将 做完CONV2D运算后 将其各个部分组装还原到图像上
def _detection_layer(inputs, num_classes, anchors, img_size, data_format):
    num_anchors = len(anchors)
    predictions = slim.conv2d(inputs, num_anchors * (5 + num_classes), 1,
                              stride=1, normalizer_fn=None,
                              activation_fn=None,
                              biases_initializer=tf.zeros_initializer())
    #prediction 是 卷积完成后的结果
    shape = predictions.get_shape().as_list() #输出的NCHW 维度
    # 这里得到的gridsize 是H W
    grid_size = _get_size(shape, data_format)
    #DIM = H*W
    dim = grid_size[0] * grid_size[1]
    # 这里构造的是 最终输出 每个结果的一个长度 5 + class数量 5是 h w 宽 高 可信度 class数量是one hot
    bbox_attrs = 5 + num_classes

    if data_format == 'NCHW':
        predictions = tf.reshape(
            predictions, [-1, num_anchors * bbox_attrs, dim]) #这里的anchor 就是取样用box
        predictions = tf.transpose(predictions, [0, 2, 1])
    # 将预测结果 重新排布
    predictions = tf.reshape(predictions, [-1, num_anchors * dim, bbox_attrs]) #-1基本等于N
    # 用图片H 除以 输出H 的到stride
    stride = (img_size[0] // grid_size[0], img_size[1] // grid_size[1])
    # 用 anchor box H 除以 stride h 更新anchor
    anchors = [(a[0] / stride[0], a[1] / stride[1]) for a in anchors] #stride 相当于缩小的倍数 这里就是把anchor 除stride 得到 在最后输出的HW上用的 anchor 的大小

    #从预测结果中拿到 中心点 大小 可信度 分类
    box_centers, box_sizes, confidence, classes = tf.split(
        predictions, [2, 2, 1, num_classes], axis=-1)

    #将其放入到sigmoid 中 归一化
    box_centers = tf.nn.sigmoid(box_centers)
    confidence = tf.nn.sigmoid(confidence)
    #
    grid_x = tf.range(grid_size[0], dtype=tf.float32)# 输出的 0 1 2...H的list
    grid_y = tf.range(grid_size[1], dtype=tf.float32)# 同上 换成 w
    a, b = tf.meshgrid(grid_x, grid_y) #将向量扩增为矩阵 扩增为 x(列数)*y(列数) 矩阵 a每一行都是grid_x b每一列都是grid_y

    x_offset = tf.reshape(a, (-1, 1))# 一行一行展平 平铺
    y_offset = tf.reshape(b, (-1, 1))

    x_y_offset = tf.concat([x_offset, y_offset], axis=-1) #连成一列
    x_y_offset = tf.reshape(tf.tile(x_y_offset, [1, num_anchors]), [1, -1, 2])#将其复制几份 anchor 决定 然后reshap 将其改成 1,x,2(这里的2代表 h w )

    # 所有位置的 所有anchor box 的中心点 (这时候是缩放到输出图像大小的)
    box_centers = box_centers + x_y_offset
    box_centers = box_centers * stride # 在缩放会原图大小

    anchors = tf.tile(anchors, [dim, 1])#将anchor box 复制输出 H*W份
    box_sizes = tf.exp(box_sizes) * anchors
    box_sizes = box_sizes * stride

    detections = tf.concat([box_centers, box_sizes, confidence], axis=-1)

    classes = tf.nn.sigmoid(classes)
    predictions = tf.concat([detections, classes], axis=-1)
    return predictions

#向上取样 利用 tf.image.resize_nearest_neighbor模式
def _upsample(inputs, out_shape, data_format='NCHW'):
    # tf.image.resize_nearest_neighbor accepts input in format NHWC
    if data_format == 'NCHW':
        inputs = tf.transpose(inputs, [0, 2, 3, 1])

    if data_format == 'NCHW':
        new_height = out_shape[3]
        new_width = out_shape[2]
    else:
        new_height = out_shape[2]
        new_width = out_shape[1]

    inputs = tf.image.resize_nearest_neighbor(inputs, (new_height, new_width))

    # back to NCHW if needed
    if data_format == 'NCHW':
        inputs = tf.transpose(inputs, [0, 3, 1, 2])

    inputs = tf.identity(inputs, name='upsampled')
    return inputs

# 相当于MAIN
def yolo_v3(inputs, num_classes, is_training=False, data_format='NCHW', reuse=False, with_spp=False):
    """
    Creates YOLO v3 model.

    :param inputs: a 4-D tensor of size [batch_size, height, width, channels].
        Dimension batch_size may be undefined. The channel order is RGB.
    :param num_classes: number of predicted classes.
    :param is_training: whether is training or not.
    :param data_format: data format NCHW or NHWC.
    :param reuse: whether or not the network and its variables should be reused.
    :param with_spp: whether or not is using spp layer.
    :return:
    """
    # it will be needed later on #这里顺序不对 NHWC 时候是 HW NCHW的话 就是CH了 哈哈 我把他放到下一句后面
    #img_size = inputs.get_shape().as_list()[1:3]

    # transpose the inputs to NCHW
    if data_format == 'NCHW':
        inputs = tf.transpose(inputs, [0, 3, 1, 2])#转成 NHWC  [0, 3, 1, 2]这个里面代表把原来地方放到新的哪里 c 放到 3的位置 H放到 1的位置...

    img_size = inputs.get_shape().as_list()[1:3]
    # normalize values to range [0..1] 归一化
    inputs = inputs / 255

    # set batch norm params
    batch_norm_params = {
        'decay': _BATCH_NORM_DECAY,
        'epsilon': _BATCH_NORM_EPSILON,
        'scale': True,
        'is_training': is_training,
        'fused': None,  # Use fused batch norm if possible.
    }

    # Set activation_fn and parameters for conv2d, batch_norm.
    with slim.arg_scope([slim.conv2d, slim.batch_norm, _fixed_padding], data_format=data_format, reuse=reuse):
        with slim.arg_scope([slim.conv2d], normalizer_fn=slim.batch_norm,
                            normalizer_params=batch_norm_params,
                            biases_initializer=None,
                            activation_fn=lambda x: tf.nn.leaky_relu(x, alpha=_LEAKY_RELU)):
            #上面就是将 conv2d 设置一个默认参数
            with tf.variable_scope('darknet-53'):
                route_1, route_2, inputs = darknet53(inputs)
            #darknet 53 部分运算完事

            with tf.variable_scope('yolo-v3'):
                #进入到yolo部分
                route, inputs = _yolo_block(inputs, 512, data_format, with_spp)

                detect_1 = _detection_layer(
                    inputs, num_classes, _ANCHORS[6:9], img_size, data_format)
                detect_1 = tf.identity(detect_1, name='detect_1') #复制一个(感觉就是加个名字)
                #分多个层就因为 darknet53 输出的时候就是多个
                inputs = _conv2d_fixed_padding(route, 256, 1)
                upsample_size = route_2.get_shape().as_list()
                inputs = _upsample(inputs, upsample_size, data_format)
                inputs = tf.concat([inputs, route_2],
                                   axis=1 if data_format == 'NCHW' else 3)
                route, inputs = _yolo_block(inputs, 256)
                detect_2 = _detection_layer(
                    inputs, num_classes, _ANCHORS[3:6], img_size, data_format)
                detect_2 = tf.identity(detect_2, name='detect_2')

                inputs = _conv2d_fixed_padding(route, 128, 1)
                upsample_size = route_1.get_shape().as_list()
                inputs = _upsample(inputs, upsample_size, data_format)
                inputs = tf.concat([inputs, route_1],
                                   axis=1 if data_format == 'NCHW' else 3)

                _, inputs = _yolo_block(inputs, 128)

                detect_3 = _detection_layer(
                    inputs, num_classes, _ANCHORS[0:3], img_size, data_format)
                detect_3 = tf.identity(detect_3, name='detect_3')

                detections = tf.concat([detect_1, detect_2, detect_3], axis=1)
                detections = tf.identity(detections, name='detections')
                return detections


def yolo_v3_spp(inputs, num_classes, is_training=False, data_format='NCHW', reuse=False):
    """
    Creates YOLO v3 with SPP  model.

    :param inputs: a 4-D tensor of size [batch_size, height, width, channels].
        Dimension batch_size may be undefined. The channel order is RGB.
    :param num_classes: number of predicted classes.
    :param is_training: whether is training or not.
    :param data_format: data format NCHW or NHWC.
    :param reuse: whether or not the network and its variables should be reused.
    :return:
    """
    return yolo_v3(inputs, num_classes, is_training=is_training, data_format=data_format, reuse=reuse, with_spp=True)

 

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值