1. 程式人生 > >第三十六節,目標檢測之yolo原始碼解析

第三十六節,目標檢測之yolo原始碼解析

在一個月前,我就已經介紹了yolo目標檢測的原理,後來也把tensorflow實現程式碼仔細看了一遍。但是由於這個暑假事情比較大,就一直擱淺了下來,趁今天有時間,就把原始碼解析一下。關於yolo目標檢測的原理請參考前面一篇文章:第三十五節,目標檢測之YOLO演算法詳解

在講解原始碼之前,我們需要做一些準備工作:

  1. yolo原始碼所在目錄下,建立一個目錄data,然後在data裡面建立一個pascal_voc目錄,用來儲存與VOC 2012資料集相關的資料,我們把下載好的資料集解壓到該目錄下,如下圖所示,其中VOCdevkit為資料集解壓得到的檔案,剩餘三個資料夾我們先不用理會,後面會詳細介紹
  2. 根據自己的需求修改配置檔案yolo/config.py。

  3. 執行train.py檔案,開始訓練。
  4. 執行test.py檔案,開始測試。

 二  yolo程式碼檔案結構

如果你按照上面我說的步驟配置好檔案之後,原始碼結構就會如下圖所示:

我們來粗略的介紹一下每個檔案的功能:

  • data資料夾,上面已經說過了,存放資料集以及訓練時生成的模型,快取檔案。
  • test資料夾,用來存放測試時用到的圖片。
  • utils資料夾,包含兩個檔案一個是pascal_voc.py,主要用來獲取訓練集圖片檔案,以及生成對應的標籤檔案,為yolo網路訓練做準備。另一個檔案是timer.py用來計時。
  • yolo資料夾,也包含兩個檔案,config.py包含yolo網路的配置引數,yolo_net.py檔案包含yolo網路的結構。
  • train.py檔案用來訓練yolo網路。
  • test.py檔案用來測試yolo網路。

三 config.py檔案講解

我們先從配置檔案說起,程式碼如下:

# -*- coding: utf-8 -*-
"""
Created on Tue Jun 12 12:08:15 2018

@author: lenovo
"""

'''
配置引數
'''

import os


#
# 資料集路徑,和模型檢查點檔案路徑
#

DATA_PATH 
= 'data' #所有資料所在的根目錄 PASCAL_PATH = os.path.join(DATA_PATH, 'pascal_voc') #VOC2012資料集所在的目錄 CACHE_PATH = os.path.join(PASCAL_PATH, 'cache') #儲存生成的資料集標籤緩衝檔案所在資料夾 OUTPUT_DIR = os.path.join(PASCAL_PATH, 'output') #儲存生成的網路模型和日誌檔案所在的資料夾 WEIGHTS_DIR = os.path.join(PASCAL_PATH, 'weights') #檢查點檔案所在的目錄 #WEIGHTS_FILE = None WEIGHTS_FILE = os.path.join(WEIGHTS_DIR, 'YOLO_small.ckpt') #VOC 2012資料集類別名 CLASSES = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'] #使用水平映象,擴大一倍資料集? FLIPPED = True ''' 網路模型引數 ''' #圖片大小 IMAGE_SIZE = 448 #單元格大小S 一共有CELL_SIZExCELL_SIZE個單元格 CELL_SIZE = 7 #每個單元格邊界框的個數B BOXES_PER_CELL = 2 #洩露修正線性啟用函式 係數 ALPHA = 0.1 #控制檯輸出資訊 DISP_CONSOLE = False #損失函式 的權重設定 OBJECT_SCALE = 1.0 #有目標時,置信度權重 NOOBJECT_SCALE = 1.0 #沒有目標時,置信度權重 CLASS_SCALE = 2.0 #類別權重 COORD_SCALE = 5.0 #邊界框權重 ''' 訓練引數設定 ''' GPU = '' #學習率 LEARNING_RATE = 0.0001 #退化學習率衰減步數 DECAY_STEPS = 30000 #衰減率 DECAY_RATE = 0.1 STAIRCASE = True #批量大小 BATCH_SIZE = 45 #最大迭代次數 MAX_ITER = 15000 #日誌檔案儲存間隔步 SUMMARY_ITER = 10 #模型儲存間隔步 SAVE_ITER = 500 ''' 測試時的相關引數 ''' #格子有目標的置信度閾值 THRESHOLD = 0.2 #非極大值抑制 IoU閾值 IOU_THRESHOLD = 0.5

