1. 程式人生 > >【Deep Learning】YOLO_v1 的 TensorFlow 原始碼分析

【Deep Learning】YOLO_v1 的 TensorFlow 原始碼分析

  本文是對上一篇文章的繼續補充,在這裡首先說明,這個 TensorFlow 版本的原始碼 來自於 hizhangp/yolo_tensorflow,經過部分細節的調整執行在我的裝置上,我使用的環境是Win10+GPU+OpenCV 3.3 + Python3.6 +TensorFlow1.4 和 Ubuntu16.04 + GPU+OpenCV 3.3 + Python3.6 + TensorFlow 1.4 。

1.準備工作

  github 的原始碼頁面如圖所示:
這裡寫圖片描述
  這裡我們將所有檔案clone 或 下載到本地後,再根據 README 的連線下載相應的檔案放在相應的資料夾內,主要需要下載的是 pascal VOC 2007 資料集和 一個 small_yolo的ckpt檔案,這裡,考慮到外網可能不太好下載,這裡給出百度網盤的下載連結:

  下載好後分別放在相應的資料夾內即可,我使用的 Python 編譯器是 Pycharm,這裡可以看一下大致的目錄結構,如下所示:
這裡寫圖片描述
  將這些都準備好後,便可以直接執行 test 進行檢測了,過程中可能會出現一些報錯,主要出錯的問題我遇到了兩個,分別是

  1. print 報錯,這主要是因為我是用的 Python 版本是 Python3,只需要在相應的位置加上括號即可
  2. cv2.AA 報錯,主要原因應該是 OpenCV 版本問題,解決方法是將 cv2.CV_AA 改為 cv2.LINE_AA

  在改好後,直接執行程式,得到的是檢測影象的結果,如下所示:
這裡寫圖片描述
這裡寫圖片描述
  此外,程式還提供了實時呼叫攝像頭進行檢測的程式碼,在電腦上接好攝像頭並將相應部分取消註釋直接執行即可,效果如下所示:
這裡寫圖片描述


  可以看出,雖然是對藝術作品進行檢測,但是Yolo 仍然能夠獲得比較好的檢測效果,而且能夠達到實時的效果。
  下面分別介紹專案中的各個程式:

2. config.py

  這部分程式主要是用來定義網路中的一些整體結構引數,具體程式如下所示:

import os

#
# path and dataset parameter
#
# 第一部分是使用到的資料相關引數,包括資料路徑預訓練權重等相關內容。
DATA_PATH = 'data'

PASCAL_PATH = os.path.join(DATA_PATH, 'pascal_voc')

CACHE_PATH = os.path.join(PASCAL_PATH, 'cache'
) OUTPUT_DIR = os.path.join(PASCAL_PATH, 'output') WEIGHTS_DIR = os.path.join(PASCAL_PATH, 'weight') WEIGHTS_FILE = None # WEIGHTS_FILE = os.path.join(DATA_PATH, 'weights', 'YOLO_small.ckpt') CLASSES = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'] # 是否對樣本影象進行flip(水平映象)操作 FLIPPED = True # # model parameter # # 這部分主要是模型引數 # 影象size IMAGE_SIZE = 448 # 網格 size CELL_SIZE = 7 # 每個 cell 中 bounding box 數量 BOXES_PER_CELL = 2 # 權重衰減相關引數 ALPHA = 0.1 DISP_CONSOLE = False # 權重衰減的相關引數 OBJECT_SCALE = 1.0 NOOBJECT_SCALE = 0.5 CLASS_SCALE = 2.0 COORD_SCALE = 5.0 # # solver parameter # # 訓練過程中的相關引數 GPU = '' # 學習速率 LEARNING_RATE = 0.0001 # 衰減步數 DECAY_STEPS = 30000 # 衰減率 DECAY_RATE = 0.1 STAIRCASE = True # batch_size初始值為45 BATCH_SIZE = 16 # 最大迭代次數 MAX_ITER = 15000 # 日誌記錄迭代步數 SUMMARY_ITER = 10 SAVE_ITER = 1000 # # test parameter # # 測試時的相關引數 # 閾值引數 THRESHOLD = 0.2 # IoU 引數 IOU_THRESHOLD = 0.5

  因為這部分程式比較簡單,具體內容可以參考上面的註釋。

3. yolo_net.py

