yolov3源碼分析keras(一)數據的處理


一.前言

       本次分析的源碼為大佬復現的keras版本,上一波地址:https://github.com/qqwweee/keras-yolo3 

       初步打算重點分析兩部分,第一部分為數據,即分析圖像如何做等比變化如何將標注框(groud truth boxs) 的信息轉換為計算損失時使用的label。另一部分為損失函數計算的源碼。個人認為這兩部分比較難理解,所以想把自己的理解寫出來,以便大家一起交流。作為菜鳥中的菜菜鳥可能理解有不到位的地方,希望大家批評指正。


 二.數據處理關鍵代碼位置及功能簡介

           在keras-yolo3工程下,主程序為train.py。數據讀取的代碼位於train.py的59行,名稱為data_generator_wrapper的函數。
           data_generator_wrapper函數    調用位置:train.py的59行        函數定義位置:train.py的184行                                功能:獲取讀取數據的長度並判斷長度是否為0,調用data_generator函數。
           data_generator函數                   調用位置:train.py的187行      函數定義位置:model.py的165行                              功能:按照batchsize大小讀取數據,並打亂順序送入到get_random_data函數,將得到的圖像和標注信息轉換為numpy格式,將得到的標注信息送入到preprocess_true_boxes行處理。
           get_random_data函數               調用位置:train.py的175行      函數定義位置:yolo3文件夾下util.py 的36行           功能:處理標注數據,限制最大框為20(同時也方便了拼接操作,詳細原因后邊解釋)。

           preprocess_true_boxes函數      調用位置: train.py的181行     函數定義位置:yolo3文件夾下model.py 的232行     功能:將boxs信息及與他們匹配的anchors,置信度信息,類別信息保存到y_true中,即label標簽的制作。

 


三.關鍵代碼詳解

       通過代碼邏輯可以看出最主要的操作在 get_random_datapreprocess_true_boxes兩個函數中,為了方便分析本次選取了6張圖片及其標注信息,模擬一個batchsize的數據。數據具體信息如下:

------------------------------------------------------------------------------------------------------------------------------------

./datas/000005.jpg 263,211,324,339,8 165,264,253,372,8 241,194,295,299,8
./datas/000007.jpg 141,50,500,330,6
./datas/000009.jpg 69,172,270,330,12 150,141,229,284,14 285,201,327,331,14 258,198,297,329,14
./datas/000016.jpg 92,72,305,473,1
./datas/000019.jpg 231,88,483,256,7 11,113,266,259,7
./datas/000020.jpg 33,148,371,416,6

------------------------------------------------------------------------------------------------------------------------------------

  1. get_random_data關鍵部分分析

         主要分析圖片等比變化的方式,等比變化的方式由於不會改變物體的原有比例,這樣檢測精度會高一些(個人理解)。下面以一張圖片為栗子,並且設置random=False,來看下具體操作吧!關鍵代碼及部分注釋如下:

 1 #!/usr/bin/env python2
 2 # -*- coding: utf-8 -*-
 3 """
 4 Created on Wed Mar 27 22:02:48 2019
 5 
 6 @author: wxz
 7 """
 8 import numpy as np
 9 from PIL import Image