各個引數我已經在上面註釋了,下面就不在重複了。下面我們來介紹yolo網路的構建。

四 yolo.py檔案講解

yolo網路的建立是通過yolo資料夾中的yolo_net.py檔案的程式碼實現的,yolo_net.py檔案定義了YOLONet類,該類包含了網路初始化(__init__()),建立網路(build_networks)和loss函式(loss_layer())等方法。

# -*- coding: utf-8 -*-
"""
Created on Tue Jun 12 12:08:15 2018

@author: lenovo
"""

'''
定義YOLO網路模型
https://blog.csdn.net/qq_34784753/article/details/78803423
https://blog.csdn.net/qq1483661204/article/details/79681926
'''

import numpy as np
import tensorflow as tf
import yolo.config as cfg
import logging
import sys

slim = tf.contrib.slim

#配置logging
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
                    level=logging.INFO,
                    stream=sys.stdout)

class YOLONet(object):

1、網路引數初始化

網路的所有初始化引數包含於__init__()方法中。

    def __init__(self, is_training=True):
        '''
        建構函式 
        利用 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
        
        args:
            is_training:訓練?
        '''
        #VOC 2012資料集類別名
        self.classes = cfg.CLASSES
        #類別個數C 20
        self.num_class = len(self.classes)
        #網路輸入影象大小448, 448 x 448
        self.image_size = cfg.IMAGE_SIZE
        #單元格大小S=7  將影象分為SxS的格子
        self.cell_size = cfg.CELL_SIZE
        #每個網格邊界框的個數B=2
        self.boxes_per_cell = cfg.BOXES_PER_CELL
        #網路輸出的大小 S*S*(B*5 + C) = 1470
        self.output_size = (self.cell_size * self.cell_size) *\
            (self.num_class + self.boxes_per_cell * 5)
        #圖片的縮放比例 64
        self.scale = 1.0 * self.image_size / self.cell_size        
        '''#將網路輸出分離為類別和置信度以及邊界框的大小,輸出維度為7*7*20 + 7*7*2 + 7*7*2*4=1470'''
        #7*7*20
        self.boundary1 = self.cell_size * self.cell_size * self.num_class
        #7*7*20+7*7*2
        self.boundary2 = self.boundary1 +\
            self.cell_size * self.cell_size * self.boxes_per_cell

        #代價函式 權重
        self.object_scale = cfg.OBJECT_SCALE  #1
        self.noobject_scale = cfg.NOOBJECT_SCALE  #1
        self.class_scale = cfg.CLASS_SCALE  #2.0
        self.coord_scale = cfg.COORD_SCALE  #5.0
        
        #學習率0.0001
        self.learning_rate = cfg.LEARNING_RATE
        #批大小 45
        self.batch_size = cfg.BATCH_SIZE
        #洩露修正線性啟用函式 係數0.1
        self.alpha = cfg.ALPHA

        #偏置 形狀[7,7,2]
        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))

        #輸入圖片佔位符 [NONE,image_size,image_size,3]
        self.images = tf.placeholder(
            tf.float32, [None, self.image_size, self.image_size, 3],
            name='images')
        #構建網路 獲取YOLO網路的輸出(不經過啟用函式的輸出)  形狀[None,1470]
        self.logits = self.build_network(
            self.images, num_outputs=self.output_size, alpha=self.alpha,
            is_training=is_training)

        if is_training:
            #設定標籤佔位符 [None,S,S,5+C]  即[None,7,7,25]
            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()
            #將損失以標量形式顯示,該變數命名為total_loss
            tf.summary.scalar('total_loss', self.total_loss)

2、構建網路