3.1 def __init__(self, is_training=True)

  首先,來看第一部分:

    def __init__(self, is_training=True):
        self.classes = cfg.CLASSES
        self.num_class = len(self.classes)
        self.image_size = cfg.IMAGE_SIZE
        self.cell_size = cfg.CELL_SIZE
        self.boxes_per_cell = cfg.BOXES_PER_CELL
        self.output_size = (self.cell_size * self.cell_size) * (self.num_class + self.boxes_per_cell * 5)
        self.scale = 1.0 * self.image_size / self.cell_size
        self.boundary1 = self.cell_size * self.cell_size * self.num_class
        self.boundary2 = self.boundary1 + self.cell_size * self.cell_size * self.boxes_per_cell

        self.object_scale = cfg.OBJECT_SCALE
        self.noobject_scale = cfg.NOOBJECT_SCALE
        self.class_scale = cfg.CLASS_SCALE
        self.coord_scale = cfg.COORD_SCALE

        self.learning_rate = cfg.LEARNING_RATE
        self.batch_size = cfg.BATCH_SIZE
        self.alpha = cfg.ALPHA

        self.offset = np.transpose(np.reshape(np.array(
            [np.arange(self.cell_size)] * self.cell_size * self.boxes_per_cell),
            (self.boxes_per_cell, self.cell_size, self.cell_size)), (1, 2, 0))

        self.images = tf.placeholder(tf.float32, [None, self.image_size, self.image_size, 3], name='images')
        self.logits = self.build_network(self.images, num_outputs=self.output_size, alpha=self.alpha, is_training=is_training)

        if is_training:
            self.labels = tf.placeholder(tf.float32, [None, self.cell_size, self.cell_size, 5 + self.num_class])
            self.loss_layer(self.logits, self.labels)
            self.total_loss = tf.losses.get_total_loss()
            tf.summary.scalar('total_loss', self.total_loss)

  這部分的主要作用是利用 cfg 檔案對網路引數進行初始化,同時定義網路的輸入和輸出 size 等資訊,其中 offset 的作用應該是一個定長的偏移;
  boundery1和boundery2 作用是在輸出中確定每種資訊的長度(如類別,置信度等)。其中 boundery1 指的是對於所有的 cell 的類別的預測的張量維度,所以是 self.cell_size * self.cell_size * self.num_class
boundery2 指的是在類別之後每個cell 所對應的 bounding boxes 的數量的總和,所以是self.boundary1 + self.cell_size * self.cell_size * self.boxes_per_cell

3.2 build_network

  這部分主要是實現了 yolo 網路模型的構成,可以清楚的看到網路的組成,而且為了使程式更加簡潔,構建網路使用的是 TensorFlow 中的 slim 模組,主要的函式有slim.arg_scope slim.conv2d slim.fully_connectedslim.dropoout等,具體程式如下所示:

    def build_network(self,
                      images,
                      num_outputs,
                      alpha,
                      keep_prob=0.5,
                      is_training=True,
                      scope='yolo'):
        with tf.variable_scope(scope):
            with slim.arg_scope([slim.conv2d, slim.fully_connected],
                                activation_fn=leaky_relu(alpha),
                                weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),
                                weights_regularizer=slim.l2_regularizer(0.0005)):
                net = tf.pad(images, np.array([[0, 0], [3, 3], [3, 3], [0, 0]]), name='pad_1')
                net = slim.conv2d(net, 64, 7, 2, padding='VALID', scope='conv_2')
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_3')
                net = slim.conv2d(net, 192, 3, scope='conv_4')
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_5')
                net = slim.conv2d(net, 128, 1, scope='conv_6')
                net = slim.conv2d(net, 256, 3, scope='conv_7')
                net = slim.conv2d(net, 256, 1, scope='conv_8')
                net = slim.conv2d(net, 512, 3, scope='conv_9')
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_10')
                net = slim.conv2d(net, 256, 1, scope='conv_11')
                net = slim.conv2d(net, 512, 3, scope='conv_12')
                net = slim.conv2d(net, 256, 1, scope='conv_13')
                net = slim.conv2d(net, 512, 3, scope='conv_14')
                net = slim.conv2d(net, 256, 1, scope='conv_15')
                net = slim.conv2d(net, 512, 3, scope='conv_16')
                net = slim.conv2d(net, 256, 1, scope='conv_17')
                net = slim.conv2d(net, 512, 3, scope='conv_18')
                net = slim.conv2d(net, 512, 1, scope='conv_19')
                net = slim.conv2d(net, 1024, 3, scope='conv_20')
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_21')
                net = slim.conv2d(net, 512, 1, scope='conv_22')
                net = slim.conv2d(net, 1024, 3, scope='conv_23')
                net = slim.conv2d(net, 512, 1, scope='conv_24')
                net = slim.conv2d(net, 1024, 3, scope='conv_25')
                net = slim.conv2d(net, 1024, 3, scope='conv_26')
                net = tf.pad(net, np.array([[0, 0], [1, 1], [1, 1], [0, 0]]), name='pad_27')
                net = slim.conv2d(net, 1024, 3, 2, padding='VALID', scope='conv_28')
                net = slim.conv2d(net, 1024, 3, scope='conv_29')
                net = slim.conv2d(net, 1024, 3, scope='conv_30')
                net = tf.transpose(net, [0, 3, 1, 2], name='trans_31')
                net = slim.flatten(net, scope='flat_32')
                net = slim.fully_connected(net, 512, scope='fc_33')
                net = slim.fully_connected(net, 4096, scope='fc_34')
                net = slim.dropout(net, keep_prob=keep_prob,
                                   is_training=is_training, scope='dropout_35')
                net = slim.fully_connected(net, num_outputs,
                                           activation_fn=None, scope='fc_36')
        return net

  在訓練網路的過程中,我們使用 TensorBoard 可以看到這個網路的結構以及每一層的輸入輸出大小,具體如下所示:
