视觉跟踪:MDnet
应用需注明原创!
深度学习在2015年中左右基本已经占据了计算机视觉领域中大部分分支,如图像分类、物体检测等等,但迟迟没有视觉跟踪工作公布,2015年底便出现了一篇叫MDNet的论文,致力于用神经网络解决视觉跟踪,它同时也是2015年VOT的冠军。
先图再理论:
离线学习

frame1

frame2

在线学习:
frame by K+1 fine turn

策略:
1、第一帧训练。
2、打分
在上一帧目标区域附近采样大量的样本,然后用这个分类器打分(当我们找到最像目标的样本时,我们便认为这一帧的目标就是这个样本)
3、在线更新MDnet:消除跟踪过程的累计误差
在跟踪了一段时间之后,当前的分类器便不再那么有效了。
此时可能会发现所有的样本都不再“像”。所以我们不得不需要在跟踪中动态的更新这个分类器,即在线更新MDNet。
4、更新策略:
a):MDNet采取了短期及长期的在线更新策略。在跟踪的过程中,文中算法会保存历史跟踪到的目标作为正样本。长期特征对应历史中最近的100个样本,然后固定时间间隔做一次网络的更新,比如程序中设置为每8帧更新一次;
b):短期特征对应最近的20个样本。这里正样本和负样本都是根据当前分类器的打分得出的,比如分数较高的认为是正样本,分数偏低的认为是负样本。
随着长期和短期的网络更新,MDNet便实时的根据视频的内容自动适应跟踪目标的特征,最终生成了稳定的跟踪结果。
卷积神经网络(CNN)有一些基本的问题:
1 数据量的问题
CNN在图像分类上取得巨大成功,一个必要的条件是拥有了数以百万计的已标注训练数据集ImageNet. 而对于视觉跟踪而言,却一直缺乏类似数据量的数据集。如果只使用视频中第一帧给定的Ground Truth做训练数据,毫无疑问会非常容易产生过拟合。

(图像摘自棒子论文)
作者指出与图像分类任务有区别另外一个比较容易想到的方案是利用现有已训练好的图像分类的网络和视觉跟踪数据做Fine-tune。这个方案并不合理,因为图像分类和视觉跟踪在问题本身设置上就有本质不同。所以,性能也一直不好。
2 图像歧义问题
对于不同场景的视频数据,它们的前背景可能又有着冲突或歧义(如视频A中的跟踪对象反而是视频B中的背景),所以直接用有限的视频数据集(如VOT)来训练一个统一的CNN一直非常困难。

MDNet的网络结构及离线训练
1 网络结构
MDNet把视觉跟踪的特征分析为两个部分,一个是所有视频的共性(Shared Features),另一个是不同视频的个性(Domain-specific Features)。而MDNet就是需要联合地训练一个网络,从而使得这个网络拥有着提取共性和个性特征的能力。为了这个目标,MDNet的结构设计如下:

它主要分为共享的和特定的层:
共享层,用于提取公共特性。
在网络层前的MDNet共享设计,在帧图像数据中作为数据,然后经过三层卷积(conv1 ~ 3)两个全连接层(fc4 ~ 5),一个512维特征向量的输出。这部分的卷积层与vggm - m网络中的卷积层相同,只有特征图的大小进行了调整。
用于提取单个视频个性特征的特定层(Domain-specific Layers)
共享层的输出接着会连接K个分支,每个分支都连接一个全连接层fc6. 这里K为训练数据集中视频的个数,所以每一个分支对应了一个特定的视频场景。MDNet的作者希望通过这些特定层,可以提取出单个视频的个性特征,而避免图像歧义的影响。
2 离线训练
在构建了这样一种网络结构之后,作者便可以开始训练了。对于K个视频,作者每次只输入其中一个视频的图像帧,以训练其对应的某一个分支。在经过大量的迭代之后,由于每次训练共享层都在反复更新,而特定层只有在对应的视频数据被加载时才被更新,可以想象最终共享层便可以提取所有视频中的共性而把个性部分留在了特定层中.
MDNet的在线跟踪
在离线训练好MDNet之后,还不能直接运用在新的视频上,因为它并不具备新视频任何个性的特征信息。实际上,它在面对新的视频时会做一系列的自适应措施。
根据第一帧的Ground Truth做fine-tune
视觉跟踪任务中,第一帧会提供被跟踪对象的位置信息。而MDNet便会利用第一帧的目标信息进一步训练现有的网络。它首先会初始化一个全新的fc6层,然后一并更新fc4~6的权重数值,而保持 conv1~3的权重不变。

