1. 程式人生 > >【目標檢測】閱讀YOLOv1 論文的一些feelings

【目標檢測】閱讀YOLOv1 論文的一些feelings

   YOLOv1 是這周看的跟目標檢測相關的第5篇paper,在瞭解了rcnn系列paper的work原理之後,YOLO還是有很大不同的,rcnn系列的論文要麼通過ss方法要麼通過RPN 產生bounding box,對每個產生的bounding box進行分類檢測,而YOLO則從全域性的角度出發,將ob任務當作一個regression任務,一次輸入圖片直接產生圖片中bounding box的 coordinate 和類別概率。即 you only look once! YOLO最大的特點是解決了OB任務中速度慢的問題 基礎YOLO版本GPU可以達到45fps 快速的可達155ps!

  論文下載:http://arxiv.org/abs/1506.02640 
  程式碼下載:https://github.com/pjreddie/darknet

目錄

1.YOLO的網路結構

2.訓練

output

代價

3.預測

4.YOLO實驗

5.YOLO缺點

6.常見問題

7.YOLO原始碼


1.YOLO的網路結構

YOLO的核心思想即將原圖片分成S*S個cell,如果某個object落在這個cell中,這個cell就負責預測這個object的類別。(在此做了一個粗略估算,即一個cell中只有一個object,這樣做近似顯然影響YOLO對於密集小物體的召回率,但勝在速度很快。)。

在此強調一個坑:這裡所指的物體落入某個cell,是指物體的中心點落入這個cell,比如下圖,狗的中心點落在這個cell中,那麼這個cell就負責預測狗。

這裡寫圖片描述

  每一個cell 將會預測B個bounding box,並對每一個bounding box預算一個置信度 confidence,同時預測這個cell中物體的類別。所謂置信度包含了兩個方面的考慮,bounding box中是否落入物體的可能性以及這個bounding box的準確度,前者記為Pr(object),當該邊界框是背景時(即不包含目標),此時Pr(object)=0。而當該邊界框包含目標時,Pr(object)=1。邊界框的準確度可以用預測框與實際框(ground truth)的IOU(intersection over union,交併比)來表徵,記為IOUtruthpred,因此置信度可以定義為Pr(object)∗IOUtruthpred。對於B個bounding box,預測(x,y,w,h),x,y代表中心點距離這個cell的偏移,w,h代表bounding box 相對於整張圖片的W,H所佔的比例還有分類問題,對於每一個單元格其還要給出預測出C
個類別概率值,其表徵的是每一個cell中產生的bounding box負責的物體的類別概率。注意這裡的類別概率是一個條件概率,是在該cell中存在object的時候,即cell中每一個bounding box的置信度不為0的情況下,即Pr(classi|object)

。這裡注意的一點是不管一個cell最後產出多少個bounding box,最終只輸出一個概率,取置信度最高的那個bounding box來負責對這個object的類別進行預測,這是YOLO的一個缺點,即可能存在漏檢查的情況。同時,我們可以計算出各個邊界框類別置信度,Pr(classi|object)∗Pr(object)∗IOUtruthpred=Pr(classi)∗IOUtruthpred。邊界框類別置信度表徵的是該邊界框中目標屬於各個類別的可能性大小以及邊界框匹配目標的好壞。後面會說,一般會根據類別置信度來過濾網路的預測框。
  總結一下,每個單元格需要預測(B∗5+C)個值。如果將輸入圖片劃分為S×S網格,那麼最終預測值為S×S×(B∗5+C)
大小的張量。整個模型的預測值結構如下圖所示。對於PASCAL VOC資料,其共有20個類別,如果使用S=7,B=2
,那麼最終的預測結果就是7×7×30大小的張量。在下面的網路結構中我們會詳細講述每個單元格的預測值的分佈位置。

舉例說明: 在PASCAL VOC中,影象輸入為448x448,取S=7,B=2,一共有20個類別(C=20)。則輸出就是7x7x30的一個tensor。 
整個網路結構如下圖所示: 

輸入為448*448彩色影象,比RCNN系列增大一倍。其中包含24或9個卷積層(fast-YOLO),基本遵循googLeNet2的設計,降取樣為2^4,輸出7*7*1024的特徵圖。兩個全連層從特徵圖迴歸出每個網格的30個預測。

2.訓練

output

如果目標的中心在一個網格內部,稱為“目標在網格內”。