這裡寫圖片描述
  可以看到,網路最後輸出的是一個1470 維的張量(1470 = 7*7*30)。最後一層全連線層的內部如下圖所示:
這裡寫圖片描述

3.3 calc_iou

  這個函式的主要作用是計算兩個 bounding box 之間的 IoU。這部分程式有些地方我還沒有弄沒明白,輸入是兩個 5 維的bounding box,輸出的兩個 bounding Box 的IoU 。具體程式如下所示:

    def calc_iou(self, boxes1, boxes2, scope='iou'):
        """calculate ious
        Args:
          boxes1: 5-D tensor [CELL_SIZE, CELL_SIZE, BOXES_PER_CELL, 4]  ====> (x_center, y_center, w, h)
          boxes2: 1-D tensor [CELL_SIZE, CELL_SIZE, BOXES_PER_CELL, 4] ===> (x_center, y_center, w, h)
        Return:
          iou: 3-D tensor [CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
        """
        with tf.variable_scope(scope):
            boxes1 = tf.stack([boxes1[:, :, :, :, 0] - boxes1[:, :, :, :, 2] / 2.0,
                               boxes1[:, :, :, :, 1] - boxes1[:, :, :, :, 3] / 2.0,
                               boxes1[:, :, :, :, 0] + boxes1[:, :, :, :, 2] / 2.0,
                               boxes1[:, :, :, :, 1] + boxes1[:, :, :, :, 3] / 2.0])
            boxes1 = tf.transpose(boxes1, [1, 2, 3, 4, 0])

            boxes2 = tf.stack([boxes2[:, :, :, :, 0] - boxes2[:, :, :, :, 2] / 2.0,
                               boxes2[:, :, :, :, 1] - boxes2[:, :, :, :, 3] / 2.0,
                               boxes2[:, :, :, :, 0] + boxes2[:, :, :, :, 2] / 2.0,
                               boxes2[:, :, :, :, 1] + boxes2[:, :, :, :, 3] / 2.0])
            boxes2 = tf.transpose(boxes2, [1, 2, 3, 4, 0])

            # calculate the left up point & right down point
            lu = tf.maximum(boxes1[:, :, :, :, :2], boxes2[:, :, :, :, :2])
            rd = tf.minimum(boxes1[:, :, :, :, 2:], boxes2[:, :, :, :, 2:])

            # intersection
            intersection = tf.maximum(0.0, rd - lu)
            inter_square = intersection[:, :, :, :, 0] * intersection[:, :, :, :, 1]

            # calculate the boxs1 square and boxs2 square
            square1 = (boxes1[:, :, :, :, 2] - boxes1[:, :, :, :, 0]) * \
                (boxes1[:, :, :, :, 3] - boxes1[:, :, :, :, 1])
            square2 = (boxes2[:, :, :, :, 2] - boxes2[:, :, :, :, 0]) * \
                (boxes2[:, :, :, :, 3] - boxes2[:, :, :, :, 1])

            union_square = tf.maximum(square1 + square2 - inter_square, 1e-10)

        return tf.clip_by_value(inter_square / union_square, 0.0, 1.0)

  上面這個函式中主要用到的函式有tf.stack tf.transpose 以及 tf.maximum,下面分別簡單介紹一下這幾個函式:

  1. tf.stack(),定義為:def stack(values, axis=0, name="stack")。該函式的主要作用是對矩陣進行拼接,我們在 TensorFlow 原始碼中可以看到這句話tf.stack([x, y, z]) = np.stack([x, y, z]),也就是說,它和numpy 中的 stack 函式的作用是相同的。都是在指定軸的方向上對矩陣進行拼接。預設值是0。具體如下圖:這裡寫圖片描述
  2. tf.transpose,定義為def transpose(a, perm=None, name="transpose") 這個函式的作用是根據 perm 的值對矩陣 a 進行轉置操作,返回陣列的 dimension(尺寸、維度) i與輸入的 perm[i]的維度相一致。如果未給定perm,預設設定為 (n-1…0),這裡的 n 值是輸入變數的 rank 。因此預設情況下,這個操作執行了一個正規(regular)的2維矩形的轉置。具體如下圖:這裡寫圖片描述
  3. tf.maximum,定義為def maximum(x, y, name=None) 這個函式的作用是返回的是a,b之間的最大值。