短期及长期的在线更新
第一帧训练好的网络便可以作为分类器做接下来的跟踪了。这个过程会比较简单,即在上一帧目标区域附近采样大量的样本,然后用这个分类器打分。当我们找到最像目标的样本时,我们便认为这一帧的目标就是这个样本。
然而,这样一个简单的思路在每一帧跟踪的过程中,无可避免的会引入累计误差。这样一来,在跟踪了一段时间之后,当前的分类器便不再那么有效了。此时可能会发现所有的样本都不再“像”。所以我们不得不需要在跟踪中动态的更新这个分类器,即在线更新MDNet。
MDNet采取了短期及长期的在线更新策略。在跟踪的过程中,文中算法会保存历史跟踪到的目标作为正样本。长期特征对应历史中最近的100个样本,然后固定时间间隔做一次网络的更新,比如程序中设置为每8帧更新一次;短期特征对应最近的20个样本。这里正样本和负样本都是根据当前分类器的打分得出的,比如分数较高的认为是正样本,分数偏低的认为是负样本。
随着长期和短期的网络更新,MDNet便实时的根据视频的内容自动适应跟踪目标的特征,最终生成了稳定的跟踪结果。
MDNet的跟踪结果评估与结论
至于MDNet的结果,原论文中给出了比较详细的比较,在OTB和VOT2014数据集上统统秒杀之前的方法。如下的曲线无一证明了这一点:
OBT result:

VOT2014 dataset:

