您当前的位置: 首页 >  网络
  • 3浏览

    0关注

    417博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

目标检测0-02:YOLO V3-网络结构输入输出解析

江南才尽,年少无知! 发布时间:2019-08-03 17:47:39 ,浏览量:3

以下链接是个人关于YOLO V3所有见解,如有错误欢迎大家指出,我会第一时间纠正,如有兴趣可以加微信:17575010159 相互讨论技术。 目标检测0-00:YOLO V3目录-史上最全

一、源码目录总览
tensorflow-yolov3-master-bk
    ├── checkpoint //保存模型的目录
    ├── convert_weight.py//对权重进行转换,为了模型的预训练
    ├── core//核心代码文件夹
    │   ├── backbone.py
    │   ├── common.py
    │   ├── config.py//配置文件
    │   ├── dataset.py//数据处理
    │   ├── __init__.py
    │   ├── __pycache__
    │   │   ├── backbone.cpython-36.pyc
    │   │   ├── common.cpython-36.pyc
    │   │   ├── config.cpython-36.pyc
    │   │   ├── dataset.cpython-36.pyc
    │   │   ├── __init__.cpython-36.pyc
    │   │   ├── utils.cpython-36.pyc
    │   │   └── yolov3.cpython-36.pyc
    │   ├── utils.py
    │   └── yolov3.py//网络核心结构
    ├── data
    │   ├── anchors//预训练框
    │   │   ├── basline_anchors.txt
    │   │   └── coco_anchors.txt
    │   ├── classes//训练预测目标的种类
    │   │   ├── coco.names
    │   │   └── voc.names
    │   ├── dataset//保存图片的相关信息:路径,box,置信度,类别编号
    │   │   ├── voc_test.txt//测试数据
    │   │   └── voc_train.txt//训练数据
    │   └── log//存储log
    │       └── events.out.tfevents.1564706916.WIN-RCRPPSUQJFP
    ├── docs//比较混杂
    │   ├── Box-Clustering.ipynb//根据数据信息生成预选框anchors
    │   ├── images
    │   │   ├── 611_result.jpg
    │   │   ├── darknet53.png
    │   │   ├── iou.png
    │   │   ├── K-means.png
    │   │   ├── levio.jpeg
    │   │   ├── probability_extraction.png
    │   │   ├── road.jpeg
    │   │   ├── road.mp4
    │   │   └── yolov3.png
    │   └── requirements.txt//环境搭建
    ├── evaluate.py//模型评估
    ├── freeze_graph.py//生成pb文件
    ├── image_demo.py//一张图片测试的demo
    ├── LICENSE
    ├── LICENSE.fuck
    ├── mAP//模型评估相关信息存储
    │   ├── extra
    │   │   ├── class_list.txt
    │   │   ├── convert_gt_xml.py
    │   │   ├── convert_gt_yolo.py
    │   │   ├── convert_keras-yolo3.py
    │   │   ├── convert_pred_darkflow_json.py
    │   │   ├── convert_pred_yolo.py
    │   │   ├── find_class.py
    │   │   ├── intersect-gt-and-pred.py
    │   │   ├── README.md
    │   │   ├── remove_class.py
    │   │   ├── remove_delimiter_char.py
    │   │   ├── remove_space.py
    │   │   ├── rename_class.py
    │   │   └── result.txt
    │   ├── __init__.py
    │   └── main.py
    ├── README.md
    ├── scripts
    │   ├── show_bboxes.py
    │   └── voc_annotation.py//把xml转化为网络可以使用的txt文件
    ├── train.py//模型训练
    └── video_demo.py//视屏测试的demo

上述没有注释部分,后续再填充,下面的讲解都是基于VOC数据

二、YOLO V3输入

当我们拿到一个网络得时候,首先我们要知道一个网络得输入和输出是什么,这样我们能尽快的理解这个网络。按照大佬源码中的操作,运行scripts/voc_annotation.py之后,我们得到data/dataset中的voc_test与voc_train文件,其格式如下:

../VOC/train/VOCdevkit/VOC2007\JPEGImages\000005.jpg 263,211,324,339,8 165,264,253,372,8 241,194,295,299,8

首选是图像的路径,然后是图像中所包含box左上角和右下角的坐标以及其所属类别,即路径后面,每五个数字,可以表示一个box,以及这个box对应类别的编号。有了这样的数据信息之后我们就能进行训练了,即能够执行train.py文件。

