Fork版本项目地址:SSD

一、输入标签生成

在数据预处理之后,图片、类别、真实框格式较为原始,不能够直接作为损失函数的输入标签(ssd向前网络只需要图像就行,这里的处理主要需要满足loss的计算),对于一张图片(三维CHW)我们需要如下格式的数据作为损失函数标签:

gclasse:           搜索框对应的真实类别

         长度为ssd特征层f的list,每一个元素是一个Tensor,shape为:该层中心点行数×列数×每个中心点包含搜索框数目

gscores:           搜索框和真实框的IOU,gclasses中记录的就是该真实框的类别

            长度为ssd特征层f的list,每一个元素是一个Tensor,shape为:该层中心点行数×列数×每个中心点包含搜索框数目

glocalisations:  搜索框相较于真实框位置修正,由于有4个坐标,所以维度多了一维

         长度为ssd特征层f的list,每一个元素是一个Tensor,shape为:该层中心点行数×列数×每个中心点包含搜索框数目×4

为了计算出上面标签,我们函数调用如下(train_ssd_network.py):

            # f层个(m,m,k),f层个(m,m,k,4xywh),f层个(m,m,k) f层表示提取ssd特征的层的数目
# 0-20数字,方便loss的坐标记录,IOU值
gclasses, glocalisations, gscores = \
ssd_net.bboxes_encode(glabels, gbboxes, ssd_anchors)

输入变量都是前几节中的函数输出(train_ssd_network.py):

ssd_anchors = ssd_net.anchors(ssd_shape)  # 调用类方法,创建搜素框

# Pre-processing image, labels and bboxes.
# 'CHW' (n,) (n, 4)
image, glabels, gbboxes = \
image_preprocessing_fn(image, glabels, gbboxes,
out_shape=ssd_shape, # (300,300)
data_format=DATA_FORMAT) # 'NCHW'

至此,我们再来看一看该函数如何实现,其处理过程是按照ssd特征层进行划分,首先建立三个list,然后对于每一个特征层计算该层的三个Tensor,最后分别添加进list中(ssd_common.py):

def tf_ssd_bboxes_encode(labels,
bboxes,
anchors,
num_classes,
no_annotation_label,
ignore_threshold=0.5,
prior_scaling=(0.1, 0.1, 0.2, 0.2),
dtype=tf.float32,
scope='ssd_bboxes_encode'):
with tf.name_scope(scope):
target_labels = []
target_localizations = []
target_scores = []
# anchors_layer: (y, x, h, w)
for i, anchors_layer in enumerate(anchors):
with tf.name_scope('bboxes_encode_block_%i' % i):
# (m,m,k),xywh(m,m,4k),(m,m,k)
t_labels, t_loc, t_scores = \
tf_ssd_bboxes_encode_layer(labels, bboxes, anchors_layer,
num_classes, no_annotation_label,
ignore_threshold,
prior_scaling, dtype)
target_labels.append(t_labels)
target_localizations.append(t_loc)
target_scores.append(t_scores)
return target_labels, target_localizations, target_scores

每一层处理是重点(ssd_common.py),从这里我们可以更深刻体会到所有框体长度信息归一化的便捷之处——不同层的框体均可以直接和真实框做运算,毕竟它们都是0~1的相对位置:

# 为了有助理解,m表示该层中心点行列数,k为每个中心点对应的框数,n为图像上的目标数
def tf_ssd_bboxes_encode_layer(labels, # (n,)
bboxes, # (n, 4)
anchors_layer, # y(m, m, 1), x(m, m, 1), h(k,), w(k,)
num_classes,
no_annotation_label,
ignore_threshold=0.5,
prior_scaling=(0.1, 0.1, 0.2, 0.2),
dtype=tf.float32):
"""Encode groundtruth labels and bounding boxes using SSD anchors from
one layer. Arguments:
labels: 1D Tensor(int64) containing groundtruth labels;
bboxes: Nx4 Tensor(float) with bboxes relative coordinates;
anchors_layer: Numpy array with layer anchors;
matching_threshold: Threshold for positive match with groundtruth bboxes;
prior_scaling: Scaling of encoded coordinates. Return:
(target_labels, target_localizations, target_scores): Target Tensors.
"""
# Anchors coordinates and volume.
yref, xref, href, wref = anchors_layer # y(m, m, 1), x(m, m, 1), h(k,), w(k,)
ymin = yref - href / 2. # (m, m, k)
xmin = xref - wref / 2.
ymax = yref + href / 2.
xmax = xref + wref / 2.
vol_anchors = (xmax - xmin) * (ymax - ymin) # 搜索框面积(m, m, k) # Initialize tensors...
# 下面各个Tensor矩阵的shape等于中心点坐标矩阵的shape
shape = (yref.shape[0], yref.shape[1], href.size) # (m, m, k)
feat_labels = tf.zeros(shape, dtype=tf.int64) # (m, m, k)
feat_scores = tf.zeros(shape, dtype=dtype) feat_ymin = tf.zeros(shape, dtype=dtype)
feat_xmin = tf.zeros(shape, dtype=dtype)
feat_ymax = tf.ones(shape, dtype=dtype)
feat_xmax = tf.ones(shape, dtype=dtype) def jaccard_with_anchors(bbox):
"""Compute jaccard score between a box and the anchors.
"""
int_ymin = tf.maximum(ymin, bbox[0]) # (m, m, k)
int_xmin = tf.maximum(xmin, bbox[1])
int_ymax = tf.minimum(ymax, bbox[2])
int_xmax = tf.minimum(xmax, bbox[3])
h = tf.maximum(int_ymax - int_ymin, 0.)
w = tf.maximum(int_xmax - int_xmin, 0.)
# Volumes.
# 处理搜索框和bbox之间的联系
inter_vol = h * w # 交集面积
union_vol = vol_anchors - inter_vol \
+ (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) # 并集面积
jaccard = tf.div(inter_vol, union_vol) # 交集/并集,即IOU
return jaccard # (m, m, k) def condition(i, feat_labels, feat_scores,
feat_ymin, feat_xmin, feat_ymax, feat_xmax):
"""Condition: check label index.
"""
r = tf.less(i, tf.shape(labels))
return r[0] # tf.shape(labels)有维度,所以r有维度 def body(i, feat_labels, feat_scores,
feat_ymin, feat_xmin, feat_ymax, feat_xmax):
"""Body: update feature labels, scores and bboxes.
Follow the original SSD paper for that purpose:
- assign values when jaccard > 0.5;
- only update if beat the score of other bboxes.
"""
# Jaccard score.
label = labels[i] # 当前图片上第i个对象的标签
bbox = bboxes[i] # 当前图片上第i个对象的真实框bbox
jaccard = jaccard_with_anchors(bbox) # 当前对象的bbox和当前层的搜索网格IOU,(m, m, k)
# Mask: check threshold + scores + no annotations + num_classes.
mask = tf.greater(jaccard, feat_scores) # 掩码矩阵,IOU大于历史得分的为True,(m, m, k)
# mask = tf.logical_and(mask, tf.greater(jaccard, matching_threshold))
mask = tf.logical_and(mask, feat_scores > -0.5)
mask = tf.logical_and(mask, label < num_classes) # 不太懂,label应该必定小于类别数
imask = tf.cast(mask, tf.int64) # 整形mask
fmask = tf.cast(mask, dtype) # 浮点型mask # Update values using mask.
# 保证feat_labels存储对应位置得分最大对象标签,feat_scores存储那个得分
# (m, m, k) × 当前类别scalar + (1 - (m, m, k)) × (m, m, k)
# 更新label记录,此时的imask已经保证了True位置当前对像得分高于之前的对象得分,其他位置值不变
feat_labels = imask * label + (1 - imask) * feat_labels
# 更新score记录,mask为True使用本类别IOU,否则不变
feat_scores = tf.where(mask, jaccard, feat_scores) # 下面四个矩阵存储对应label的真实框坐标
# (m, m, k) × 当前框坐标scalar + (1 - (m, m, k)) × (m, m, k)
feat_ymin = fmask * bbox[0] + (1 - fmask) * feat_ymin
feat_xmin = fmask * bbox[1] + (1 - fmask) * feat_xmin
feat_ymax = fmask * bbox[2] + (1 - fmask) * feat_ymax
feat_xmax = fmask * bbox[3] + (1 - fmask) * feat_xmax return [i+1, feat_labels, feat_scores,
feat_ymin, feat_xmin, feat_ymax, feat_xmax]
# Main loop definition.
# 对当前图像上每一个目标进行循环
i = 0
(i,
feat_labels, feat_scores,
feat_ymin, feat_xmin,
feat_ymax, feat_xmax) = tf.while_loop(condition, body,
[i,
feat_labels, feat_scores,
feat_ymin, feat_xmin,
feat_ymax, feat_xmax])
# Transform to center / size.
# 这里的y、x、h、w指的是对应位置所属真实框的相关属性
feat_cy = (feat_ymax + feat_ymin) / 2.
feat_cx = (feat_xmax + feat_xmin) / 2.
feat_h = feat_ymax - feat_ymin
feat_w = feat_xmax - feat_xmin # Encode features.
# prior_scaling: [0.1, 0.1, 0.2, 0.2],放缩意义不明
# ((m, m, k) - (m, m, 1)) / (k,) * 10
# 以搜索网格中心点为参考,真实框中心的偏移,单位长度为网格hw
feat_cy = (feat_cy - yref) / href / prior_scaling[0]
feat_cx = (feat_cx - xref) / wref / prior_scaling[1]
# log((m, m, k) / (m, m, 1)) * 5
# 真实框宽高/搜索网格宽高,取对
feat_h = tf.log(feat_h / href) / prior_scaling[2]
feat_w = tf.log(feat_w / wref) / prior_scaling[3]
# Use SSD ordering: x / y / w / h instead of ours.(m, m, k, 4)
feat_localizations = tf.stack([feat_cx, feat_cy, feat_w, feat_h], axis=-1) # -1会扩维,故有4 return feat_labels, feat_localizations, feat_scores