3.3 loss_layer

  這個函式的主要作用是計算 Loss。具體程式如下所示:

    def loss_layer(self,predicts, labels, scope='loss_layer'):
        with tf.variable_scope(scope):
            # 將預測結果的前 20維(表示類別)轉換為相應的矩陣形式 (類別向量)
            predict_classes = tf.reshape(predicts[:, :self.boundary1], [self.batch_size, self.cell_size, self.cell_size, self.num_class])
            # 將預測結果 的 21 ~ 34 轉換為相應的矩陣形式 (尺度向量?)
            predict_scales = tf.reshape(predicts[:, self.boundary1:self.boundary2], [self.batch_size, self.cell_size, self.cell_size, self.box_per_cell])
            # 將預測結果剩餘的維度轉變為相應的矩陣形式(boxes 所在的位置向量)
            predict_boxes = tf.reshape(predicts[:, self.boundary2:], [self.batch_size, self.cell_size, self.cell_size, self.box_per_cell, 4])

            # 將真實的  labels 轉換為相應的矩陣形式
            response = tf.reshape(labels[:, :, :, 0], [self.batch_size, self.cell_size, self.cell_size, 1])
            boxes = tf.reshape(labels[:, :, :, 1:5], [self.batch_size, self.cell_size, self.cell_size, 1, 4])
            boxes = tf.tile(boxes, [1, 1, 1, self.box_per_cell, 1]) / self.image_size
            classes = labels[:, :, :, 5:]

            offset = tf.constant(self.offset, dtype = tf.float32) # [7, 7, 2]
            offset = tf.reshape(offset, [1, self.cell_size, self.cell_size, self.box_per_cell]) # [1, 7, 7, 2]
            offset = tf.tile(offset, [self.batch_size, 1, 1, 1])  # [batch_size, 7, 7, 1]
            # shape為 [4, batch_size, 7, 7, 2]
            predict_boxes_tran = tf.stack([(predict_boxes[:, :, :, :, 0] + offset) / self.cell_size,
                                           (predict_boxes[:, :, :, :, 1] + tf.transpose(offset, (0, 2, 1, 3))) / self.cell_size,
                                           tf.square(predict_boxes[:, :, :, :, 2]),
                                           tf.square(predict_boxes[:, :, :, :, 3])])
            # shape為 [batch_size, 7, 7, 2, 4]
            predict_boxes_tran = tf.transpose(predict_boxes_tran, [1, 2, 3, 4, 0])

            iou_predict_truth = self.calc_iou(predict_boxes_tran, boxes)

            # calculate I tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
            object_mask = tf.reduce_max(iou_predict_truth, axis = 3, keep_dims = True)
            object_mask = tf.cast((iou_predict_truth >= object_mask), tf.float32) * response

            # calculate no_I tensor [CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
            noobject_mask = tf.ones_like(object_mask, dtype = tf.float32) - object_mask

            # 引數中加上平方根是對 w 和 h 進行開平方操作,原因在論文中有說明
            # #shape為(4, batch_size, 7, 7, 2)
            boxes_tran = tf.stack([boxes[:, :, :, :, 0] * self.cell_size - offset,
                                   boxes[:, :, :, :, 1] * self.cell_size - tf.transpose(offset, (0, 2, 1, 3)),
                                   tf.sqrt(boxes[:, :, :, :, 2]),
                                   tf.sqrt(boxes[:, :, :, :, 3])])
            boxes_tran = tf.transpose(boxes_tran, [1, 2, 3, 4, 0])

            # class_loss 分類損失
            class_delta = response * (predict_classes - classes)
            class_loss = tf.reduce_mean(tf.reduce_sum(tf.square(class_delta),
                                                      axis=[1, 2, 3]),
                                        name='class_loss') * self.class_scale
            # object_loss 有目標物體存在的損失
            object_delta = object_mask * (predict_scales - iou_predict_truth)
            object_loss = tf.reduce_mean(tf.reduce_sum(tf.square(object_delta),
                                                       axis = [1, 2, 3]), name = 'object_loss') * self.object_scale
            # 沒有目標物體時的損失
            noobject_delta = noobject_mask * predict_scales
            noobject_loss = tf.reduce_mean(tf.reduce_sum(tf.square(noobject_delta),
                                                         axis = [1, 2, 3]),
                                           name = 'noobject_loss') * self.noobject_scale

            # coord_loss 座標損失 #shape 為 (batch_size, 7, 7, 2, 1)
            coord_mask = tf.expand_dims(object_mask, 4)
            # shape 為(batch_size, 7, 7, 2, 4)
            boxes_delta = coord_mask * (predict_boxes - boxes_tran)
            coord_loss = tf.reduce_mean(tf.reduce_sum(tf.square(boxes_delta),
                                                      axis = [1, 2, 3, 4]),
                                        name = 'coord_loss') * self.coord_scale

            # 將所有損失放在一起
            tf.losses.add_loss(class_loss)
            tf.losses.add_loss(object_loss)
            tf.losses.add_loss(noobject_loss)
            tf.losses.add_loss(coord_loss)

            # 將每個損失新增到日誌記錄
            tf.summary.scalar('class_loss', class_loss)
            tf.summary.scalar('object_loss', object_loss)
            tf.summary.scalar('noobject_loss', noobject_loss)
            tf.summary.scalar('coord_loss', coord_loss)

            tf.summary.histogram('boxes_delta_x', boxes_delta[:, :, :, :, 0])
            tf.summary.histogram('boxes_delta_y', boxes_delta[:, :, :, :, 1])
            tf.summary.histogram('boxes_delta_w', boxes_delta[:, :, :, :, 2])
            tf.summary.histogram('boxes_delta_h', boxes_delta[:, :, :, :, 3])
            tf.summary.histogram('iou', iou_predict_truth)

  這部分內容的大致內容已經在註釋中進行標出。