总结:
巧妙的利用了CNN特征,把它分为共性特征和个性特征,对应地设计出的神经网络也反映了跟踪任务的特点;
在线更新模型,使得累积误差不会太大,可以适应场景变化;
另外一些小的trick的使用,虽然本文没有详细介绍,但对最终结果也有了比较好的作用,如难负样本挖掘(Hard Negative Mining)和外框拟合(Bounding Box Regression)。
代码:
net:
# conv1
with tf.variable_scope('conv1') as scope:
kernel = _variable_init_from_constant(
'weights',
shape=[7, 7, 3, 96],
val=conv1,
wd=5e-5,
)
# VALID means no padding
conv = tf.nn.conv2d(images, kernel, [1, 2, 2, 1], padding='VALID')
biases = _variable_on_cpu('biases', [96], tf.constant_initializer(0.0))
pre_activation = tf.nn.bias_add(conv, biases)
conv1 = tf.nn.relu(pre_activation, name=scope.name)
_activation_summary(conv1) norm1 = tf.nn.lrn(conv1, 5, bias=2, alpha=1e-4, beta=0.75, name='norm1')
pool1 = tf.nn.max_pool(norm1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding='VALID', name='pool1') # conv2
with tf.variable_scope('conv2') as scope:
kernel = _variable_init_from_constant(
'weights',
shape=[5, 5, 96, 256],
val=conv2,
wd=5e-5,
)
conv = tf.nn.conv2d(pool1, kernel, [1, 2, 2, 1], padding='VALID')
biases = _variable_on_cpu('biases', [256], tf.constant_initializer(0.0))
pre_activation = tf.nn.bias_add(conv, biases)
conv2 = tf.nn.relu(pre_activation, name=scope.name)
_activation_summary(conv2) norm2 = tf.nn.lrn(conv2, 5, bias=2, alpha=1e-4, beta=0.75, name='norm2')
pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding='VALID', name='pool2') # conv3
with tf.variable_scope('conv3') as scope:
kernel = _variable_init_from_constant(
'weights',
shape=[3, 3, 256, 512],
val=conv3,
wd=5e-5,
)
conv = tf.nn.conv2d(pool2, kernel, strides=[1, 1, 1, 1], padding='VALID')
biases = _variable_on_cpu('biases', [512], tf.constant_initializer(0.0))
pre_activation = tf.nn.bias_add(conv, biases)
conv3 = tf.nn.relu(pre_activation, name=scope.name)
_activation_summary(conv3) # fc4
with tf.variable_scope('fc4') as scope:
# reshape dim: 3 * 3 * 512 = 4608
reshape = tf.reshape(conv3, [-1, 4608])
dim = reshape.get_shape()[1].value
weights = _variable_with_weight_decay('weights',
shape=[dim, 512],
stddev=1e-2,
wd=5e-4)
biases = _variable_on_cpu('biases', [512], tf.constant_initializer(0.0))
fc4 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)
_activation_summary(fc4)
drop4 = tf.nn.dropout(fc4, 0.5, name='drop4') # fc5
with tf.variable_scope('fc5') as scope:
weights = _variable_with_weight_decay('weights',
shape=[512, 512],
stddev=1e-2,
wd=5e-4)
biases = _variable_on_cpu('biases', [512], tf.constant_initializer(0.1))
fc5 = tf.nn.relu(tf.matmul(drop4, weights) + biases, name=scope.name)
_activation_summary(fc5)
drop5 = tf.nn.dropout(fc5, 0.5, name='drop5') # fc6
with tf.variable_scope('fc6') as scope:
if K is None:
# only one branches for online tracking
K = 1
k = 0 weights = _variable_with_weight_decay('weights',
shape=[512, 2 * K],
stddev=1e-2,
wd=5e-4) # K branches for training
biases = _variable_on_cpu('biases', [2 * K], tf.constant_initializer(0))
branch_weight = tf.slice(weights, [0, 2 * k], [512, 2])
branch_biases = tf.slice(biases, [2 * k], [2]) fc6 = tf.nn.bias_add(tf.matmul(drop5, branch_weight), branch_biases, name=scope.name)
#logits = tf.nn.softmax(fc6, name='logits')
_activation_summary(fc6) return fc6 def loss(logits, labels):
"""Add l2loss to all the traninalbe varibles"""
labels = tf.cast(labels, tf.int64)
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=labels, logits=logits, name='cross_entropy_per_example'
)
cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
tf.add_to_collection('losses', cross_entropy_mean) return tf.add_n(tf.get_collection('losses'), name='total_loss')
online fc4,fc5,fc6:
# Add new fully connected layers for online training def init_networks(sess):
"""Load the pre-trained networks, then replace the fc6"""
fc4_weight, fc4_biase, fc5_weight, fc5_biase = load_model(sess)
conv3_feature = tf.get_default_graph().get_tensor_by_name('conv3/conv3:0')
image_tensor = tf.get_default_graph().get_tensor_by_name('images:0')
# Add new fully connected layers for online training
with tf.variable_scope('input'):
conv3_input = tf.placeholder(
tf.float32, shape=[None, 3, 3, 512], name='conv3_input'
)
label_input = tf.placeholder(tf.int32, name='label') with tf.variable_scope('online_fc4'):
reshaped = tf.reshape(conv3_input, [-1, 4608])
weights = networks._variable_init_from_constant('weights',
shape=[4608, 512],
val=fc4_weight,
wd=5e-4)
biases = networks._variable_on_cpu('biases', shape=[512], initializer=tf.constant_initializer(fc4_biase))
online_fc4 = tf.nn.relu(tf.matmul(reshaped, weights) + biases)
drop4 = tf.nn.dropout(online_fc4, 0.5) with tf.variable_scope('online_fc5'):
weights = networks._variable_init_from_constant('weights',
shape=[512, 512],
val=fc5_weight,
wd=5e-4)
biases = networks._variable_on_cpu('biases', shape=[512], initializer=tf.constant_initializer(fc5_biase))
online_fc5 = tf.nn.relu(tf.matmul(drop4, weights) + biases)
drop5 = tf.nn.dropout(online_fc5, 0.5) with tf.variable_scope('online_fc6'):
weights = networks._variable_with_weight_decay('weights',
shape=[512, 2],
stddev=1e-2,
wd=5e-4) biases = networks._variable_on_cpu('biases', shape=[2], initializer=tf.constant_initializer(0))
online_fc6 = tf.nn.relu(tf.matmul(drop5, weights) + biases)
logits = tf.nn.softmax(online_fc6, name='online_logits') with tf.variable_scope('online_cross_entropy'):
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=label_input, logits=logits
)
with tf.name_scope('total'):
cross_entropy_mean = tf.reduce_mean(cross_entropy) with tf.variable_scope('train'):
opt = tf.train.AdamOptimizer(0.0001) grads = opt.compute_gradients(cross_entropy_mean)
for i in range(len(grads)):
grad, var = grads[i]
if var.name.find('online_fc6') != -1:
grads[i] = (grad * 10, var)
apply_grad_op = opt.apply_gradients(grads) return image_tensor, conv3_feature, conv3_input, label_input, logits, apply_grad_op, cross_entropy_mean
视觉跟踪:MDnet的更多相关文章
- 时空上下文视觉跟踪(STC)算法的解读与代码复现(转)
时空上下文视觉跟踪(STC)算法的解读与代码复现 zouxy09@qq.com http://blog.csdn.net/zouxy09 本博文主要是关注一篇视觉跟踪的论文.这篇论文是Kaihua Z ...
- TLD视觉跟踪算法(转)
源:TLD视觉跟踪算法 TLD算法好牛逼一个,这里有个视频,是作者展示算法的效果,http://www.56.com/u83/v_NTk3Mzc1NTI.html.下面这个csdn博客里有人做的相关总 ...
- 关于视觉跟踪中评价标准的相关记录(The Evaluation of Visual Tracking Results on OTB-100 Dataset)
关于视觉跟踪中评价标准的相关记录(The Evaluation of Visual Tracking Results on OTB-100 Dataset) 2018-01-22 21:49:17 ...
- DIY一个基于树莓派和Python的无人机视觉跟踪系统
DIY一个基于树莓派和Python的无人机视觉跟踪系统 无人机通过图传将航拍到的图像存储并实时传送回地面站差点儿已经是标配.假设想来点高级的--在无人机上直接处理拍摄的图像并实现自己主动控制要怎么实现 ...
- TLD视觉跟踪算法
TLD算法好牛逼一个,这里有个视频,是作者展示算法的效果,http://www.56.com/u83/v_NTk3Mzc1NTI.html.下面这个csdn博客里有人做的相关总结,感觉挺好的,收藏了! ...
- 时空上下文视觉跟踪(STC)算法
论文原文以及Matlab代码下载 算法概述 而STC跟踪算法基于贝叶斯框架,根据跟踪目标与周围区域形成的的时空关系,在图像低阶特征上(如图像灰度和位置)对目标与附近区域进行了统计关系建模.通过计算置信 ...
- paper 140:TLD视觉跟踪算法(超棒)
我是看了这样的一个视频:http://www.56.com/u83/v_NTk3Mzc1NTI.html 然后在准备针对TLD视觉跟踪算法来个小的总结. 以下博文转自:http://blog.csdn ...
- ICCV2021 | 用于视觉跟踪的学习时空型transformer
前言 本文介绍了一个端到端的用于视觉跟踪的transformer模型,它能够捕获视频序列中空间和时间信息的全局特征依赖关系.在五个具有挑战性的短期和长期基准上实现了SOTA性能,具有实时性,比 ...
- 时空上下文视觉跟踪(STC)
论文的关键点是对时空上下文(Spatio-Temporal Context)信息的利用.主要思想是通过贝叶斯框架对要跟踪的目标和它的局部上下文区域的时空关系进行建模,得到目标和其周围区域低级特征的统计 ...
随机推荐
- Mysql 查询时间段是否可用,查询时间段是否有交集
最近遇到 类似, 会议室预订的模型, 基本上 是 会议室 + 时间段来检测是否被占用. 其实思路比较简单 , 一开始的思路是 去查询 自己选择的时间段 与数据库已经存在的时间段匹配 是否 可用, ...
- Linux下将.Asp Core 部署到 Docker容器中
我们来部署一个简单的例子: 将一个简单的.Aps Core项目部署到Docker容器中并被外网访问 说明: 下面的步骤都是建立在宿主服务器系统已经安装配置过Docker容器,安装Docker相对比较简 ...
- python学习-35 文件处理
1.简单的打开文件 f=open('test.txt',encoding='utf-8') # 打开了名字为test.txt的文件里的内容 data=f.read() # 读取里面的内容 print( ...
- Python之路【第三十篇】:django 模型层-多表关系
多表操作 文件为 ----> orm2 数据库表关系之关联字段与外键约束 一对多Book id title price publish email addr 1 php 100 人民出版社 1 ...
- 转!!DBCP2 配置详解说明
转自:https://www.cnblogs.com/diyunpeng/p/6980098.html 由于commons-dbcp所用的连接池出现版本升级,因此commons-dbcp2中的数据库池 ...
- vscode入门使用教程(页面调试)
初次使用vscode时各种不适应,所有需要用到的功能貌似都需要单独安装插件才能用.这让很多初次使用vscode的朋友有点无所适从. 下面本人就带各位朋友学习下如何使用vscode来进行最基本的工作—— ...
- spark-sql使用笔记
如何使用hive的udf 可以使用spark-sql --jars /opt/hive/udf.jar,指定udf的路径 还可以在spark-default.conf里指定spark.jars /op ...
- sdcard不可执行.
Possibly you placed it on your sdcard -- which is mounted with the noexec flag. You either need to m ...
- 学习vue生命周期
首先,每个Vue实例在被创建之前都要经过一系列的初始化过程,这个过程就是vue的生命周期.首先看一张图吧~这是官方文档上的图片相信大家一定都会很熟悉: 可以看到在vue一整个的生命周期中会有很多钩子函 ...
- 内核加载错误module license
出现如下错误: module_name: Unknown symbol "symbol_name" tail /var/log/messages查看具体错误 出现如下错误: Unk ...