網路的建立是通過build_network()函式實現的,網路由卷積層,池化層和全連線層組成,網路的輸入維度是[None,448,448,3],輸出維度為[None,1470]。

 def build_network(self,
                      images,
                      num_outputs,
                      alpha,
                      keep_prob=0.5,
                      is_training=True,
                      scope='yolo'):
        '''
        構建YOLO網路
        
        args:
            images:輸入圖片佔位符 [None,image_size,image_size,3]  這裡是[None,448,448,3]
            num_outputs:標量,網路輸出節點數 1470
            alpha:洩露修正線性啟用函式 係數0.1
            keep_prob:棄權 保留率
            is_training:訓練?
            scope:名稱空間名
            
        return:
            返回網路最後一層,啟用函式處理之前的值  形狀[None,1470]
        '''
        #定義變數名稱空間
        with tf.variable_scope(scope):
            #定義共享引數  使用l2正則化
            with slim.arg_scope(
                [slim.conv2d, slim.fully_connected],
                activation_fn=leaky_relu(alpha),
                weights_regularizer=slim.l2_regularizer(0.0005),
                weights_initializer=tf.truncated_normal_initializer(0.0, 0.01)
            ):
                logging.info('image shape{0}'.format(images.shape))
                #pad_1 填充 454x454x3
                net = tf.pad(
                    images, np.array([[0, 0], [3, 3], [3, 3], [0, 0]]),
                    name='pad_1')
                logging.info('Layer pad_1  {0}'.format(net.shape))
                #卷積層conv_2 s=2    (n-f+1)/s向上取整    224x224x64
                net = slim.conv2d(
                    net, 64, 7, 2, padding='VALID', scope='conv_2')         
                logging.info('Layer conv_2 {0}'.format(net.shape))
                #池化層pool_3 112x112x64
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_3')
                logging.info('Layer pool_3 {0}'.format(net.shape))
                #卷積層conv_4、3x3x192 s=1  n/s向上取整   112x112x192
                net = slim.conv2d(net, 192, 3, scope='conv_4')
                logging.info('Layer conv_4 {0}'.format(net.shape))
                #池化層pool_5 56x56x192
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_5')
                logging.info('Layer pool_5 {0}'.format(net.shape))
                #卷積層conv_6、1x1x128 s=1  n/s向上取整  56x56x128
                net = slim.conv2d(net, 128, 1, scope='conv_6')
                logging.info('Layer conv_6 {0}'.format(net.shape))
                #卷積層conv_7、3x3x256 s=1  n/s向上取整 56x56x256
                net = slim.conv2d(net, 256, 3, scope='conv_7')
                logging.info('Layer conv_7 {0}'.format(net.shape))
                #卷積層conv_8、1x1x256 s=1  n/s向上取整 56x56x256
                net = slim.conv2d(net, 256, 1, scope='conv_8')
                logging.info('Layer conv_8 {0}'.format(net.shape))
                #卷積層conv_9、3x3x512 s=1  n/s向上取整 56x56x512
                net = slim.conv2d(net, 512, 3, scope='conv_9')
                logging.info('Layer conv_9 {0}'.format(net.shape))
                #池化層pool_10 28x28x512
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_10')
                logging.info('Layer pool_10 {0}'.format(net.shape))
                #卷積層conv_11、1x1x256 s=1  n/s向上取整 28x28x256
                net = slim.conv2d(net, 256, 1, scope='conv_11')
                logging.info('Layer conv_11 {0}'.format(net.shape))
                #卷積層conv_12、3x3x512 s=1  n/s向上取整 28x28x512
                net = slim.conv2d(net, 512, 3, scope='conv_12')
                logging.info('Layer conv_12 {0}'.format(net.shape))
                #卷積層conv_13、1x1x256 s=1  n/s向上取整 28x28x256
                net = slim.conv2d(net, 256, 1, scope='conv_13')
                logging.info('Layer conv_13 {0}'.format(net.shape))
                #卷積層conv_14、3x3x512 s=1  n/s向上取整 28x28x512
                net = slim.conv2d(net, 512, 3, scope='conv_14')
                logging.info('Layer conv_14 {0}'.format(net.shape))
                #卷積層conv_15、1x1x256 s=1  n/s向上取整 28x28x256
                net = slim.conv2d(net, 256, 1, scope='conv_15')
                logging.info('Layer conv_15 {0}'.format(net.shape))                
                #卷積層conv_16、3x3x512 s=1  n/s向上取整 28x28x512
                net = slim.conv2d(net, 512, 3, scope='conv_16')
                logging.info('Layer conv_16 {0}'.format(net.shape))
                #卷積層conv_17、1x1x256 s=1  n/s向上取整 28x28x256
                net = slim.conv2d(net, 256, 1, scope='conv_17')
                logging.info('Layer conv_17 {0}'.format(net.shape))                
                #卷積層conv_18、3x3x512 s=1  n/s向上取整 28x28x512
                net = slim.conv2d(net, 512, 3, scope='conv_18')
                logging.info('Layer conv_18 {0}'.format(net.shape))
                #卷積層conv_19、1x1x512 s=1  n/s向上取整 28x28x512
                net = slim.conv2d(net, 512, 1, scope='conv_19')
                logging.info('Layer conv_19 {0}'.format(net.shape))
                #卷積層conv_20、3x3x1024 s=1  n/s向上取整 28x28x1024
                net = slim.conv2d(net, 1024, 3, scope='conv_20')
                logging.info('Layer conv_20 {0}'.format(net.shape))
                #池化層pool_21 14x14x1024
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_21')
                logging.info('Layer pool_21 {0}'.format(net.shape))
                #卷積層conv_22、1x1x512 s=1  n/s向上取整 14x14x512
                net = slim.conv2d(net, 512, 1, scope='conv_22')
                logging.info('Layer conv_22 {0}'.format(net.shape))
                #卷積層conv_23、3x3x1024 s=1  n/s向上取整 14x14x1024
                net = slim.conv2d(net, 1024, 3, scope='conv_23')
                logging.info('Layer conv_23 {0}'.format(net.shape))
                #卷積層conv_24、1x1x512 s=1  n/s向上取整 14x14x512
                net = slim.conv2d(net, 512, 1, scope='conv_24')
                logging.info('Layer conv_24 {0}'.format(net.shape))
                #卷積層conv_25、3x3x1024 s=1  n/s向上取整 14x14x1024
                net = slim.conv2d(net, 1024, 3, scope='conv_25')
                logging.info('Layer conv_25 {0}'.format(net.shape))
                #卷積層conv_26、3x3x1024 s=1  n/s向上取整 14x14x1024
                net = slim.conv2d(net, 1024, 3, scope='conv_26')
                logging.info('Layer conv_26 {0}'.format(net.shape))
                #pad_27 填充 16x16x2014
                net = tf.pad(
                    net, np.array([[0, 0], [1, 1], [1, 1], [0, 0]]),
                    name='pad_27')
                logging.info('Layer pad_27 {0}'.format(net.shape))
                #卷積層conv_28、3x3x1024 s=2  (n-f+1)/s向上取整 7x7x1024
                net = slim.conv2d(
                    net, 1024, 3, 2, padding='VALID', scope='conv_28')
                logging.info('Layer conv_28 {0}'.format(net.shape))
                #卷積層conv_29、3x3x1024 s=1  n/s向上取整 7x7x1024
                net = slim.conv2d(net, 1024, 3, scope='conv_29')
                logging.info('Layer conv_29 {0}'.format(net.shape))
                #卷積層conv_30、3x3x1024 s=1  n/s向上取整 7x7x1024
                net = slim.conv2d(net, 1024, 3, scope='conv_30')
                logging.info('Layer conv_30 {0}'.format(net.shape))
                #trans_31 轉置[None,1024,7,7]
                net = tf.transpose(net, [0, 3, 1, 2], name='trans_31')
                logging.info('Layer trans_31 {0}'.format(net.shape))
                #flat_32 展開 50176
                net = slim.flatten(net, scope='flat_32')
                logging.info('Layer flat_32 {0}'.format(net.shape))
                #全連線層fc_33  512
                net = slim.fully_connected(net, 512, scope='fc_33')
                logging.info('Layer fc_33 {0}'.format(net.shape))
                #全連線層fc_34  4096
                net = slim.fully_connected(net, 4096, scope='fc_34')
                logging.info('Layer fc_34 {0}'.format(net.shape))
                #棄權層dropout_35 4096
                net = slim.dropout(
                    net, keep_prob=keep_prob, is_training=is_training,
                    scope='dropout_35')
                logging.info('Layer dropout_35 {0}'.format(net.shape))
                #全連線層fc_36 1470
                net = slim.fully_connected(
                    net, num_outputs, activation_fn=None, scope='fc_36')
                logging.info('Layer fc_36 {0}'.format(net.shape))
        return net