4. train.py

  這部分程式碼主要實現的是對已經構建好的網路和損失函式利用資料進行訓練,在訓練過程中,對變數採用了指數平均數(exponential moving average (EMA))來提高整體的訓練效能。同時,為了獲得比較好的學習效能,對學習速率同向進行了指數衰減,使用了 exponential_decay 函式來實現這個功能。這個函式的具體計算公式如下所示:

decayed_learning_rate=learning_ratedecay_rate(globalstep/decaysteps)
  在訓練的同時,對我們的訓練模型(網路權重)進行儲存,這樣以後可以直接進行呼叫這些權重;同時,每隔一定的迭代次數便寫入 TensorBoard,這樣在最後可以觀察整體的情況。
  具體程式碼如下所示:
class Solver(object):

    def __init__(self, net, data):
        self.net = net
        self.data = data
        self.weights_file = cfg.WEIGHT_FILE #網路權重
        self.max_iter = cfg.MAX_ITER  #最大迭代數目
        self.initial_learning_rate = cfg.LEARNING_RATE
        self.decay_steps = cfg.DECAY_STEPS
        self.decay_rate = cfg.DECAY_RATE
        self.staircase = cfg.STAIRCASE
        self.summary_iter = cfg.SUMMARY_ITER
        self.save_iter = cfg.SAVE_ITER
        self.output_dir = os.path.join(
            cfg.OUTPUT_PATH, datetime.datetime.now().strftime('%Y_%m_%d_%H_%M'))
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)
        self.save_cfg()
        # tf.get_variable 和tf.Variable不同的一點是,前者擁有一個變數檢查機制,
        # 會檢測已經存在的變數是否設定為共享變數,如果已經存在的變數沒有設定為共享變數,
        # TensorFlow 執行到第二個擁有相同名字的變數的時候,就會報錯。
        self.variable_to_restore = tf.global_variables()
        self.restorer = tf.train.Saver(self.variable_to_restore, max_to_keep = None)
        self.saver = tf.train.Saver(self.variable_to_restore, max_to_keep = None)
        self.ckpt_file = os.path.join(self.output_dir, 'save.ckpt')
        self.summary_op = tf.summary.merge_all()
        self.writer = tf.summary.FileWriter(self.output_dir, flush_secs = 60)

        self.global_step = tf.get_variable(
            'global_step', [], initializer = tf.constant_initializer(0), trainable = False)
        # 產生一個指數衰減的學習速率
        self.learning_rate = tf.train.exponential_decay(
            self.initial_learning_rate, self.global_step, self.decay_steps,
            self.decay_rate, self.staircase, name = 'learning_rate')
        self.optimizer = tf.train.GradientDescentOptimizer(
            learning_rate = self.learning_rate).minimize(
            self.net.total_loss, global_step = self.global_step)
        self.ema = tf.train.ExponentialMovingAverage(decay = 0.9999)
        self.average_op = self.ema.apply(tf.trainable_variables())
        with tf.control_dependencies([self.optimizer]):
            self.train_op = tf.group(self.average_op)

        gpu_options = tf.GPUOptions()
        config = tf.ConfigProto(gpu_options=gpu_options)
        self.sess = tf.Session(config = config)
        self.sess.run(tf.global_variables_initializer())

        if self.weights_file is not None:
            print('Restoring weights from: '+ self.weights_file)
            self.restorer.restore(self.sess, self.weights_file)

        self.writer.add_graph(self.sess.graph)

    def train(self):
        train_timer = Timer()
        load_timer = Timer()

        for step in range(1, self.max_iter+1):

            load_timer.tic()
            images, labels = self.data.get()
            load_timer.toc()
            feec_dict = {self.net.images: images, self.net.labels: labels}

            if step % self.summary_iter == 0:
                if step % (self.summary_iter * 10) == 0:

                    train_timer.tic()
                    summary_str, loss, _ = self.sess.run(
                        [self.summary_op, self.net.total_loss, self.train_op],
                        feed_dict = feec_dict)
                    train_timer.toc()

                    log_str = ('{} Epoch: {}, Step: {}, Learning rate : {},'
                               'Loss: {:5.3f}\nSpeed: {:.3f}s/iter,'
                               ' Load: {:.3f}s/iter, Remain: {}').format(
                        datetime.datetime.now().strftime('%m/%d %H:%M:%S'),
                        self.data.epoch,
                        int(step),
                        round(self.learning_rate.eval(session = self.sess), 6),
                        loss,
                        train_timer.average_time,
                        load_timer.average_time,
                        train_timer.remain(step, self.max_iter))
                    print(log_str)

                else:
                    train_timer.tic()
                    summary_str, _ = self.sess.run(
                        [self.summary_op, self.train_op],
                        feed_dict = feec_dict)
                    train_timer.toc()

                self.writer.add_summary(summary_str, step)

            else:
                train_timer.tic()
                self.sess.run(self.train_op, feed_dict = feec_dict)
                train_timer.toc()

            if step % self.save_iter == 0:
                print('{} Saving checkpoint file to: {}'.format(
                    datetime.datetime.now().strftime('%m/%d %H:%M:%S'),
                    self.output_dir))
                self.saver.save(self.sess, self.ckpt_file,
                                global_step = self.global_step)

    def save_cfg(self):
        with open(os.path.join(self.output_dir, 'config.txt'), 'w') as f:
            cfg_dict = cfg.__dict__
            for key in sorted(cfg_dict.keys()):
                if key[0].isupper():
                    cfg_str = '{}: {}\n'.format(key, cfg_dict[key])
                    f.write(cfg_str)

  在訓練過程中主要大致的資料流向如下圖所示:
