zoukankan      html  css  js  c++  java
  • 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包含的具体物体的种类。

    数据处理部分就结束了!欢迎各位大佬一起讨论! 

  • 相关阅读:
    Vs2013在Linux开发中的应用(19): 启动gdb
    Codeforces Round #277 (Div. 2)---C. Palindrome Transformation (贪心)
    DataGridView依据下拉列表显示数据
    android POI搜索,附近搜索,周边搜索定位介绍
    HDU OJ Max sum 题目1003
    Android时时监測手机的旋转角度 依据旋转角度确定在什么角度载入竖屏布局 在什么时候载入横屏布局
    Hadoop架构设计、执行原理具体解释
    关联引用
    Linux性能诊断工具
    HDU 5089 Assignment(rmq+二分 或 单调队列)
  • 原文地址:https://www.cnblogs.com/wangxinzhe/p/10592184.html
Copyright © 2011-2022 走看看