10 def get_random_data(annotation_line, input_shape, random=False, max_boxes=20, jitter=.3, hue=.1, sat=1.5, val=1.5, proc_img=True):
11     line = annotation_line.split()
12     image = Image.open(line[0]) #獲取圖像位置並讀取
13     iw, ih = image.size   #(500,375 )
14     h, w = input_shape
15     box = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]])
16     '''
17     得到物體坐標信息及類別信息 [xmin, ymin, xmax, ymax, class] 
18     [[263 211 324 339   8]
19      [165 264 253 372   8]
20      [241 194 295 299   8]]    
21     '''   
22     if not random:
23         # resize image
24         scale = min(np.float(w)/iw,  np.float(h)/ih) #python 3 scale = min(w/iw,  np.h/ih)
25         nw = int(iw*scale)
26         nh = int(ih*scale)
27         dx = (w-nw)//2
28         dy = (h-nh)//2
29         image_data=0  
30         if proc_img:
31             image = image.resize((nw,nh), Image.BICUBIC)
32             new_image = Image.new('RGB', (w,h), (128,128,128))#生成一個(416,416)灰色圖像
33             new_image.paste(image, (dx, dy)) #將image 放在灰色圖中央。 圖像開始的位置坐標會增大哦! 
34             image_data = np.array(new_image)/255.
35         # correct boxes
36         box_data = np.zeros((max_boxes,5))
37         if len(box)>0:
38             np.random.shuffle(box)
39             if len(box)>max_boxes: box = box[:max_boxes]
40             box[:, [0,2]] = box[:, [0,2]]*scale + dx #dx 為xmin,xmax增量  圖像位置變化了 所以 標注信息也要隨之變化 這樣標注框才不會偏移。
41             box[:, [1,3]] = box[:, [1,3]]*scale + dy #dy 為ymin,ymax的增量
42             box_data[:len(box)] = box
43 
44         return image_data, box_data
45 if __name__=='__main__':
46     datas = './datas/data.txt'
47     input_shape = (416,416)
48     with open(datas,'r') as f:
49         annotation_lines = f.readlines()     
50     image ,boxes=get_random_data(annotation_lines[0], input_shape)
51     '''
52     boxes:
53     [[137. 271. 210. 361.   8.]
54      [218. 227. 269. 334.   8.]
55      [200. 213. 245. 300.   8.]
56      [  0.   0.   0.   0.   0.]
57      [  0.   0.   0.   0.   0.]
58      [  0.   0.   0.   0.   0.]
59      [  0.   0.   0.   0.   0.]
60      [  0.   0.   0.   0.   0.]
61      [  0.   0.   0.   0.   0.]
62      [  0.   0.   0.   0.   0.]
63      [  0.   0.   0.   0.   0.]
64      [  0.   0.   0.   0.   0.]
65      [  0.   0.   0.   0.   0.]
66      [  0.   0.   0.   0.   0.]
67      [  0.   0.   0.   0.   0.]
68      [  0.   0.   0.   0.   0.]
69      [  0.   0.   0.   0.   0.]
70      [  0.   0.   0.   0.   0.]
71      [  0.   0.   0.   0.   0.]
72      [  0.   0.   0.   0.   0.]]
73     '''

 圖像進行等比變化后的效果。灰色部分為dy(dx=0)。是不是圖像開始的位置增大了呢嘿嘿,這就是等比變化的過程!有不清楚地方在交流吧。由於時間有限,先寫這些,以后慢慢更新。想要一個batchsize操作代碼de可附郵箱。

  2.preprocess_true_boxes  關鍵部分分析 

       輸入true_boxes類型為np.array,形狀為(6,20,5)的矩陣,6是本次選取了6張圖片作為一個btachsize, 20因為每個圖片定義包含的標注框最大為20不夠的補0,超出的舍棄。5代表物體坐標信息及類別信息 [xmin, ymin, xmax, ymax, class] 。

       imput_shape=(416,416)。

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

       num_classes = 20 (voc 2007包含20類檢測物體)  

 1 #!/usr/bin/env python2
 2 # -*- coding: utf-8 -*-
 3 """
 4 Created on Thu Mar 28 22:03:10 2019
 5 
 6 @author: wxz
 7 """
 8 def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
 9     assert (true_boxes[..., 4]<num_classes).all(), 'class id must be less than num_classes' #判斷類別是否超出了20