真實資料記為:

\widehat{x_{i}},\widehat{y_{i}},:網格i中目標的左上角位置
\widehat{w_{i}},\widehat{h_{i}},:網格i中目標的尺寸
\widehat{C_{ij}},:網格i的第j個bounding box和目標的IOU的乘積
\widehat{p_{i}},:網格i的1-hot編碼分類概率

對於每一個網格i,輸出B*4個定位結果(目標在哪裡):

xij,yij:bounding box左上角位置
wij,hij:bounding box尺寸
B個檢測結果(是目標的可能性):

Cij:bounding box屬於目標的概率
以及C個分類結果(目標是哪一類):

pi:長度為類別數的向量

代價

這裡寫圖片描述

代價包含以下三個方面

【定位代價】考察每個含有目標的網格中置信度最高的的bounding box:應該與目標位置近似。

 

【檢測代價】考察每個網格中置信度最高的預測結果:如果網格含有目標,應等於目標與bounding box的IOU;如果網格不含目標,應為0。 

【分類代價】考察每個含有目標的網格 

這裡詳細講一下loss function。在loss function中,前面兩行表示localization error(即座標誤差),第一行是box中心座標(x,y)的預測,第二行為寬和高的預測。這裡注意用寬和高的開根號代替原來的寬和高,這樣做主要是因為相同的寬和高誤差對於小的目標精度影響比大的目標要大。舉個例子,原來w=10,h=20,預測出來w=8,h=22,跟原來w=3,h=5,預測出來w1,h=7相比,其實前者的誤差要比後者小,但是如果不加開根號,那麼損失都是一樣:4+4=8,但是加上根號後,變成0.15和0.7。 
第三、四行表示bounding box的confidence損失,就像前面所說的,分成grid cell包含與不包含object兩種情況。這裡注意下因為每個grid cell包含兩個bounding box,所以只有當ground truth 和該網格中的某個bounding box的IOU值最大的時候,才計算這項。 第五行表示預測類別的誤差,注意前面的係數只有在grid cell包含object的時候才為1。

所以具體實現的時候是什麼樣的過程呢?

訓練的時候:輸入N個影象,每個影象包含M個objec,每個object包含4個座標(x,y,w,h)和1個label。然後通過網路得到7*7*30大小的三維矩陣。每個1*30的向量前5個元素表示第一個bounding box的4個座標和1個confidence,第6到10元素表示第二個bounding box的4個座標和1個confidence。最後20個表示這個grid cell所屬類別。注意這30個都是預測的結果。然後就可以計算損失函式的第一、二 、五行。至於第二三行,confidence可以根據ground truth和預測的bounding box計算出的IOU和是否有object的0,1值相乘得到。真實的confidence是0或1值,即有object則為1,沒有object則為0。 這樣就能計算出loss function的值了。
 

3.預測

在test的時候,每個網格預測的class資訊和bounding box預測的confidence資訊相乘,就得到每個bounding box的class-specific confidence score: 

等式左邊第一項就是每個網格預測的類別資訊,第二三項就是每個bounding box預測的confidence。這個乘積即encode了預測的box屬於某一類的概率,也有該box準確度的資訊。得到每個box的class-specific confidence score以後,設定閾值,濾掉得分低的boxes,對保留的boxes進行NMS處理,就得到最終的檢測結果。

測試的時候:輸入一張影象,跑到網路的末端得到7*7*30的三維矩陣,這裡雖然沒有計算IOU,但是由訓練好的權重已經直接計算出了bounding box的confidence。然後再跟預測的類別概率相乘就得到每個bounding box屬於哪一類的概率。


4.YOLO實驗

作為實時檢測器,YOLO在PASCAL VOC上取得了不錯的效能 
這裡寫圖片描述

將YOLO與Fast-RCNN的分類結果進行比較,得到兩個十分有啟發性的結論: 
這裡寫圖片描述

錯誤 解釋 比較 原因
虛警(紅) 將背景誤判為目標 YOLO更佳 YOLO在訓練時,將整張影象送入網路,上下文資訊更豐富
定位(藍) 類別正確,位置偏差 YOLO更差 YOLO的候選框受網格限制

5.YOLO缺點

YOLO對相互靠的很近的物體,還有很小的群體 檢測效果不好,這是因為一個網格中只預測了兩個框,並且只屬於一類。

對測試影象中,同一類物體出現的新的不常見的長寬比和其他情況是。泛化能力偏弱。