可以看到(最后几行),feat_localizations用于位置修正记录,其中存储的并不是直接的搜索框和真实框的差,而是按照loss函数所需要的格式进行存储,但是进行prior_scaling处理的意义不明,不过直观来看对loss函数不构成负面影响(损失函数值依旧是搜索框等于真实框最佳)。

二、处理为batch

生成batch数据队列

截止到目前,我们的数据都是对单张图片而言,需要将之整理为batch size的Tensor,不过有点小麻烦,就是我们的数据以list包含Tensor为主,维度扩充需要一点小技巧(tf_utils.py):

def reshape_list(l, shape=None):
"""Reshape list of (list): 1D to 2D or the other way around. Args:
l: List or List of list.
shape: 1D or 2D shape.
Return
Reshaped list.
"""
r = []
if shape is None:
# Flatten everything.
for a in l:
if isinstance(a, (list, tuple)):
r = r + list(a)
else:
r.append(a)
else:
# Reshape to list of list.
i = 0
for s in shape:
if s == 1:
r.append(l[i])
else:
r.append(l[i:i+s])
i += s
return r

这个函数可以将list1:[Tensor11, [Tensor21, Tensor22, ……], [Ten31, Tensor32, ……], ……]和list2:[Tensor1, Tensor2, ……]这样的形式相互转换,需要的就是记录下list1中各子list长度,单个Tensor记为1(train_ssd_network.py):

            batch_shape = [1] + [len(ssd_anchors)] * 3  # (1,f层,f层,f层)

            # Training batches and queue.
r = tf.train.batch( # 图片,中心点类别,真实框坐标,得分
tf_utils.reshape_list([image, gclasses, glocalisations, gscores]),
batch_size=FLAGS.batch_size, # 32
num_threads=FLAGS.num_preprocessing_threads,
capacity=5 * FLAGS.batch_size) b_image, b_gclasses, b_glocalisations, b_gscores = \
tf_utils.reshape_list(r, batch_shape) # Intermediate queueing: unique batch computation pipeline for all
# GPUs running the training.
batch_queue = slim.prefetch_queue.prefetch_queue(
tf_utils.reshape_list([b_image, b_gclasses, b_glocalisations, b_gscores]),
capacity=2 * deploy_config.num_clones)

由于tf.train.batch接收输入格式为[Tensor1, Tensor2, ……],所以要先使用上面函数处理输入,使单张图片的标签数据变化为batch size的标签数据,再将标签数据格式变换回来(实际就是把list1化为list2后给其中每一个Tensor加了一个维度,再变换回list1的格式),最后将batch size的Tensor创建队列,不过没必要这么麻烦,实际上像下面这么做也不会报错,省略了来回折腾Tensor的过程……

            batch_shape = [1] + [len(ssd_anchors)] * 3 # (1,f层,f层,f层)
r = tf.train.batch( # 图片,中心点类别,真实框坐标,得分
tf_utils.reshape_list([image, gclasses, glocalisations, gscores]),
batch_size=FLAGS.batch_size, # 32
num_threads=FLAGS.num_preprocessing_threads,
capacity=5 * FLAGS.batch_size) # Intermediate queueing: unique batch computation pipeline for all
# GPUs running the training.
batch_queue = slim.prefetch_queue.prefetch_queue(
r, # <-----输入格式实际上并不需要调整
capacity=2 * deploy_config.num_clones)

