本论文主要讲解了一个通过低帧率生成高帧率的视频的算法,训练数据就是高帧率的视频。
原文在这里
这里我结合代码讲解一下我对整个训练过程,网络和里面的公式的一些理解。
参考代码,基于tensorflow
公式讲解
要想合成0帧和1帧之间的任意帧,这里的做法是分别求到t到0和1的光流,然后用光流复原t帧图像,如公式1所示。
公式1,0和1之间的中间任意第t帧的生成方式,用I0和Ft0来生成t,用I1和Ft1来生成t,再将两个生成图加权求到It。这里的alpha权值不是一个值,这里权值的计算要考虑时间一致性和遮挡问题。所谓时间一致性,就是t离哪一帧近就更像哪一帧;遮挡问题,文章认为t中的像素,0和1中至少有一帧中会出现。所以设计了以下公式
公式2就是将公式一种的alpha具体化的效果,这里(1-t)和(t)是考虑的时间一致性;Vt0和Vt1是所谓的可视性map用来考虑遮挡问题,Vt0+Vt1=1,对于1中不可见的像素,就依靠0帧,那么Vt0接近1,如果都可见,Vt0和Vt1就55开。再乘上一个归一化因子1/Z。得到最后结果。所以实际用的时候是用公式2,在下面代码中也有说明。
使用公式2能求到t帧,但问题是我们还没有求到Ft-0和Ft-1两个光流。本文的做法是,先求F10和F01两个光流图
这里用公式3求Ft-1的光流,这里前提条件是光流场局部平滑,应该是指局部区域的光流是一样的意思,不然感觉公式3推导有问题。
通过公式三,将三的两个公式加权求和得到最终的Ft-1,同理求到Ft-0
这样就通过F1-0和F0-1求到了Ft-1和Ft-1。
网络构建
整个网络的构成就是如Figure4所示,前后两个网络用的是U-NET(具体参见本文),是个全卷积网络。
flow computation输入是I0和I1两帧图像,输出是F0-1和F1-0两个光流图,按道理然后我们通过公式4就可以算出Ft-1和Ft-0,但是文中说由于有些区域的不平滑所以这样求到的结果不好,所以用第二个网络来优化一下。
第二个网络的输入是,I1和I0两帧图像,通过网络1算出来的Ft-1和Ft-0,通过I1和Ft-1算出来的It,通过I0和Ft-0算出来的It,一共6个项组合输入,输出的是Ft-1和Ft-0的修正值,和Vt-0,三项。
Vt-0是t对于0的可见性map,这里要求Vt-0和Vt-1要和为1。
Ft-1和Ft-0的修正值加上前面算到的FT-1和Ft-0就是最后的Ft-1和Ft-0。这样要求得值都求到了。最后用公式2求到最后的T帧值It.
损失函数
损失函数由4项损失加权求得
重构损失计算的是gt的It和预测的It的每个像素的L1损失值
感知损失方法是,将两个IT分别输入VGG网络中,取中间的特征层计算两者的L2损失,重构的好应该是要相同的
wrap有4项,I0和用I1,F0-1重构的I0之间的l1损失,I1和用I0,F1-0重构的I1之间的l1损失,IT和用I0,FT-0重构的It之间的L1损失,IT和用I1,FT-1重构的It之间的L1损失,这里Ft-1和Ft-0是用第一个网络输出的计算的,不是用的最后的修正值
平滑损失,就是要求所求的F0-1和F1-0的相邻像素的光流值要一样,求了他们的一阶导数。
接下来是相关代码及注释
代码讲解
# SloMo vanila model 这段代码包含了构建网络的全过程
def SloMo_model(frame0, frame1, frameT, FLAGS, reuse=False, timestamp=0.5):
# Define the container of the parameter
if FLAGS is None:
raise ValueError('No FLAGS is provided for generator')
Network = collections.namedtuple('Network', 'total_loss, reconstruction_loss, perceptual_loss, \
wrapping_loss, smoothness_loss, pred_frameT \
Ft0, Ft1, Vt0,\
grads_and_vars, train, global_step, learning_rate')
with tf.variable_scope("SloMo_model", reuse=reuse):
with tf.variable_scope("flow_computation"):
flow_comp_input = tf.concat([frame0, frame1], axis=3)
flow_comp_out, flow_comp_enc_out = UNet(flow_comp_input,
output_channels=4, # 2 channel for each flow
first_kernel=FLAGS.first_kernel,
second_kernel=FLAGS.second_kernel) #使用UNET构建第一个光流生成网络,有两个输出
#第一个输出为两张图的双向光流,4层,每个光流两层;
#第二个输出是网络中间的的编码层
flow_comp_out = lrelu(flow_comp_out)
F01, F10 = flow_comp_out[:, :, :, :2], flow_comp_out[:, :, :, 2:] #将上面的第一个输出分开成两个光流图
print("Flow Computation Graph Initialized !!!!!! ")
with tf.variable_scope("flow_interpolation"):
Fdasht0 = (-1 * (1 - timestamp) * timestamp * F01) + (timestamp * timestamp * F10)
Fdasht1 = ((1 - timestamp) * (1 - timestamp) * F01) - (timestamp * (1 - timestamp) * F10)
#通过两张光流图计算t时刻到0和1时刻的光流图
flow_interp_input = tf.concat([frame0, frame1,
flow_back_wrap(frame1, Fdasht1),
flow_back_wrap(frame0, Fdasht0),
Fdasht0, Fdasht1], axis=3)
#构建第二个网络的输入,为0,1两帧图像,用flow_back_wrap计算合成得到的t时刻的帧,分别用Fasht0和Fdasht1两个光流得到,还有Fdasht0和Fdasht1两个光流图,一共6个张图组成的输入
flow_interp_output, _ = UNet(flow_interp_input,
output_channels=5, # 2 channels for each flow, 1 visibilty map.
decoder_extra_input=flow_comp_enc_out,
first_kernel=3,
second_kernel=3)
#构建第二个网络,输出5个通道,额外的输入是在中间加进去的,加入网络的解码部分
deltaFt0, deltaFt1, Vt0 = flow_interp_output[:, :, :, :2], flow_interp_output[:, :, :, 2:4], \
flow_interp_output[:, :, :, 4:5]
#分解网络的输出,为两个光流输出图,是对t到1和0 的两个光流的的修正,还有一个可见map通道
deltaFt0 = lrelu(deltaFt0)
deltaFt1 = lrelu(deltaFt1)
Vt0 = tf.sigmoid(Vt0)
Vt0 = tf.tile(Vt0, [1, 1, 1, 3]) # Copy same in all three channels
Vt1 = 1 - Vt0
Ft0, Ft1 = Fdasht0 + deltaFt0, Fdasht1 + deltaFt1 #用网络2的输出修正t到1和0的两个光流,作为最后的光流输出
normalization_factor = 1 / ((1 - timestamp) * Vt0 + timestamp * Vt1 + FLAGS.epsilon)
pred_frameT = tf.multiply((1 - timestamp) * Vt0, flow_back_wrap(frame0, Ft0)) + \
tf.multiply(timestamp * Vt1, flow_back_wrap(frame1, Ft1))
pred_frameT = tf.multiply(normalization_factor, pred_frameT)
#这里使用公式2来计算最终的合成图像,考虑了可见性map
print("Flow Interpolation Graph Initialized !!!!!! ")
rec_loss = reconstruction_loss(pred_frameT, frameT) #重构损失,就是计算gt的T帧和生成的T帧每个像素的l1损失
percep_loss = perceptual_loss(pred_frameT, frameT, layers=FLAGS.perceptual_mode)
#感知损失,将gt帧和生成帧分别输入到vgg网络中用中间层的特征来计算l2损失
wrap_loss = wrapping_loss(frame0, frame1, frameT, F01, F10, Fdasht0, Fdasht1)
#wrap损失,分别计算 用frame1和 F01生成frame0帧,用frame0和 F10生成frame1帧,用frame0和Fdasht0生成frameT帧
#用frame1和Fdasht1生成frameT帧,和他们的真实值之间的l1损失
smooth_loss = smoothness_loss(F01, F10)
#平滑损失,就是要求F01和F10两个光流图尽量平滑,相邻像素之间的光流要相等
total_loss = FLAGS.reconstruction_scaling * rec_loss + \
FLAGS.perceptual_scaling * percep_loss + \
FLAGS.wrapping_scaling * wrap_loss + \
FLAGS.smoothness_scaling * smooth_loss
#将损失加权求和
#以上就是整个的训练模型的构建,以下就是网络训练方式的构建,都是标准写法
with tf.variable_scope("global_step_and_learning_rate", reuse=reuse):
global_step = tf.contrib.framework.get_or_create_global_step()
learning_rate = tf.train.exponential_decay(FLAGS.learning_rate, global_step, FLAGS.decay_step,
FLAGS.decay_rate,
staircase=FLAGS.stair)
incr_global_step = tf.assign(global_step, global_step + 1)
with tf.variable_scope("optimizer", reuse=reuse):
with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)):
tvars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope='SloMo_model')
optimizer = tf.train.AdamOptimizer(learning_rate, beta1=FLAGS.beta)
grads_and_vars = optimizer.compute_gradients(total_loss, tvars)
train_op = optimizer.apply_gradients(grads_and_vars)
return Network(
total_loss=total_loss,
reconstruction_loss=rec_loss,
perceptual_loss=percep_loss,
wrapping_loss=wrap_loss,
smoothness_loss=smooth_loss,
pred_frameT=pred_frameT,
Ft0=Ft0,
Ft1=Ft1,
Vt0=Vt0,
grads_and_vars=grads_and_vars,
train=tf.group(total_loss, incr_global_step, train_op),
global_step=global_step,
learning_rate=learning_rate
)
这里是几种损失的实现
Ipred = tf.image.convert_image_dtype(Ipred, dtype=tf.uint8)
Iref = tf.image.convert_image_dtype(Iref, dtype=tf.uint8)
Ipred = tf.cast(Ipred, dtype=tf.float32)
Iref = tf.cast(Iref, dtype=tf.float32)
# tf.reduce_mean(tf.norm(tf.math.subtract(Ipred, Iref), ord=1, axis=[3]))
return l1_loss(Ipred, Iref)
def perceptual_loss(Ipred, Iref, layers="VGG54"): #用了vgg54层的特征
# Note name scope is ignored in varibale naming (scope)
with tf.name_scope("vgg19_Ipred"):
Ipred_features = VGG19_slim(Ipred, layers, reuse=tf.AUTO_REUSE)
with tf.name_scope("vgg19_Iref"):
Iref_features = VGG19_slim(Iref, layers, reuse=tf.AUTO_REUSE)
return l2_loss(Ipred_features, Iref_features)
def wrapping_loss(frame0, frame1, frameT, F01, F10, Fdasht0, Fdasht1):
return l1_loss(frame0, flow_back_wrap(frame1, F01)) + \
l1_loss(frame1, flow_back_wrap(frame0, F10)) + \
l1_loss(frameT, flow_back_wrap(frame0, Fdasht0)) + \
l1_loss(frameT, flow_back_wrap(frame1, Fdasht1))
def smoothness_loss(F01, F10):#计算delta,将图像平移一个像素,作差
deltaF01 = tf.reduce_mean(tf.abs(F01[:, 1:, :, :] - F01[:, :-1, :, :])) + tf.reduce_mean(
tf.abs(F01[:, :, 1:, :] - F01[:, :, :-1, :]))
deltaF10 = tf.reduce_mean(tf.abs(F10[:, 1:, :, :] - F10[:, :-1, :, :])) + tf.reduce_mean(
tf.abs(F10[:, :, 1:, :] - F10[:, :, :-1, :]))
return 0.5 * (deltaF01 + deltaF10)
以上是个人阅读论文笔记,如有错误,希望大家批评指正,谢谢