3、代價函式

代價函式是通過loss_layer()實現的,在程式碼中,我們優化以下多部分損失函式:

def loss_layer(self, predicts, labels, scope='loss_layer'):
        '''
        計算預測和標籤之間的損失函式
        
        args:
            predicts:Yolo網路的輸出 形狀[None,1470]  
                      0:7*7*20:表示預測類別   
                      7*7*20:7*7*20 + 7*7*2:表示預測置信度,即預測的邊界框與實際邊界框之間的IOU
                      7*7*20 + 7*7*2:1470:預測邊界框    目標中心是相對於當前格子的,寬度和高度的開根號是相對當前整張影象的(歸一化的)
            labels:標籤值 形狀[None,7,7,25]
                      0:1:置信度,表示這個地方是否有目標
                      1:5:目標邊界框  目標中心,寬度和高度(沒有歸一化)
                      5:25:目標的類別
        '''
        with tf.variable_scope(scope):
            '''#將網路輸出分離為類別和置信度以及邊界框的大小,輸出維度為7*7*20 + 7*7*2 + 7*7*2*4=1470'''
            #預測每個格子目標的類別 形狀[45,7,7,20]
            predict_classes = tf.reshape(
                predicts[:, :self.boundary1],
                [self.batch_size, self.cell_size, self.cell_size, self.num_class])
            #預測每個格子中兩個邊界框的置信度 形狀[45,7,7,2]
            predict_scales = tf.reshape(
                predicts[:, self.boundary1:self.boundary2],
                [self.batch_size, self.cell_size, self.cell_size, self.boxes_per_cell])
            #預測每個格子中的兩個邊界框,(x,y)表示邊界框相對於格子邊界框的中心 w,h的開根號相對於整個圖片  形狀[45,7,7,2,4]
            predict_boxes = tf.reshape(
                predicts[:, self.boundary2:],
                [self.batch_size, self.cell_size, self.cell_size, self.boxes_per_cell, 4])

            #標籤的置信度,表示這個地方是否有框 形狀[45,7,7,1]
            response = tf.reshape(
                labels[..., 0],
                [self.batch_size, self.cell_size, self.cell_size, 1])
            #標籤的邊界框 (x,y)表示邊界框相對於整個圖片的中心 形狀[45,7,7,1,4]
            boxes = tf.reshape(
                labels[..., 1:5],
                [self.batch_size, self.cell_size, self.cell_size, 1, 4])
            #標籤的邊界框 歸一化後 張量沿著axis=3重複兩邊,擴充後[45,7,7,2,4]
            boxes = tf.tile(
                boxes, [1, 1, 1, self.boxes_per_cell, 1]) / self.image_size
            classes = labels[..., 5:]

            '''
            predict_boxes_tran:offset變數用於把預測邊界框predict_boxes中的座標中心(x,y)由相對當前格子轉換為相對當前整個圖片
            
            offset,這個是構造的[7,7,2]矩陣,每一行都是[7,2]的矩陣,值為[[0,0],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6]]
            這個變數是為了將每個cell的座標對齊,後一個框比前一個框要多加1
            比如我們預測了cell_size的每個中心點座標,那麼我們這個中心點落在第幾個cell_size
            就對應座標要加幾,這個用法比較巧妙,構造了這樣一個數組,讓他們對應位置相加
            '''
            #offset shape為[1,7,7,2]  如果忽略axis=0,則每一行都是  [[0,0],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6]]
            offset = tf.reshape(
                tf.constant(self.offset, dtype=tf.float32),
                [1, self.cell_size, self.cell_size, self.boxes_per_cell])
            #shape為[45,7,7,2]
            offset = tf.tile(offset, [self.batch_size, 1, 1, 1])
            #shape為[45,7,7,2]  如果忽略axis=0 第i行為[[i,i],[i,i],[i,i],[i,i],[i,i],[i,i],[i,i]]
            offset_tran = tf.transpose(offset, (0, 2, 1, 3))
            #shape為[45,7,7,2,4]  計算每個格子中的預測邊界框座標(x,y)相對於整個圖片的位置  而不是相對當前格子
            #假設當前格子為(3,3),當前格子的預測邊界框為(x0,y0),則計算座標(x,y) = ((x0,y0)+(3,3))/7
            predict_boxes_tran = tf.stack(
                [(predict_boxes[..., 0] + offset) / self.cell_size,         #x
                 (predict_boxes[..., 1] + offset_tran) / self.cell_size,    #y
                 tf.square(predict_boxes[..., 2]),                          #width
                 tf.square(predict_boxes[..., 3])], axis=-1)                #height

            #計算每個格子預測邊界框與真實邊界框之間的IOU  [45,7,7,2]
            iou_predict_truth = self.calc_iou(predict_boxes_tran, boxes)

            # calculate I tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]  
            #這個是求論文中的1ijobj引數,[45,7,7,2]     1ijobj:表示網格單元i的第j個編輯框預測器’負責‘該預測
            #先計算每個框交併比最大的那個,因為我們知道,YOLO每個格子預測兩個邊界框,一個類別。在訓練時,每個目標只需要
            #一個預測器來負責,我們指定一個預測器"負責",根據哪個預測器與真實值之間具有當前最高的IOU來預測目標。
            #所以object_mask就表示每個格子中的哪個邊界框負責該格子中目標預測?哪個邊界框取值為1,哪個邊界框就負責目標預測
            #當格子中的確有目標時,取值為[1,1],[1,0],[0,1]
            #比如某一個格子的值為[1,0],表示第一個邊界框負責該格子目標的預測  [0,1]:表示第二個邊界框負責該格子目標的預測
            #當格子沒有目標時,取值為[0,0]
            object_mask = tf.reduce_max(iou_predict_truth, 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.onr_like,使得全部為1,再減去有目標的,也就是有目標的對應座標為1,這樣一減,就變為沒有的了。[45,7,7,2]
            noobject_mask = tf.ones_like(
                object_mask, dtype=tf.float32) - object_mask

            # boxes_tran 這個就是把之前的座標換回來(相對整個影象->相對當前格子),長和寬開方(原因在論文中有說明),後面求loss就方便。 shape為(4, 45, 7, 7, 2)
            boxes_tran = tf.stack(
                [boxes[..., 0] * self.cell_size - offset,
                 boxes[..., 1] * self.cell_size - offset_tran,
                 tf.sqrt(boxes[..., 2]),
                 tf.sqrt(boxes[..., 3])], axis=-1)

            #class_loss 分類損失,如果目標出現在網格中 response為1,否則response為0  原文代價函式公式第5項
            #該項表名當格子中有目標時,預測的類別越接近實際類別,代價值越小  原文代價函式公式第5項
            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 有目標物體存在的置信度預測損失   原文代價函式公式第3項
            #該項表名當格子中有目標時,負責該目標預測的邊界框的置信度越越接近預測的邊界框與實際邊界框之間的IOU時,代價值越小
            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_loss  沒有目標物體存在的置信度的損失(此時iou_predict_truth為0)  原文代價函式公式第4項
            #該項表名當格子中沒有目標時,預測的兩個邊界框的置信度越接近0,代價值越小
            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]  原文代價函式公式1,2項
            #該項表名當格子中有目標時,預測的邊界框越接近實際邊界框,代價值越小
            coord_mask = tf.expand_dims(object_mask, 4)                 #1ij
            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)