获取batch数据队列

            # Dequeue batch.
b_image, b_gclasses, b_glocalisations, b_gscores = \
tf_utils.reshape_list(batch_queue.dequeue(), batch_shape) # 重整list

出队后整理一下list格式即可,此时获取的数据格式如下(vgg_300为例):

<tf.Tensor 'batch:0' shape=(32, 3, 300, 300) dtype=float32>

[<tf.Tensor 'batch:1' shape=(32, 38, 38, 4) dtype=int64>,
<tf.Tensor 'batch:2' shape=(32, 19, 19, 6) dtype=int64>,
<tf.Tensor 'batch:3' shape=(32, 10, 10, 6) dtype=int64>,
<tf.Tensor 'batch:4' shape=(32, 5, 5, 6) dtype=int64>,
<tf.Tensor 'batch:5' shape=(32, 3, 3, 4) dtype=int64>,
<tf.Tensor 'batch:6' shape=(32, 1, 1, 4) dtype=int64>] [<tf.Tensor 'batch:7' shape=(32, 38, 38, 4, 4) dtype=float32>,
<tf.Tensor 'batch:8' shape=(32, 19, 19, 6, 4) dtype=float32>,
<tf.Tensor 'batch:9' shape=(32, 10, 10, 6, 4) dtype=float32>,
<tf.Tensor 'batch:10' shape=(32, 5, 5, 6, 4) dtype=float32>,
<tf.Tensor 'batch:11' shape=(32, 3, 3, 4, 4) dtype=float32>,
<tf.Tensor 'batch:12' shape=(32, 1, 1, 4, 4) dtype=float32>] [<tf.Tensor 'batch:13' shape=(32, 38, 38, 4) dtype=float32>,
<tf.Tensor 'batch:14' shape=(32, 19, 19, 6) dtype=float32>,
<tf.Tensor 'batch:15' shape=(32, 10, 10, 6) dtype=float32>,
<tf.Tensor 'batch:16' shape=(32, 5, 5, 6) dtype=float32>,
<tf.Tensor 'batch:17' shape=(32, 3, 3, 4) dtype=float32>,
<tf.Tensor 'batch:18' shape=(32, 1, 1, 4) dtype=float32>]

此时的数据格式已经符合loss函数和网络输入要求,运行即可:

            # Construct SSD network.
# 这个实例方法会返回之前定义的函数ssd_arg_scope(允许修改两个参数)
arg_scope = ssd_net.arg_scope(weight_decay=FLAGS.weight_decay,
data_format=DATA_FORMAT)
with slim.arg_scope(arg_scope):
# predictions: (BS, H, W, 4, 21)
# localisations: (BS, H, W, 4, 4)
# logits: (BS, H, W, 4, 21)
predictions, localisations, logits, end_points = \
ssd_net.net(b_image, is_training=True) # Add loss function.
ssd_net.losses(logits, localisations,
b_gclasses, b_glocalisations, b_gscores,
match_threshold=FLAGS.match_threshold, # .5
negative_ratio=FLAGS.negative_ratio, # 3
alpha=FLAGS.loss_alpha, # 1
label_smoothing=FLAGS.label_smoothing) # .0

正向传播函数会获取相关的节点,损失函数则会将函数值添加到loss collection中。