由於損失函式的問題,定位誤差是影響檢測效果的主要原因。尤其是大小物體的處理上,還有待加強。
 

6.常見問題

1.為什麼每個cell中只取一個置信度最高的bounding box?

原文說的意思是指定一個預測器“負責”根據哪個預測與真實值之間具有當前最高的IOU來預測目標。會使每個預測器可以更好地預測特定大小,方向角,或目標的類別,從而改善整體召回率。

2.YOLO準確度下降原因?

每個網格預設1類 ,多次下采樣等原因

3.一個grid cell中是否有object怎麼界定? 
首先要明白grid cell的含義,以文中7*7為例,這個size其實就是對輸入影象(假設是224*224)不斷提取特徵然後sample得到的(縮小了32倍),然後就是把輸入影象劃分成7*7個grid cell,這樣輸入影象中的32個畫素點就對應一個grid cell。迴歸正題,那麼我們有每個object的標註資訊,也就是知道每個object的中心點座標在輸入影象的哪個位置,那麼不就相當於知道了每個object的中心點座標屬於哪個grid cell了嗎,而只要object的中心點座標落在哪個grid cell中,這個object就由哪個grid cell負責預測,也就是該grid cell包含這個object。另外由於一個grid cell會預測兩個bounding box,實際上只有一個bounding box是用來預測屬於該grid cell的object的,因為這兩個bounding box到底哪個來預測呢?答案是:和該object的ground truth的IOU值最大的bounding box。
 

7.YOLO原始碼

# -*- coding: utf-8 -*-
 
import tensorflow as tf
import numpy as np
import cv2
 
 
# leaky_relu啟用函式
def leaky_relu(x, alpha=0.1):
    return tf.maximum(alpha * x, x)
 
 
