一.前言

本次分析的源码为大佬复现的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_data,preprocess_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,来看下具体操作吧!关键代码及部分注释如下:

 #!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Wed Mar 27 22:02:48 2019 @author: wxz
"""
import numpy as np
from PIL import Image
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):
line = annotation_line.split()
image = Image.open(line[0]) #获取图像位置并读取
iw, ih = image.size #(500,375 )
h, w = input_shape
box = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]])
'''
得到物体坐标信息及类别信息 [xmin, ymin, xmax, ymax, class]
[[263 211 324 339 8]
[165 264 253 372 8]
[241 194 295 299 8]]
'''
if not random:
# resize image
scale = min(np.float(w)/iw, np.float(h)/ih) #python 3 scale = min(w/iw, np.h/ih)
nw = int(iw*scale)
nh = int(ih*scale)
dx = (w-nw)//2
dy = (h-nh)//2
image_data=0
if proc_img:
image = image.resize((nw,nh), Image.BICUBIC)
new_image = Image.new('RGB', (w,h), (128,128,128))#生成一个(416,416)灰色图像
new_image.paste(image, (dx, dy)) #将image 放在灰色图中央。 图像开始的位置坐标会增大哦!
image_data = np.array(new_image)/255.
# correct boxes
box_data = np.zeros((max_boxes,5))
if len(box)>0:
np.random.shuffle(box)
if len(box)>max_boxes: box = box[:max_boxes]
box[:, [0,2]] = box[:, [0,2]]*scale + dx #dx 为xmin,xmax增量 图像位置变化了 所以 标注信息也要随之变化 这样标注框才不会偏移。
box[:, [1,3]] = box[:, [1,3]]*scale + dy #dy 为ymin,ymax的增量
box_data[:len(box)] = box return image_data, box_data
if __name__=='__main__':
datas = './datas/data.txt'
input_shape = (416,416)
with open(datas,'r') as f:
annotation_lines = f.readlines()
image ,boxes=get_random_data(annotation_lines[0], input_shape)
'''
boxes:
[[137. 271. 210. 361. 8.]
[218. 227. 269. 334. 8.]
[200. 213. 245. 300. 8.]
[ 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. 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. 0. 0.]
[ 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0.]]
'''

图像进行等比变化后的效果。灰色部分为dy(dx=0)。是不是图像开始的位置增大了呢嘿嘿,这就是等比变化的过程!有不清楚地方在交流吧。由于时间有限,先写这些,以后慢慢更新。想要一个batchsize操作代码de可附邮箱。

.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类检测物体)

 #!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 28 22:03:10 2019 @author: wxz
"""
def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
assert (true_boxes[..., 4]<num_classes).all(), 'class id must be less than num_classes' #判断类别是否超出了20
'''true_boxes[...,4] 表示取出array的所的第四个数值
array([[ 8., 8., 8., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0.], 第一张图片20个标注框的类别信息
[ 6., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0.], 第一张图片20个标注框的类别信息
[12., 14., 14., 14., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0.],
[ 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0.],
[ 7., 7., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0.],
[ 6., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0.]])
'''
num_layers = len(anchors)//3 # default setting num_layers=3
anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]]
true_boxes = np.array(true_boxes, dtype='float32')
input_shape = np.array(input_shape, dtype='int32')
boxes_xy = (true_boxes[..., 0:2] + true_boxes[..., 2:4]) // 2 # 计算中心点坐标 (xmin+xmax)/2 (ymin+ymax)/2
boxes_wh = true_boxes[..., 2:4] - true_boxes[..., 0:2]#计算图片宽高 xmax-xmin ymax-ymin
true_boxes[..., 0:2] = boxes_xy/input_shape[::-1] #将中心点及宽高 对输入图片416 做归一化
true_boxes[..., 2:4] = boxes_wh/input_shape[::-1]
m = true_boxes.shape[0] #获取barchsize大小 此处m=6
grid_shapes = [input_shape//{0:32, 1:16, 2:8}[l] for l in range(num_layers)] #获取特征图的尺寸 13, 26,52
'''
grid_shapes是一个长度为3的列表具体数值如下
[array([13, 13], dtype=int32), array([26, 26], dtype=int32), array([52, 52], dtype=int32)] '''
y_true = [np.zeros((m,grid_shapes[l][0],grid_shapes[l][1],len(anchor_mask[l]),5+num_classes),
dtype='float32') for l in range(num_layers)]
'''
y_true是一个长度为3的列表,列表包含三个numpyarray float32类型的全零矩阵,具体形状如下
(6, 13, 13, 3, 25) (6, 26, 26, 3, 25) (6, 52, 52, 3, 25) 即三个尺度特征图大小 '''
# Expand dim to apply broadcasting.
anchors = np.expand_dims(anchors, 0)#扩展第一个维度原来为(9,2) --->(1,9,2)这样操作可以充分利用numpy的广播机制
anchor_maxes = anchors / 2. #将anchors 中心点放(0,0) 因为anchors没有中心点只有宽高,计算与boxs计算iou时两者中心点均为(0.0)
anchor_mins = -anchor_maxes # anchor_mins 记录了xim ymin 两个坐标点
valid_mask = boxes_wh[..., 0]>0 #判断是否有异常标注boxes for b in range(m):
# Discard zero rows.
wh = boxes_wh[b, valid_mask[b]] #第一个图片为例 wh=[[ 51. 107.] shape=(3,2)
if len(wh)==0: continue # [ 45. 87.]
# Expand dim to apply broadcasting. [ 73. 90.]]
wh = np.expand_dims(wh, -2)#在第二个维度扩展 [[[ 51. 107.]] shape=(3,1,2)
box_maxes = wh / 2. # [[ 45. 87.]]
box_mins = -box_maxes # [[ 73. 90.]]]
intersect_mins = np.maximum(box_mins, anchor_mins)
intersect_maxes = np.minimum(box_maxes, anchor_maxes)
intersect_wh = np.maximum(intersect_maxes - intersect_mins, 0.)
intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
box_area = wh[..., 0] * wh[..., 1]
anchor_area = anchors[..., 0] * anchors[..., 1]
iou = intersect_area / (box_area + anchor_area - intersect_area)
print iou
# Find best anchor for each true box
best_anchor = np.argmax(iou, axis=-1)
print best_anchor
for t, n in enumerate(best_anchor):
print t
print n
for l in range(num_layers):
if n in anchor_mask[l]:
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')
k = anchor_mask[l].index(n)
c = true_boxes[b,t, 4].astype('int32')
y_true[l][b, j, i, k, 0:4] = true_boxes[b,t, 0:4]
y_true[l][b, j, i, k, 4] = 1
y_true[l][b, j, i, k, 5+c] = 1 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包含的具体物体的种类。

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

yolov3源码分析keras(一)数据的处理的更多相关文章

  1. yolov3源码分析keras(二)损失函数计算

    一.前言 损失函数计算主要分析两部分一部分是yolo_head函数的分析另一部分为ignore_mask的生成的分析. 二.重要细节分析 2.1损失函数计算具体代码及部分分析 def yolo_los ...

  2. tcprstat源码分析之tcp数据包分析

    tcprstat是percona用来监测mysql响应时间的.不过对于任何运行在TCP协议上的响应时间,都可以用.本文主要做源码分析,如何使用tcprstat请大家查看博文<tcprstat分析 ...

  3. 【Netty源码分析】发送数据过程

    前面两篇博客[Netty源码分析]Netty服务端bind端口过程和[Netty源码分析]客户端connect服务端过程中我们分别介绍了服务端绑定端口和客户端连接到服务端的过程,接下来我们分析一下数据 ...

  4. jQuery 源码分析(十四) 数据操作模块 类样式操作 详解

    jQuery的属性操作模块总共有4个部分,本篇说一下第3个部分:类样式操作部分,用于修改DOM元素的class特性的,对于类样式操作来说,jQuery并没有定义静态方法,而只定义了实例方法,如下: a ...

  5. jQuery 源码分析(十二) 数据操作模块 html特性 详解

    jQuery的属性操作模块总共有4个部分,本篇说一下第1个部分:HTML特性部分,html特性部分是对原生方法getAttribute()和setAttribute()的封装,用于修改DOM元素的特性 ...

  6. jQuery 源码分析(十五) 数据操作模块 val详解

    jQuery的属性操作模块总共有4个部分,本篇说一下最后一个部分:val值的操作,也是属性操作里最简单的吧,只有一个API,如下: val(vlaue)        ;获取匹配元素集合中第一个元素的 ...

  7. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  8. jquery2源码分析系列

    学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...

  9. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

随机推荐

  1. 2018.08.02 hdu1558 Segment set(并查集+计算几何)

    传送门 这个直接用并查集维护. 每加入一条线段就将它与其他能相交的集合合并,维护一个size" role="presentation" style="posit ...

  2. head first 设计模式文摘

    1 欢迎来到设计模式世界:设计模式入门 2 让你的对象知悉现况:观察者模式 3 装饰对象:装饰者模式 4 工厂模式:烘烤OO的精华 5 单件模式:独一无二的对象 6 命令模式:封装调用 7 适配器模式 ...

  3. JPEG Camer 图片上传

    /* Linksprite */ #include <SoftwareSerial.h> #include <Ethernet.h> #include <SPI.h> ...

  4. spring mvc 文档哪里有

    官方: http://docs.spring.io/spring/docs/4.2.0.RC1/spring-framework-reference/htmlsingle/#spring-web Th ...

  5. 零停重启程序工具Huptime研究

    目录 目录 1 1. 官网 1 2. 功能 1 3. 环境要求 2 4. 实现原理 2 5. SIGHUP信号处理 3 6. 重启线程 4 7. 重启目标程序 5 8. 系统调用钩子辅助 6 9. 被 ...

  6. (最小生成树 次小生成树)The Unique MST -- POJ -- 1679

    链接: http://poj.org/problem?id=1679 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=82831#probl ...

  7. paip.双网卡多网卡不能上网的联网配置

    paip.双网卡多网卡不能上网的联网配置 作者Attilax ,  EMAIL:1466519819@qq.com  来源:attilax的专栏 地址:http://blog.csdn.net/att ...

  8. spring MVC controller中的方法跳转到另外controller中的某个method的方法

    1. 需求背景     需求:spring MVC框架controller间跳转,需重定向.有几种情况:不带参数跳转,带参数拼接url形式跳转,带参数不拼接参数跳转,页面也能显示. 本来以为挺简单的一 ...

  9. What if you are involved in an automobile accident in the US

    What if you are involved in an automobile accident in the US With increasing Chinese tourists and vi ...

  10. SQL Server OS 调度

    --SQL SERVER OS 采用合作模式的线程调度模式,即除非Worker主动放弃CPU,否则SQL OS 不会强制剥夺其CPU,从而减少Context Switch --默认设置下,SQL SE ...