『TensorFlow』SSD源码学习_其六:标签整理
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源码学习_其六:标签整理的更多相关文章
- 『TensorFlow』SSD源码学习_其一:论文及开源项目文档介绍
一.论文介绍 读论文系列:Object Detection ECCV2016 SSD 一句话概括:SSD就是关于类别的多尺度RPN网络 基本思路: 基础网络后接多层feature map 多层feat ...
- 『TensorFlow』SSD源码学习_其五:TFR数据读取&数据预处理
Fork版本项目地址:SSD 一.TFR数据读取 创建slim.dataset.Dataset对象 在train_ssd_network.py获取数据操作如下,首先需要slim.dataset.Dat ...
- 『TensorFlow』SSD源码学习_其四:数据介绍及TFR文件生成
Fork版本项目地址:SSD 一.数据格式介绍 数据文件夹命名为VOC2012,内部有5个子文件夹,如下, 我们的检测任务中使用JPEGImages文件夹和Annotations文件夹. JPEGIm ...
- 『TensorFlow』SSD源码学习_其二:基于VGG的SSD网络前向架构
Fork版本项目地址:SSD 参考自集智专栏 一.SSD基础 在分类器基础之上想要识别物体,实质就是 用分类器扫描整张图像,定位特征位置 .这里的关键就是用什么算法扫描,比如可以将图片分成若干网格,用 ...
- 『TensorFlow』SSD源码学习_其七:损失函数
Fork版本项目地址:SSD 一.损失函数介绍 SSD损失函数分为两个部分:对应搜索框的位置loss(loc)和类别置信度loss(conf).(搜索框指网络生成的网格) 详细的说明如下: i指代搜索 ...
- 『TensorFlow』SSD源码学习_其八:网络训练
Fork版本项目地址:SSD 作者使用了分布式训练的写法,这使得训练部分代码异常臃肿,我给出了部分注释.我对于多机分布式并不很熟,而且不是重点,所以不过多介绍,简单的给出一点训练中作者的优化手段,包含 ...
- 『TensorFlow』SSD源码学习_其三:锚框生成
Fork版本项目地址:SSD 上一节中我们定义了vgg_300的网络结构,实际使用中还需要匹配SSD另一关键组件:被选取特征层的搜索网格.在项目中,vgg_300网络和网格生成都被统一进一个class ...
- nginx源码学习_源码结构
nginx的优秀除了体现在程序结构以及代码风格上,nginx的源码组织也同样简洁明了,目录结构层次结构清晰,值得我们去学习.nginx的源码目录与nginx的模块化以及功能的划分是紧密结合,这也使得我 ...
- spring源码学习之默认标签的解析(一)
继续spring源码的学习之路,现在越来越觉得这个真的很枯燥的,而且我觉得要是自己来看源码,真的看不下去,不是没有耐心,而是真的没有头绪,我觉得结合着书来看,还是很有必要的,最起码大致的流程是能够捋清 ...
随机推荐
- P4306 [JSOI2010]连通数
思路 要求求每个点能到达的点数就是传递闭包 然后n^3Floyd可做,但是n=2000,然后bitset压位 复杂度\(O(\frac{n^3}{32})\),能过 代码 #include <c ...
- HDU 1465 不容易系列之一
扯淡 貌似有傻逼的做法XD 话说我没开long long,忘读入n,忘了清零ans WA了三遍是什么操作啊 傻了傻了 思路 显然是一个错排问题啊XD 但是我们不套公式,我们用一发二项式反演 二项式反演 ...
- 配置和运行 MatchNet CVPR 2015 MatchNet: Unifying Feature and Metric Learning for Patch-Based Matching
配置和运行 MatchNet CVPR 2015 GitHub: https://github.com/hanxf/matchnet 最近一个同学在配置,测试这个网络,但是总是遇到各种问题. 我也尝试 ...
- Shell: nohup守护进程化
如果想在终端会话中启动shell脚本,然后让脚本一直以后台模式运行,直到其完成,即使你退出了终端会话,可以使用nohup命令实现.感觉nohup就是将一个进程初始化为一个守护进程. nohup命令运行 ...
- spring applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...
- js点击显示隐藏
这个栗子…… 可以不吃,先预设一个变量表示div的状态,例子中0是显示的,一开始是隐藏的.当点击时判断状态是显示0的还是隐藏1的:如果是显示的就把div隐藏,再把变量改变为1.再次点击时把会判断到变量 ...
- [从零开始搭网站三]CentOS配置JDK
点击下面连接查看从零开始搭网站全系列 从零开始搭网站 上一章我介绍了,如何不用每次都输密码连接服务器.那么这一章终于要开始服务器的开发环境配置了. 1:先输入以下代码来检验有没有已经安装的CDK: r ...
- c# 通过反射输出成员变量以及成员变量的值
/*** @Author rexzhao* 工具类 仅限于* public variable*/using System.Collections;using System.Collections.Ge ...
- ImgNoGoodWindow
using System;using System.Collections.Generic;using System.Linq;using System.Text;using UnityEditor; ...
- Systemd初始化进程/RHEL 6系统中System V init命令与RHEL 7系统中systemctl命令的对比
Linux操作系统的开机过程是这样的,即从BIOS开始,然后进入Boot Loader,再加载系统内核,然后内核进行初始化,最后启动初始化进程.初始化进程作为Linux系统的第一个进程,它需要完成Li ...