class Yolo(object):
    def __init__(self, weights_file, input_image, verbose=True):
        # 後面程式列印描述功能的標誌位
        self.verbose = verbose
 
        # 檢測超引數
        self.S = 7  # cell數量
        self.B = 2  # 每個網格的邊界框數
        self.classes = ["aeroplane", "bicycle", "bird", "boat", "bottle",
                        "bus", "car", "cat", "chair", "cow", "diningtable",
                        "dog", "horse", "motorbike", "person", "pottedplant",
                        "sheep", "sofa", "train", "tvmonitor"]
        self.C = len(self.classes)  # 類別數
 
        self.x_offset = np.transpose(np.reshape(np.array([np.arange(self.S)] * self.S * self.B),
                                                [self.B, self.S, self.S]), [1, 2, 0])
        self.y_offset = np.transpose(self.x_offset, [1, 0, 2])  # 改變陣列的shape
 
        self.threshold = 0.2  # 類別置信度分數閾值
        self.iou_threshold = 0.4  # IOU閾值,小於0.4的會過濾掉
 
        self.max_output_size = 10  # NMS選擇的邊界框的最大數量
 
        self.sess = tf.Session()
        self._build_net()  # 【1】搭建網路模型(預測):模型的主體網路部分,這個網路將輸出[batch,7*7*30]的張量
        self._build_detector()  # 【2】解析網路的預測結果:先判斷預測框類別,再NMS
        self._load_weights(weights_file)  # 【3】匯入權重檔案
        self.detect_from_file(image_file=input_image)  # 【4】從預測輸入圖片,並可視化檢測邊界框、將obj的分類結果和座標儲存成txt。
 
    # 【1】搭建網路模型(預測):模型的主體網路部分,這個網路將輸出[batch,7*7*30]的張量
    def _build_net(self):
        # 列印狀態資訊
        if self.verbose:
            print("Start to build the network ...")
 
        # 輸入、輸出用佔位符,因為尺寸一般不會改變
        self.images = tf.placeholder(tf.float32, [None, 448, 448, 3])  # None表示不確定,為了自適應batchsize
 
        # 搭建網路模型
        net = self._conv_layer(self.images, 1, 64, 7, 2)
        net = self._maxpool_layer(net, 1, 2, 2)
        net = self._conv_layer(net, 2, 192, 3, 1)
        net = self._maxpool_layer(net, 2, 2, 2)
        net = self._conv_layer(net, 3, 128, 1, 1)
        net = self._conv_layer(net, 4, 256, 3, 1)
        net = self._conv_layer(net, 5, 256, 1, 1)
        net = self._conv_layer(net, 6, 512, 3, 1)
        net = self._maxpool_layer(net, 6, 2, 2)
        net = self._conv_layer(net, 7, 256, 1, 1)
        net = self._conv_layer(net, 8, 512, 3, 1)
        net = self._conv_layer(net, 9, 256, 1, 1)
        net = self._conv_layer(net, 10, 512, 3, 1)
        net = self._conv_layer(net, 11, 256, 1, 1)
        net = self._conv_layer(net, 12, 512, 3, 1)
        net = self._conv_layer(net, 13, 256, 1, 1)
        net = self._conv_layer(net, 14, 512, 3, 1)
        net = self._conv_layer(net, 15, 512, 1, 1)
        net = self._conv_layer(net, 16, 1024, 3, 1)
        net = self._maxpool_layer(net, 16, 2, 2)
        net = self._conv_layer(net, 17, 512, 1, 1)
        net = self._conv_layer(net, 18, 1024, 3, 1)
        net = self._conv_layer(net, 19, 512, 1, 1)
        net = self._conv_layer(net, 20, 1024, 3, 1)
        net = self._conv_layer(net, 21, 1024, 3, 1)
        net = self._conv_layer(net, 22, 1024, 3, 2)
        net = self._conv_layer(net, 23, 1024, 3, 1)
        net = self._conv_layer(net, 24, 1024, 3, 1)
        net = self._flatten(net)
        net = self._fc_layer(net, 25, 512, activation=leaky_relu)
        net = self._fc_layer(net, 26, 4096, activation=leaky_relu)
        net = self._fc_layer(net, 27, self.S * self.S * (self.B * 5 + self.C))
 
        # 網路輸出,[batch,7*7*30]的張量
        self.predicts = net
 
    # 【2】解析網路的預測結果:先判斷預測框類別,再NMS
    def _build_detector(self):
        # 原始圖片的寬和高
        self.width = tf.placeholder(tf.float32, name='img_w')
        self.height = tf.placeholder(tf.float32, name='img_h')
 
        # 網路迴歸[batch,7*7*30]:
        idx1 = self.S * self.S * self.C
        idx2 = idx1 + self.S * self.S * self.B
        # 1.類別概率[:,:7*7*20]  20維
        class_probs = tf.reshape(self.predicts[0, :idx1], [self.S, self.S, self.C])
        # 2.置信度[:,7*7*20:7*7*(20+2)]  2維
        confs = tf.reshape(self.predicts[0, idx1:idx2], [self.S, self.S, self.B])
        # 3.邊界框[:,7*7*(20+2):]  8維 -> (x,y,w,h)
        boxes = tf.reshape(self.predicts[0, idx2:], [self.S, self.S, self.B, 4])
 
        # 將x,y轉換為相對於影象左上角的座標
        # w,h的預測是平方根乘以影象的寬度和高度
        boxes = tf.stack([(boxes[:, :, :, 0] + tf.constant(self.x_offset, dtype=tf.float32)) / self.S * self.width,
                          (boxes[:, :, :, 1] + tf.constant(self.y_offset, dtype=tf.float32)) / self.S * self.height,
                          tf.square(boxes[:, :, :, 2]) * self.width,
                          tf.square(boxes[:, :, :, 3]) * self.height], axis=3)
 
        # 類別置信度分數:[S,S,B,1]*[S,S,1,C]=[S,S,B,類別置信度C]
        scores = tf.expand_dims(confs, -1) * tf.expand_dims(class_probs, 2)
 
        scores = tf.reshape(scores, [-1, self.C])  # [S*S*B, C]
        boxes = tf.reshape(boxes, [-1, 4])  # [S*S*B, 4]
 
        # 只選擇類別置信度最大的值作為box的類別、分數
        box_classes = tf.argmax(scores, axis=1)  # 邊界框box的類別
        box_class_scores = tf.reduce_max(scores, axis=1)  # 邊界框box的分數
 
        # 利用類別置信度閾值self.threshold,過濾掉類別置信度低的
        filter_mask = box_class_scores >= self.threshold
        scores = tf.boolean_mask(box_class_scores, filter_mask)
        boxes = tf.boolean_mask(boxes, filter_mask)
        box_classes = tf.boolean_mask(box_classes, filter_mask)
 
        # NMS (不區分不同的類別)
        # 中心座標+寬高box (x, y, w, h) -> xmin=x-w/2 -> 左上+右下box (xmin, ymin, xmax, ymax),因為NMS函式是這種計算方式
        _boxes = tf.stack([boxes[:, 0] - 0.5 * boxes[:, 2], boxes[:, 1] - 0.5 * boxes[:, 3],
                           boxes[:, 0] + 0.5 * boxes[:, 2], boxes[:, 1] + 0.5 * boxes[:, 3]], axis=1)
        nms_indices = tf.image.non_max_suppression(_boxes, scores,
                                                   self.max_output_size, self.iou_threshold)
        self.scores = tf.gather(scores, nms_indices)
        self.boxes = tf.gather(boxes, nms_indices)
        self.box_classes = tf.gather(box_classes, nms_indices)
 
    # 【3】匯入權重檔案
    def _load_weights(self, weights_file):
        # 列印狀態資訊
        if self.verbose:
            print("Start to load weights from file:%s" % (weights_file))
 
        # 匯入權重
        saver = tf.train.Saver()  # 初始化
        saver.restore(self.sess, weights_file)  # saver.restore匯入/saver.save儲存
 
    # 【4】從預測輸入圖片,並可視化檢測邊界框、將obj的分類結果和座標儲存成txt。
    # image_file是輸入圖片檔案路徑;
    # deteted_boxes_file="boxes.txt"是最後座標txt;detected_image_file="detected_image.jpg"是檢測結果視覺化圖片
    def detect_from_file(self, image_file, imshow=True, deteted_boxes_file="boxes.txt",
                         detected_image_file="detected_image.jpg"):
        # read image
        image = cv2.imread(image_file)
        img_h, img_w, _ = image.shape
        scores, boxes, box_classes = self._detect_from_image(image)
        predict_boxes = []
        for i in range(len(scores)):
            # 預測框資料為:[概率,x,y,w,h,類別置信度]
            predict_boxes.append((self.classes[box_classes[i]], boxes[i, 0],
                                  boxes[i, 1], boxes[i, 2], boxes[i, 3], scores[i]))
        self.show_results(image, predict_boxes, imshow, deteted_boxes_file, detected_image_file)
 
    ################# 對應【1】:定義conv/maxpool/flatten/fc層#############################################################
    # 卷積層:x輸入;id:層數索引;num_filters:卷積核個數;filter_size:卷積核尺寸;stride:步長
    def _conv_layer(self, x, id, num_filters, filter_size, stride):
 
        # 通道數
        in_channels = x.get_shape().as_list()[-1]
        # 均值為0標準差為0.1的正態分佈,初始化權重w;shape=行*列*通道數*卷積核個數
        weight = tf.Variable(
            tf.truncated_normal([filter_size, filter_size, in_channels, num_filters], mean=0.0, stddev=0.1))
        bias = tf.Variable(tf.zeros([num_filters, ]))  # 列向量
 
        # padding, 注意: 不用padding="SAME",否則可能會導致座標計算錯誤
        pad_size = filter_size // 2  # 除法運算,保留商的整數部分
        pad_mat = np.array([[0, 0], [pad_size, pad_size], [pad_size, pad_size], [0, 0]])
        x_pad = tf.pad(x, pad_mat)
        conv = tf.nn.conv2d(x_pad, weight, strides=[1, stride, stride, 1], padding="VALID")
        output = leaky_relu(tf.nn.bias_add(conv, bias))
 
        # 列印該層資訊
        if self.verbose:
            print('Layer%d:type=conv,num_filter=%d,filter_size=%d,stride=%d,output_shape=%s'
                  % (id, num_filters, filter_size, stride, str(output.get_shape())))
 
        return output
 
    # 池化層:x輸入;id:層數索引;pool_size:池化尺寸;stride:步長
    def _maxpool_layer(self, x, id, pool_size, stride):
        output = tf.layers.max_pooling2d(inputs=x,
                                         pool_size=pool_size,
                                         strides=stride,
                                         padding='SAME')
        if self.verbose:
            print('Layer%d:type=MaxPool,pool_size=%d,stride=%d,out_shape=%s'
                  % (id, pool_size, stride, str(output.get_shape())))
        return output
 
    # 扁平層:因為接下來會連線全連線層,例如[n_samples, 7, 7, 32] -> [n_samples, 7*7*32]
    def _flatten(self, x):
        tran_x = tf.transpose(x, [0, 3, 1, 2])  # [batch,行,列,通道數channels] -> [batch,通道數channels,列,行]
        nums = np.product(x.get_shape().as_list()[1:])  # 計算的是總共的神經元數量,第一個表示batch數量所以去掉
        return tf.reshape(tran_x, [-1, nums])  # [batch,通道數channels,列,行] -> [batch,通道數channels*列*行],-1代表自適應batch數量
 
    # 全連線層:x輸入;id:層數索引;num_out:輸出尺寸;activation:啟用函式
    def _fc_layer(self, x, id, num_out, activation=None):
        num_in = x.get_shape().as_list()[-1]  # 通道數/維度
        # 均值為0標準差為0.1的正態分佈,初始化權重w;shape=行*列*通道數*卷積核個數
        weight = tf.Variable(tf.truncated_normal(shape=[num_in, num_out], mean=0.0, stddev=0.1))
        bias = tf.Variable(tf.zeros(shape=[num_out, ]))  # 列向量
        output = tf.nn.xw_plus_b(x, weight, bias)
 
        # 正常全連線層是leak_relu啟用函式;但是最後一層是liner函式
        if activation:
            output = activation(output)
 
        # 列印該層資訊
        if self.verbose:
            print('Layer%d:type=Fc,num_out=%d,output_shape=%s'
                  % (id, num_out, str(output.get_shape())))
        return output
 
    ######################## 對應【4】:視覺化檢測邊界框、將obj的分類結果和座標儲存成txt#########################################
    def _detect_from_image(self, image):
        """Do detection given a cv image"""
        img_h, img_w, _ = image.shape
        img_resized = cv2.resize(image, (448, 448))
        img_RGB = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)
        img_resized_np = np.asarray(img_RGB)
        _images = np.zeros((1, 448, 448, 3), dtype=np.float32)
        _images[0] = (img_resized_np / 255.0) * 2.0 - 1.0
        scores, boxes, box_classes = self.sess.run([self.scores, self.boxes, self.box_classes],
                                                   feed_dict={self.images: _images, self.width: img_w,
                                                              self.height: img_h})
        return scores, boxes, box_classes
 
    def show_results(self, image, results, imshow=True, deteted_boxes_file=None,
                     detected_image_file=None):
        """Show the detection boxes"""
        img_cp = image.copy()
        if deteted_boxes_file:
            f = open(deteted_boxes_file, "w")
        # draw boxes
        for i in range(len(results)):
            x = int(results[i][1])
            y = int(results[i][2])
            w = int(results[i][3]) // 2
            h = int(results[i][4]) // 2
            if self.verbose:
                print("class: %s, [x, y, w, h]=[%d, %d, %d, %d], confidence=%f"
                      % (results[i][0], x, y, w, h, results[i][-1]))
 
                # 中心座標 + 寬高box(x, y, w, h) -> xmin = x - w / 2 -> 左上 + 右下box(xmin, ymin, xmax, ymax)
                cv2.rectangle(img_cp, (x - w, y - h), (x + w, y + h), (0, 255, 0), 2)
 
                # 在邊界框上顯示類別、分數(類別置信度)
                cv2.rectangle(img_cp, (x - w, y - h - 20), (x + w, y - h), (125, 125, 125), -1)  # puttext函式的背景
                cv2.putText(img_cp, results[i][0] + ' : %.2f' % results[i][5], (x - w + 5, y - h - 7),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
 
            if deteted_boxes_file:
                # 儲存obj檢測結果為txt檔案
                f.write(results[i][0] + ',' + str(x) + ',' + str(y) + ',' +
                        str(w) + ',' + str(h) + ',' + str(results[i][5]) + '\n')
        if imshow:
            cv2.imshow('YOLO_small detection', img_cp)
            cv2.waitKey(1)
        if detected_image_file:
            cv2.imwrite(detected_image_file, img_cp)
        if deteted_boxes_file:
            f.close()
 
 
if __name__ == '__main__':
    yolo_net = Yolo(weights_file='D:/Python/YOLOv1-Tensorflow-master/YOLO_small.ckpt',
                    input_image='D:/Python/YOLOv1-Tensorflow-master/car.jpg')

附上參考的blog:

https://blog.csdn.net/u014380165/article/details/72616238

https://blog.csdn.net/shenxiaolu1984/article/details/78826995

https://blog.csdn.net/zhangjunp3/article/details/80421814