再继续讲解之前,我们需要了解一下yolo V3的结构,yoloV3不同于之前的yolo1与yolo2,其使用了图像金字塔的思想,对一张图片进行了3次降采样,分别为8,16,32。表示我们输入的图像必须是32的倍数,不然没有办法进行32倍的降采样。如果对yolo1,与yolo2不熟悉,可以看看以下的文章: YOLOv1到YOLOv3的演变过程及每个算法详解 总的来说,其就是把一张图片分成了很多grid(网格)或者cell(细胞)。就拿yolo1来说,假设一张下面的图片: 在这里插入图片描述 这张图片的大小为(448,448),其被划分之后变成(7,7)。即每个每个gred都代表着(64,64)的视野。YOLO1会对每个gred进行两个box的检测,每个box所包含的信息如下:box的坐标(4个数字),该box物体的置信度(1个数字),该box所属于各种类别的概率(有多个类别就需要多好数字表示,如果VOC数据,则为20个)。所以我们最后要描述一张图像的信息需要(7,7,(4+1)*2+20)=(7,7,30)的矩阵。这样我们可以描述出来一张图片的信息,如下:在这里插入图片描述 上面是YOLO1的处理,对于YOLO3的原理也是类似的,只是YOLO3采用了图像金字他的思想,做了3次变化,如假设输入一张(416,416)的图片,经过3次(8,16,32)下采样变换之后为sacel[(52,52), (26,26), (13,13)]。可以这样理解,一张图片使用3种方式进行描述:8倍下采样得到特征图,每个网格可以代表原图种8个像素的感受野。16倍下采样得到特征图,每个网格可以代表原图种16个像素的感受野。32倍下采样得到特征图,每个网格可以代表原图种32个像素的感受野(重复了一些废话,大家不要介意)。

那么同样的道理,一张图片根据不同的划分,我们使用3种方式去描述,每种方式,我们对其中的每个gred都要进行3次预测,是的,在YOLO3中,其会对每个网格进行3中预测。每次预测和YOLO1一样,需要box的坐标(4个数字),该box物体的置信度(1个数字),该box所属于各种类别的概率。那么对于 方式一8倍降采样需要[52,52,3*((4+1)+20)]=[52,52,75] 方式一16倍降采样需要[26,26,3*((4+1)+20)]=[26,26,75] 方式一32倍降采样需要[26,26,3*((4+1)+20)]=[13,13,75] 讲解到这里,也差不多了,如果想详细了解,可以通过一下两篇文章(再这里表示对大佬们的感谢) 目标检测之YoloV1论文及tensorflow实现 搞懂YOLO v1看这篇就够了

现在看看我们的网络输入需要什么,再train.py文件中可以找到如下:

self.input_data   = tf.placeholder(dtype=tf.float32, name='input_data') #图片的像素

#8倍下采样之后图片对应的box(每个gred,通过anchors变换)
self.label_sbbox  = tf.placeholder(dtype=tf.float32, name='label_sbbox') 
#16倍下采样之后图片对应的box(每个gred,通过anchors变换)
self.label_mbbox  = tf.placeholder(dtype=tf.float32, name='label_mbbox') 
#32倍下采样之后图片对应的box(每个gred,通过anchors变换)
self.label_lbbox  = tf.placeholder(dtype=tf.float32, name='label_lbbox') 

#8倍下采样之后图片对应真实的box,没有通过anchors变换
self.true_sbboxes = tf.placeholder(dtype=tf.float32, name='sbboxes')
#16倍下采样之后图片对应真实的box,没有通过anchors变换
self.true_mbboxes = tf.placeholder(dtype=tf.float32, name='mbboxes')
#32倍下采样之后图片对应真实的box,没有通过anchors变换
self.true_lbboxes = tf.placeholder(dtype=tf.float32, name='lbboxes')

self.trainable     = tf.placeholder(dtype=tf.bool, name='training')

我相信大家现在对anchors已经十分好奇了,那么他到底是什么呢?如果等不急的朋友可以观看下面链接: YOLO-v3模型参数anchor设置 其就是一个预验框,那么什么是预验框呢?简单的来说,就是我们希望训练出来的后的网络,在对数据进行预测的时候,不喜欢他预测的框是千奇百怪的,所以我们给定一些框让网络去学习,即以后预测的框,尽量和我们给出的框比较接近。这里的框表示的是长度,和宽度。后面会详细的讲解,我们继续往下,慢慢就明白是怎么回事了。

placeholder表示的是占位符,那么他的数据到底是怎么来的呢?根据train.py中的pbar = tqdm(self.trainset),一路追踪下去,可以知道其数据的预处理在core/dataset.py完成,其代码注释如下:

#! /usr/bin/env python
# coding=utf-8
#================================================================
#   Copyright (C) 2019 * Ltd. All rights reserved.
#
#   Editor      : VIM
#   File name   : dataset.py
#   Author      : YunYang1994
#   Created date: 2019-03-15 18:05:03
#   Description :
#
#================================================================

import os
import cv2
import random
import numpy as np
import tensorflow as tf
import core.utils as utils
from core.config import cfg


np.set_printoptions(suppress=True, threshold=np.nan)