這裡寫圖片描述

5. test.py

   最後給出test 部分的原始碼,這部分需要使用我們下載好的 “YOLO_small.ckpt” 權重檔案,當然,也可以使用我們之前訓練好的權重檔案。
  這部分的主要內容就是利用訓練好的權重進行預測,得到預測輸出後利用 OpenCV 的相關函式進行畫框等操作。同時,還可以利用 OpenCV 進行視訊處理,使程式能夠實時地對視訊流進行檢測。因此,在閱讀本段程式之前,大家應該對 OpenCV 有一個大致的瞭解。
  具體程式碼如下所示:

class Detector(object):

    def __init__(self, net, weight_file):
        self.net = net
        self.weights_file = weight_file

        self.classes = cfg.CLASSES
        self.num_class = len(self.classes)
        self.image_size = cfg.IMAGE_SIZE
        self.cell_size = cfg.CELL_SIZE
        self.boxes_per_cell = cfg.BOXES_PER_CELL
        self.threshold = cfg.THRESHOLD
        self.iou_threshold = cfg.IOU_THRESHOLD
        self.boundary1 = self.cell_size * self.cell_size * self.num_class
        self.boundary2 = self.boundary1 + self.cell_size * self.cell_size * self.boxes_per_cell

        self.sess = tf.Session()
        self.sess.run(tf.global_variables_initializer())

        print('Restoring weights from: ' + self.weights_file)
        self.saver = tf.train.Saver()
        self.saver.restore(self.sess, self.weights_file)

    def draw_result(self, img, result):
        for i in range(len(result)):
            x = int(result[i][1])
            y = int(result[i][2])
            w = int(result[i][3] / 2)
            h = int(result[i][4] / 2)
            cv2.rectangle(img, (x - w, y - h), (x + w, y + h), (0, 255, 0), 2)
            cv2.rectangle(img, (x - w, y - h - 20),
                          (x + w, y - h), (125, 125, 125), -1)
            cv2.putText(img, result[i][0] + ' : %.2f' % result[i][5], (x - w + 5, y - h - 7),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1,    cv2.LINE_AA)

    def detect(self, img):
        img_h, img_w, _ = img.shape
        inputs = cv2.resize(img, (self.image_size, self.image_size))
        inputs = cv2.cvtColor(inputs, cv2.COLOR_BGR2RGB).astype(np.float32)
        inputs = (inputs / 255.0) * 
            
           