在計算代價函式時,我們需要計算預測的box與實際邊界框之間的IOU。其程式碼如下:

   def calc_iou(self, boxes1, boxes2, scope='iou'):
        """calculate ious
        這個函式的主要作用是計算兩個 bounding box 之間的 IoU。輸入是兩個 5 維的bounding box,輸出的兩個 bounding Box 的IoU 
        
        Args:          
          boxes1: 5-D tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL, 4]  ====> (x_center, y_center, w, h)
          boxes2: 5-D tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL, 4] ===> (x_center, y_center, w, h)
          注意這裡的引數x_center, y_center, w, h都是歸一到[0,1]之間的,分別表示預測邊界框的中心相對整張圖片的座標,寬和高
        Return:
          iou: 4-D tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
        """
        with tf.variable_scope(scope):
            # transform (x_center, y_center, w, h) to (x1, y1, x2, y2)            
            #把以前的中心點座標和長和寬轉換成了左上角和右下角的兩個點的座標
            boxes1_t = tf.stack([boxes1[..., 0] - boxes1[..., 2] / 2.0,   #左上角x
                                 boxes1[..., 1] - boxes1[..., 3] / 2.0,   #左上角y
                                 boxes1[..., 0] + boxes1[..., 2] / 2.0,   #右下角x
                                 boxes1[..., 1] + boxes1[..., 3] / 2.0],  #右下角y
                                axis=-1)

            boxes2_t = 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],
                                axis=-1)

            # calculate the left up point & right down point
            #lu和rd就是分別求兩個框相交的矩形的左上角的座標和右下角的座標,因為對於左上角,
            #選擇的是x和y較大的,而右下角是選擇較小的,可以想想兩個矩形框相交是不是這中情況
            lu = tf.maximum(boxes1_t[..., :2], boxes2_t[..., :2])       #兩個框相交的矩形的左上角(x1,y1)
            rd = tf.minimum(boxes1_t[..., 2:], boxes2_t[..., 2:])       #兩個框相交的矩形的右下角(x2,y2)

            # intersection 這個就是求相交矩形的長和寬,所以有rd-ru,相當於x1-x2和y1-y2,
            #之所以外面還要加一個tf.maximum是因為刪除那些不合理的框,比如兩個框沒交集,
            #就會出現左上角座標比右下角還大。
            intersection = tf.maximum(0.0, rd - lu)
            #inter_square這個就是求面積了,就是長乘以寬。
            inter_square = intersection[..., 0] * intersection[..., 1]

            # calculate the boxs1 square and boxs2 square 
            #square1和square2這個就是求面積了,因為之前是中心點座標和長和寬,所以這裡直接用長和寬
            square1 = boxes1[..., 2] * boxes1[..., 3]
            square2 = boxes2[..., 2] * boxes2[..., 3
            
           

相關推薦

目標檢測yolo原始碼解析

在一個月前,我就已經介紹了yolo目標檢測的原理,後來也把tensorflow實現程式碼仔細看了一遍。但是由於這個暑假事情比較大,就一直擱淺了下來,趁今天有時間,就把原始碼解析一下。關於yolo目標檢測的原理請參考前面一篇文章:第三十五節,目標檢測之YOLO演算法詳解 在講解原始碼之前,我們需要做一些準備工作

目標檢測YOLO演算法詳解

YOLO的全拼是You Only Look Once,顧名思義就是隻看一次,把目標區域預測和目標類別預測合二為一,作者將目標檢測任務看作目標區域預測和類別預測的迴歸問題。該方法採用單個神經網路直接預測物品邊界和類別概率,實現端到端的物品檢測。因此識別效能有了很大提升,達到每秒45幀,而在快速YOLO(Fas

、人臉檢測MTCNN和人臉識別Facenet(附原始碼)

在說到人臉檢測我們首先會想到利用Harr特徵提取和Adaboost分類器進行人臉檢測(有興趣的可以去一看這篇部落格第九節、人臉檢測之Haar分類器),其檢測效果也是不錯的,但是目前人臉檢測的應用場景逐漸從室內演變到室外,從單一限定場景發展到廣場、車站、地鐵口等場景,人臉檢測面臨的要求越來越高,比如:人臉尺度多

百一Django框架中間件

auth ack 路徑 div options pre con csrf col 第三百一十六節,Django框架,中間件 django 中的中間件(middleware),在django中,中間件其實就是一個類,在請求到來和結束後,django會根據自己的規則在合適的時機

百七Django+Xadmin打造上線標準的在線教育平臺—創建用戶操作app在models.py文件生成5張表用戶咨詢表、課程評論表、用戶收藏表、用戶消息表、用戶學習表

十六 _id 收藏 創建用戶 在線教育 名稱 image images sage 第三百七十六節,Django+Xadmin打造上線標準的在線教育平臺—創建用戶操作app,在models.py文件生成5張表,用戶咨詢表、課程評論表、用戶收藏表、用戶消息表、用戶學習表 創

第二目標定位、特征點檢測依據目標檢測

回顧 邏輯 預測 簡單 AS 其中 輸入 操作 功能 一 目標定位 對象檢測,它是計算機視覺領域中一個新興的應用方向,相比前兩年,它的性能越來越好。在構建對象檢測之前,我們先了解一下對象定位,首先我們看看它的定義。 圖片分類任務我們已經熟悉了,就是算法遍歷圖片,判斷其中的

使用函式封裝庫tf.contrib.layers

這一節,介紹TensorFlow中的一個封裝好的高階庫,裡面有前面講過的很多函式的高階封裝,使用這個高階庫來開發程式將會提高效率。 我們改寫第十三節的程式,卷積函式我們使用tf.contrib.layers.conv2d(),池化函式使用tf.contrib.layers.max_pool2d(

第二百四Bootstrap彈出框和警告框插件

popover 事件 png div 數字 ott hid strong selector Bootstrap彈出框和警告框插件 學習要點:   1.彈出框   2.警告框 本節課我們主要學習一下 Bootstrap 中的彈出框和警告框插件。 一.彈出框 彈

第二百七MySQL數據庫常用命令

query 相關數 tinc man 配置 服務器性能 lai 用戶管理 常用命令 MySQL數據庫常用命令 1、顯示數據庫 SHOW DATABASES;顯示數據庫 SHOW DATABASES; mysql - 用戶權限相關數據sys - 數據庫的相關配置存放

python學習

div 完成 聯系 info 所有 沒有 進程pid star 標誌位 event模塊event.wait() 等待相當於標誌位為False。()內可以傳參數數字,為幾秒。event.set()給另一個線程傳標誌位True。 隊列 queue和列表類似,但是函數內置了互斥

AGG課 gsv_text_outline 渲染環繞的字符

text gsv outline agg::rendering_buffer &rbuf = rbuf_window(); agg::pixfmt_bgr24 pixf(rbuf); typedef agg::renderer_base<agg::pixfmt_bgr24>

mysql 篇文章~mysql慢日誌方案解讀1

日誌 iges add 代碼 bytes 周期 缺省 port 同學 一 慢日誌的相關參數 long_query_time : 設定慢查詢的閥值,超出次設定值的SQL即被記錄到慢查詢日誌,缺省值為1s log_slow_queries :1/0

學習筆記

siptables filter表小案例 需要把80端口 和21端口 22端口放行,22端口指定一個ip段,只有這個ip段的訪問才可以,其他的都拒絕。用一個shell腳本去實現。 ipt定義了一個變量,為了後面去加載它。正常執行一個命令 寫一個絕對的命令,不會因為環境變量導致命令無法執行。 首先清空之前

學習筆記

作業iptables規則備份和恢復。 service iptables save 會把規則保存到 /etc/sysconfig/iptables配置文件中,但是有時候不想保存這個位置。 可以用命令 iptables-sabe > 到你想保存的位置。 恢復備份的規則的話 是iptables-re

學習筆記

作業rsyc通過服務同步 還可以通過服務的方式同步。 要通過服務方式同步 要先配置文件 /etc/rsyncd.comf -port=873 指定一個端口, 如果不指定 就默認873 log file=/var/log/rsync.log 指定它的日誌文件。pid file=/var/run/r

第二滑動窗口和 Bounding Box 預測

方框 you 但是 幸運 ont soft 添加 一點 技術分享 上節,我們學習了如何通過卷積網絡實現滑動窗口對象檢測算法,但效率很低。這節我們講講如何在卷積層上應用這個算法。 為了構建滑動窗口的卷積應用,首先要知道如何把神經網絡的全連接層轉化成卷積層。我們先講解這部分內容

4.6 基於Spring-Boot的Mysql+jpa的增刪改查學習記錄 > 我的程式猿路:

    1.專案結構       -JDK  1.8       -SpringBoot  2.0.6     &nbs

天 守護程序 互斥鎖 程序間通訊

一.昨日回顧 1. 程序 相關概念   併發 看起來像是同時執行的中 本質是不停切換執行 多個程序隨機執行   並行 同一時刻 多個程序 同時進行 只有多喝處理器才有真正的並行   序列 一個一個 依次排隊執行   阻塞 遇到了I/O操作 看起來就是程式碼卡住了   非阻塞 不會卡住程式碼的執行

2018.11.08python學習

一:守護程序 # 守護程序:顧名思義,就是一個程序守護著另一個程序,指的是兩個程序之間的關係。 # 特點:就是守護程序在被守護程序死亡時,也會跟著被守護程序的死亡而死亡。 使用方法: from multiprocessing import Process import time def task():

C++筆記 課 經典問題解析---狄泰學院

如果在閱讀過程中發現有錯誤,望評論指正,希望大家一起學習,一起進步。 學習C++編譯環境:Linux 第三十六課 經典問題解析三 1.關於賦值的疑問 什麼時候需要過載賦值操作符?編譯器是否提供預設的賦值操作? 編譯器為每個類預設過載了賦值操作符 預設的賦值操作符僅完成淺拷貝