class Dataset(object):
    """implement Dataset here"""
    def __init__(self, dataset_type):

        # 数据注释文件的路径,此处为"./data/dataset/voc_test.txt"
        self.annot_path  = cfg.TRAIN.ANNOT_PATH if dataset_type == 'train' else cfg.TEST.ANNOT_PATH

        # 数据输入图像的大小,为了增加网络的鲁棒性,使用了随机[320, 352, 384, 416, 448, 480, 512, 544, 576, 608]
        # 中任意一种大小,注意,该处必须为32的倍数
        self.input_sizes = cfg.TRAIN.INPUT_SIZE if dataset_type == 'train' else cfg.TEST.INPUT_SIZE

        # 数据的BATCH_SIZE,由于本人电脑不好,故设置为1
        self.batch_size  = cfg.TRAIN.BATCH_SIZE if dataset_type == 'train' else cfg.TEST.BATCH_SIZE

        # 是否开启AUG数据增强,该处为True
        self.data_aug    = cfg.TRAIN.DATA_AUG   if dataset_type == 'train' else cfg.TEST.DATA_AUG

        # 训练数据输入大小
        self.train_input_sizes = cfg.TRAIN.INPUT_SIZE

        # 3中下采样方式,为[8, 16, 32]
        self.strides = np.array(cfg.YOLO.STRIDES)

        # 训练数据的类别,使用VOC数据共20中,来自"./data/classes/voc.names"
        self.classes = utils.read_class_names(cfg.YOLO.CLASSES)

        # 种类的数目,针对VOC为20
        self.num_classes = len(self.classes)

        # 来自于"./data/anchors/basline_anchors.txt",该文件的生成于docs/Box-Clustering.ipynb
        self.anchors = np.array(utils.get_anchors(cfg.YOLO.ANCHORS))

        # 对每个gred(网格)预测几个box,该处为3
        self.anchor_per_scale = cfg.YOLO.ANCHOR_PER_SCALE

        # 一张图像中,允许存在最多的box数数目
        self.max_bbox_per_scale = 150

        # 加载数据,该处为训练数据,即"./data/classes/voc.names"内容
        self.annotations = self.load_annotations(dataset_type)

        # 计算训练样本的总数目
        self.num_samples = len(self.annotations)

        # 需要多个num_batchs才能完成一个EPOCHS
        self.num_batchs = int(np.ceil(self.num_samples / self.batch_size))

        # 用于batch的技术,达到num_batchs代表训练了一个EPOCHS
        self.batch_count = 0


    def load_annotations(self, dataset_type):
        """
        随机读取"./data/classes/voc.names"中的内容
        :param dataset_type:
        :return:
        """
        with open(self.annot_path, 'r') as f:
            txt = f.readlines()
            annotations = [line.strip() for line in txt if len(line.strip().split()[1:]) != 0]
        np.random.shuffle(annotations)
        return annotations

    def __iter__(self):
        return self

    def __next__(self):
        """
        数据处理的核心函数,为了形象的表达。
        :return:
        """
        with tf.device('/cpu:0'):

            # 从给定的[320, 352, 384, 416, 448, 480, 512, 544, 576, 608]中随机选择大小
            # 为了方便讲解,假设每次随机到的大小为[416,416],注意,实际并非如此
            self.train_input_size = random.choice(self.train_input_sizes)

            # 获得3个输出图像的大小,分别为[8,16,32]下采样之后的大小,即得到[(52,52),(26,26),(13,13)]
            self.train_output_sizes = self.train_input_size // self.strides

            # 用于保存一个batch图片的像素,假设输入图像为[416,416]
            batch_image = np.zeros((self.batch_size, self.train_input_size, self.train_input_size, 3))

            # 存储下采样的实际box信息,这里是通过anchors变换的box,其包含了52x52个gred通过anchors得到的3个box,
            # 即对每张图片,进行[8,16,32]下采样,然后对其中的每个gred都画出3个box来,box的绘画根据anchors决定
            batch_label_sbbox = np.zeros((self.batch_size, self.train_output_sizes[0], self.train_output_sizes[0],
                                          self.anchor_per_scale, 5 + self.num_classes))
            batch_label_mbbox = np.zeros((self.batch_size, self.train_output_sizes[1], self.train_output_sizes[1],
                                          self.anchor_per_scale, 5 + self.num_classes))
            batch_label_lbbox = np.zeros((self.batch_size, self.train_output_sizes[2], self.train_output_sizes[2],
                                          self.anchor_per_scale, 5 + self.num_classes))

            # 保存实际的box,注意这里要和gred的box分开来,这里的box是总的box,不是gred的box,分别存储的也是对应[8,16,32]采样之后的
            batch_sbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))
            batch_mbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))
            batch_lbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))


            num = 0 #对当前的图片计算,总数为batchs
            if self.batch_count             
关注
打赏
1592542134
查看更多评论
0.1755s