相關推薦

Deep LearningYOLO_v1TensorFlow 原始碼分析

  本文是對上一篇文章的繼續補充,在這裡首先說明,這個 TensorFlow 版本的原始碼 來自於 hizhangp/yolo_tensorflow,經過部分細節的調整執行在我的裝置上,我使用的環境是Win10+GPU+OpenCV 3.3 + Python3.

Deep learning使用tensorflow mnist 程式碼 記憶體崩潰

在tensorflow 官方給出的mnist程式碼中,測試模型準確率部分在視訊記憶體小的GPU上是無法執行的,會出現out of memory的問題。 原因是, test.images的數量太大(沒記錯應該是10000) test_acc=accuracy.eval(fe

Deep Learningtensorflow實現卷積神經網路(AlexNet)

一、實驗要求         1.使用卷積神經網路實現圖片分類,資料集為OxFlowers17; 二、實驗環境         Anaconda2-4.3.1(Python2.7),tensorflow-cpu。 三、實驗原理 3.1  資料讀取         已知資料集

ArrayList集合(JDK1.8) 集合框架JDK1.8原始碼分析之ArrayList(六)

簡述   List是繼承於Collection介面,除了Collection通用的方法以外,擴充套件了部分只屬於List的方法。   常用子類  ?ArrayList介紹 1.資料結構   其底層的資料結構是陣列,陣列元素型別為Object型別,即可以存放所

SpringMVC框架前端控制器原始碼分析

前端控制器原始碼分析 雖然前面講了一些springmvc的入門程式和配置檔案中對映器和介面卡的配置,但是我們作為程式設計人員,瞭解框架的部分原始碼還是有必要的,比如前端控制器,它是如何通過Servlet的web.xml配置檔案實現攔截並跳轉至DispatcherServle

Deep learning卷積神經網路CNN演算法原理

寫在前面在上一篇【Deep learning】卷積神經網路CNN結構中我們簡單地介紹了CNN的結構。接下來我們看看這種結構的CNN模型是怎麼執行的,包括CNN的前向傳播和反向傳播演算法。1.CNN前向傳播演算法(1)輸入層前向傳播到卷積層輸入層的前向傳播是CNN前向傳播演算法

Deep LearningGithub上關注最多的53個深度學習專案

Top Deep Learning Projects A list of popular github projects related to deep learning (ranked by stars). Last Update: 2016.08.09

Deep Learning深度學習影象標註工具VGG Image Annotator (VIA)使用教程

VGG Image Annotator (VIA)是一款開源的影象標註工具,由Visual Geometry Group開發。可以線上和離線使用,可標註矩形、圓、橢圓、多邊形、點和線。標註完成後,可以匯出為csv和json檔案格式。 一、新增圖片 點選via.ht

Android實戰----從Retrofit原始碼分析到Java網路程式設計以及HTTP權威指南想到的

一、簡介        接上一篇【Android實戰】----基於Retrofit實現多圖片/檔案、圖文上傳 中曾說非常想搞明白為什麼Retrofit那麼屌。最近也看了一些其原始碼分析的文章以及親自查看了原始碼,發現其對Java網路程式設計及HTTP權威指南有了一個很好的詮釋

輸出文件 Android MountService 原始碼分析

Android 儲存裝置管理框架 在android之VOLD程序啟動原始碼分析一文中介紹了儲存裝置的管控中心Vold程序,Vold屬於native後臺程序,通過netlink方式接收kernel的uevent訊息,並通過socket方式將uevent訊息傳送給MountService,同時實時接

kubernetes/k8s概念CNI macvlan原始碼分析

macvlan原理      在linux命令列執行 lsmod | grep macvlan 檢視當前核心是否載入了該driver;如果沒有檢視到,可以通過 modprobe macvlan 來載入  &n

JUCJDK1.8原始碼分析之CountDownLatch

轉自 https://www.cnblogs.com/leesf456/p/5406191.html 一、前言 分析完了CyclicBarrier後,下面分析CountDownLatch,CountDownLatch用於同步一個或多個任務,強制他們等待由其他任務執行的一組操作完成。C

Spring AMQP 原始碼分析 05

### 準備 ## 目標 瞭解 Spring AMQP Message Listener 如何處理異常 ## 前置知識 《Spring AMQP 原始碼分析 04 - MessageListener》 ## 相關資源 原始碼版本:Spring AMQP

Spring AMQP 原始碼分析 04

### 準備 ## 目標 瞭解 Spring AMQP 如何實現非同步訊息投遞(推模式) ## 前置知識 《RabbitMQ入門_05_多執行緒消費同一佇列》 ## 相關資源 原始碼版本:Spring AMQP 1.7.3.RELEASE ## 測試

Spring AMQP 原始碼分析 03

### 準備 ## 目標 瞭解 Spring AMQP 訊息轉化實現 ## 相關資源 ## 測試程式碼 gordon.study.rabbitmq.springamqp.JsonMessage.java   ### 分析 ## Mes

JUCJDK1.8原始碼分析之CountDownLatc

一、前言 分析完了CyclicBarrier後,下面分析CountDownLatch,CountDownLatch用於同步一個或多個任務,強制他們等待由其他任務執行的一組操作完成。CountDownLatch典型的用法是將一個程式分為n個互相獨立的可解決任務,

1.3.2Deep Learning翻譯系列Activation Functions 啟用函式

1、Sigmoid與雙曲正切 當建立神經網路,要做出的選擇之一是在隱藏層、輸出單元使用什麼啟用函式。 常見如的Sigmoid函式: σ

1.3.1 Deep Learning翻譯系列Neural Network Overview神經網路概覽

上圖是一個神經網路。神經網路中每個的節點對應於的兩個步驟: 首先計算 z = w

1.2.11 Deep Learning翻譯系列Explanation of Logistic Regression Cost Function 對數機率迴歸代價函式的說明

視訊地址 本視訊給出在對數機率迴歸使用這個成本函式的理由。 在之前的對數機率迴歸中,預測 y ^