『TensorFlow』SSD源码学习_其六:标签整理的更多相关文章

  1. 『TensorFlow』SSD源码学习_其一:论文及开源项目文档介绍

    一.论文介绍 读论文系列:Object Detection ECCV2016 SSD 一句话概括:SSD就是关于类别的多尺度RPN网络 基本思路: 基础网络后接多层feature map 多层feat ...

  2. 『TensorFlow』SSD源码学习_其五:TFR数据读取&数据预处理

    Fork版本项目地址:SSD 一.TFR数据读取 创建slim.dataset.Dataset对象 在train_ssd_network.py获取数据操作如下,首先需要slim.dataset.Dat ...

  3. 『TensorFlow』SSD源码学习_其四:数据介绍及TFR文件生成

    Fork版本项目地址:SSD 一.数据格式介绍 数据文件夹命名为VOC2012,内部有5个子文件夹,如下, 我们的检测任务中使用JPEGImages文件夹和Annotations文件夹. JPEGIm ...

  4. 『TensorFlow』SSD源码学习_其二:基于VGG的SSD网络前向架构

    Fork版本项目地址:SSD 参考自集智专栏 一.SSD基础 在分类器基础之上想要识别物体,实质就是 用分类器扫描整张图像,定位特征位置 .这里的关键就是用什么算法扫描,比如可以将图片分成若干网格,用 ...

  5. 『TensorFlow』SSD源码学习_其七:损失函数

    Fork版本项目地址:SSD 一.损失函数介绍 SSD损失函数分为两个部分:对应搜索框的位置loss(loc)和类别置信度loss(conf).(搜索框指网络生成的网格) 详细的说明如下: i指代搜索 ...

  6. 『TensorFlow』SSD源码学习_其八:网络训练

    Fork版本项目地址:SSD 作者使用了分布式训练的写法,这使得训练部分代码异常臃肿,我给出了部分注释.我对于多机分布式并不很熟,而且不是重点,所以不过多介绍,简单的给出一点训练中作者的优化手段,包含 ...

  7. 『TensorFlow』SSD源码学习_其三:锚框生成

    Fork版本项目地址:SSD 上一节中我们定义了vgg_300的网络结构,实际使用中还需要匹配SSD另一关键组件:被选取特征层的搜索网格.在项目中,vgg_300网络和网格生成都被统一进一个class ...

  8. nginx源码学习_源码结构

    nginx的优秀除了体现在程序结构以及代码风格上,nginx的源码组织也同样简洁明了,目录结构层次结构清晰,值得我们去学习.nginx的源码目录与nginx的模块化以及功能的划分是紧密结合,这也使得我 ...

  9. spring源码学习之默认标签的解析(一)

    继续spring源码的学习之路,现在越来越觉得这个真的很枯燥的,而且我觉得要是自己来看源码,真的看不下去,不是没有耐心,而是真的没有头绪,我觉得结合着书来看,还是很有必要的,最起码大致的流程是能够捋清 ...

随机推荐

  1. hash进阶:使用字符串hash乱搞的姿势

    前言 此文主要介绍hash的各种乱搞方法,hash入门请参照我之前这篇文章 不好意思hash真的可以为所欲为 在开头先放一下题表(其实就是我题解中的hash题目qwq) 查询子串hash值 必备的入门 ...

  2. WebLogic调用WebService提示Failed to localize、Failed to create WsdlDefinitionFeature

    在本地Tomcat环境下调用WebService正常,但是部署到WebLogic环境中,则提示警告:[Failed to localize] MEX0008.PARSING_MDATA_FAILURE ...

  3. P4306 [JSOI2010]连通数

    思路 要求求每个点能到达的点数就是传递闭包 然后n^3Floyd可做,但是n=2000,然后bitset压位 复杂度\(O(\frac{n^3}{32})\),能过 代码 #include <c ...

  4. Docker Engine SDKs and API 的开发2

    Examples using the Docker Engine SDKs and Docker API After you install Docker, you can install the G ...

  5. sublime text 中文显示异常

    本文链接:sublime text 中文显示异常 http://www.cnblogs.com/daysme/p/7549857.html 前言 sublime text 3143 用了一年时候,最近 ...

  6. 【BZOJ】2331: [SCOI2011]地板

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2331 一眼插头DP... 考虑一个L形的东西,要构成它可以划分为两个阶段,即当前线段是拐了 ...

  7. mime模块响应或设置Node.js的Content-Type头

    转载自:https://itbilu.com/nodejs/core/VJYaAfKrl.html   MIME,即:Multipurpose Internet Mail Extensions,多用途 ...

  8. [c][c++]按位操作

    因为有时候需要大量的标志位来判断当前状态等.使用太多的int,bool等会使得程序不“漂亮” 这时候需要“位”操作来解决 建立一个标志位 unsigned ; 在定义一些宏,如 #define CON ...

  9. 重装win7系统并激活

    备份 大白菜制作启动盘 下载大白菜软件UEFI版(新电脑使用uefi版本,装机版支持的主板多) 选择默认安装 选择默认模式开始制作 下载iso镜像文件,复制到u盘(手动复制) 设置bios       ...

  10. LaTeX 交叉引用系统简介

    目录 摘要 1. 简介 2. 指定标签或标记符 3. 标签的位置 4. 引用的生成和更新 5. 引用相关的警告信息 6. 扩展引用功能的宏包 6.1 varioref宏包 6.2 cleveref宏包 ...