10     '''true_boxes[...,4] 表示取出array的所的第四個數值
11        array([[ 8.,  8.,  8.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
12          0.,  0.,  0.,  0.,  0.,  0.,  0.], 第一張圖片20個標注框的類別信息
13        [ 6.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
14          0.,  0.,  0.,  0.,  0.,  0.,  0.], 第一張圖片20個標注框的類別信息
15        [12., 14., 14., 14.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
16          0.,  0.,  0.,  0.,  0.,  0.,  0.],
17        [ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
18          0.,  0.,  0.,  0.,  0.,  0.,  0.],
19        [ 7.,  7.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
20          0.,  0.,  0.,  0.,  0.,  0.,  0.],
21        [ 6.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
22          0.,  0.,  0.,  0.,  0.,  0.,  0.]])   
23     '''  
24     num_layers = len(anchors)//3 # default setting  num_layers=3
25     anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]]
26     true_boxes = np.array(true_boxes, dtype='float32')
27     input_shape = np.array(input_shape, dtype='int32')
28     boxes_xy = (true_boxes[..., 0:2] + true_boxes[..., 2:4]) // 2 # 計算中心點坐標  (xmin+xmax)/2  (ymin+ymax)/2
29     boxes_wh = true_boxes[..., 2:4] - true_boxes[..., 0:2]#計算圖片寬高  xmax-xmin   ymax-ymin
30     true_boxes[..., 0:2] = boxes_xy/input_shape[::-1]  #將中心點及寬高 對輸入圖片416 做歸一化
31     true_boxes[..., 2:4] = boxes_wh/input_shape[::-1]
32     m = true_boxes.shape[0]   #獲取barchsize大小 此處m=6
33     grid_shapes = [input_shape//{0:32, 1:16, 2:8}[l] for l in range(num_layers)] #獲取特征圖的尺寸 13, 26,52 
34     '''
35     grid_shapes是一個長度為3的列表具體數值如下
36     [array([13, 13], dtype=int32), array([26, 26], dtype=int32), array([52, 52], dtype=int32)] 
37    
38     '''
39     y_true = [np.zeros((m,grid_shapes[l][0],grid_shapes[l][1],len(anchor_mask[l]),5+num_classes),
40         dtype='float32') for l in range(num_layers)]   
41     '''
42     y_true是一個長度為3的列表,列表包含三個numpyarray float32類型的全零矩陣,具體形狀如下
43     (6, 13, 13, 3, 25) (6, 26, 26, 3, 25) (6, 52, 52, 3, 25) 即三個尺度特征圖大小
44     
45     '''
46     # Expand dim to apply broadcasting.
47     anchors = np.expand_dims(anchors, 0)#擴展第一個維度原來為(9,2) --->(1,9,2)這樣操作可以充分利用numpy的廣播機制
48     anchor_maxes = anchors / 2.  #將anchors 中心點放(0,0) 因為anchors沒有中心點只有寬高,計算與boxs計算iou時兩者中心點均為(0.0)
49     anchor_mins = -anchor_maxes # anchor_mins 記錄了xim ymin 兩個坐標點
50     valid_mask = boxes_wh[..., 0]>0  #判斷是否有異常標注boxes 
51 
52     for b in range(m):
53         # Discard zero rows.
54         wh = boxes_wh[b, valid_mask[b]] #第一個圖片為例 wh=[[ 51. 107.]   shape=(3,2)
55         if len(wh)==0: continue   #                         [ 45.  87.]                                                 
56         # Expand dim to apply broadcasting.                 [ 73.  90.]]
57         wh = np.expand_dims(wh, -2)#在第二個維度擴展  [[[ 51. 107.]]   shape=(3,1,2)
58         box_maxes = wh / 2.                #          [[ 45.  87.]]
59         box_mins = -box_maxes              #           [[ 73.  90.]]]      
60         intersect_mins = np.maximum(box_mins, anchor_mins) 
61         intersect_maxes = np.minimum(box_maxes, anchor_maxes)
62         intersect_wh = np.maximum(intersect_maxes - intersect_mins, 0.)
63         intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
64         box_area = wh[..., 0] * wh[..., 1]
65         anchor_area = anchors[..., 0] * anchors[..., 1]
66         iou = intersect_area / (box_area + anchor_area - intersect_area)
67         print iou
68         # Find best anchor for each true box
69         best_anchor = np.argmax(iou, axis=-1)
70         print best_anchor
71         for t, n in enumerate(best_anchor):
72             print t
73             print n
74             for l in range(num_layers):
75                 if n in anchor_mask[l]:
76                     i = np.floor(true_boxes[b,t,0]*grid_shapes[l][1]).astype('int32')
77                     j = np.floor(true_boxes[b,t,1]*grid_shapes[l][0]).astype('int32')
78                     k = anchor_mask[l].index(n)
79                     c = true_boxes[b,t, 4].astype('int32')
80                     y_true[l][b, j, i, k, 0:4] = true_boxes[b,t, 0:4]
81                     y_true[l][b, j, i, k, 4] = 1
82                     y_true[l][b, j, i, k, 5+c] = 1
83 
84     return y_true

 

以下計算過程均以第一張圖片為例:
2.1  boxes_wh 的值為
    [[[ 51. 107.]
      [ 45. 87.]
      [ 73. 90.]
       [ 0. 0.]
            [ 0. 0.]
    [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]
            [ 0. 0.]]

          boxes_wh[..., 0]--->array([[ 51., 45., 73., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]
         valid_mask=boxes_wh[..., 0]>0--->[[ True True True False False False False False False False False False False False False False False False False False]
         可以看到大於0的位置對應的為true。
         wh=boxes_wh[b, valid_mask[b]] 保留對應位置為true的行去掉位置為false的行,結果如下所示:
         [[ 51. 107.]
           [ 45. 87.]  
           [ 73. 90.]]
2.2 boxes與anchors計算iou
  anchors在第一維度進行了擴展維度(9,2)--> (1,9,2) wh在第二個維度進行了擴展(3,2)--->(3,1,2)
  anchor_maxes: [[[ 5. 6.5]    box_maxes: [[[25.5 53.5]]
           [ 8. 15. ]                         [[22.5 43.5]]
                                  [ 16.5 11.5]                     [[36.5 45. ]]]
                                  [ 15. 30.5]
                                  [ 31. 22.5]
                                  [ 29.5 59.5]
                                  [ 58. 45. ]
                                  [ 78. 99. ]
                                  [ 186.5 163. ]]]

  計算時廣播后的形式:[[[ 5. 6.5]   shape=(3,9,2)          box_maxes: [[[25.5 53.5]            shape=(3,9,2)   大佬的操作很666,充分利用了廣播的特性。
                                            [ 8. 15. ]                                                         [25.5 53.5]
              [ 16.5 11.5]                                                    [25.5 53.5]
              [ 15. 30.5]                                                      [25.5 53.5]
                                             [ 31. 22.5]                                                     [25.5 53.5]
                                             [ 29.5 59.5]                                                   [25.5 53.5]
                                             [ 58. 45. ]                                                      [25.5 53.5]
                                             [ 78. 99. ]                                                      [25.5 53.5]
                                             [186.5 163. ]]                                                [25.5 53.5]]
                                            [[ 5. 6.5]                                                         [[22.5 43.5]
                                             [ 8. 15. ]                                                         [22.5 43.5]
                                             [ 16.5 11.5]                                                    [22.5 43.5]
                                             [ 15. 30.5]                                                      [22.5 43.5]
                                             [ 31. 22.5]                                                      [22.5 43.5]
                                             [ 29.5 59.5]                                                    [22.5 43.5]
                                             [ 58. 45. ]                                                       [22.5 43.5]
                                             [ 78. 99. ]                                                       [22.5 43.5]
                                             [186.5 163. ]]                                                 [22.5 43.5]]
                                            [[ 5. 6.5]                                                         [[36.5 45.]
                                             [ 8. 15. ]                                                         [36.5 45.]
                                             [ 16.5 11.5]                                                    [36.5 45.]
                                             [ 15. 30.5]                                                      [36.5 45.]
                                             [ 31. 22.5]                                                      [36.5 45.]
                                             [ 29.5 59.5]                                                    [36.5 45.]
                                             [ 58. 45. ]                                                       [36.5 45.]
                                             [ 78. 99. ]                                                       [36.5 45.]
                                             [186.5 163. ]]]                                                [36.5 45.]]]
計算時相當於每個box與每個anchor都計算了iou。

2.3.label存儲形式及具體含義
  依舊以第一張圖片為例,第一張圖片有三個標注框,通過計算得到第一張圖片的iou矩陣,shape=(3,9)
       [[[0.02382261 0.08796042 0.13908741 0.33534909 0.38558468 0.77723971 0.40594322 0.17667055 0.04487738]
         [0.03320562 0.12260536 0.19386973 0.46743295 0.43269231 0.55761288 0.375 0.12674825 0.03219625]
         [0.01978691 0.07305936 0.11552511 0.27853881 0.42465753 0.6412269 0.62931034 0.21270396 0.05403049]]]
         第一行為標注的boxes中第一個box與9個anchors的iou,第二行為標注boxes的中第二個box與9個anchors的iou,第三行為標注boxes的中第三個box與9個anchors的iou。
         best_anchor = np.argmax(iou, axis=-1) 取最后一個維度最大值的索引。
         best_anchor = [5 5 5]可見三個boxes都與第五個anchor的iou最大。
         enumerate(best_anchor)---> [(1,5),(2,5),(3,5)]
        代碼中t表示圖片中的第幾個box,n為第幾個anchor使得此box取得最大值。
        前邊提到過,y_true是一個長度為3的列表,列表包含三個numpyarray float32類型的全零矩陣,numpyarray具體形狀如下
        (  6, 13, 13, 3, 25) (6, 26, 26, 3, 25) (6, 52, 52, 3, 25) 即三個尺度特征圖大小。

         將坐標位置映射到對應特征圖上。

                  i = np.floor(true_boxes[b,t,0]*grid_shapes[l][1]).astype('int32')
                 j = np.floor(true_boxes[b,t,1]*grid_shapes[l][0]).astype('int32')

          true_boxes為歸一化的后的坐標。歸一化的好處是無論對應特征圖大小為多少,原圖上框的坐標位置信息總能映射到特征圖的對應位置。             
          摘取關鍵代碼,l表示第幾個尺度特征圖,一共三個尺度,l=(0,1,2)
          y_true[l][b, j, i, k, 0:4] = true_boxes[b,t, 0:4] --->b表示這一個batchsize的第幾個圖片,k記錄是三套anchor中的第幾個anchor。最后一維度0,1,2,3索引保存box映射到不同尺度特征圖時的坐標值。j,i為四個坐標的位置信息。
          y_true[l][b, j, i, k, 4] = 1 ---> 第4個索引記錄的為box包含物體的置信度信息,即這個box有沒有包含物體,包含為1,不包含為0。
          y_true[l][b, j, i, k, 5+c] = 1 ---> 第5+c索引記錄box包含的具體物體的種類。

數據處理部分就結束了!歡迎各位大佬一起討論! 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM