Super SloMo: High Quality Estimation of Multiple Intermediate Frames for Video Interpolation论文阅读

本论文主要讲解了一个通过低帧率生成高帧率的视频的算法,训练数据就是高帧率的视频。
原文在这里
这里我结合代码讲解一下我对整个训练过程,网络和里面的公式的一些理解。
参考代码,基于tensorflow

公式讲解

要想合成0帧和1帧之间的任意帧,这里的做法是分别求到t到0和1的光流,然后用光流复原t帧图像,如公式1所示。


公式1

公式1,0和1之间的中间任意第t帧的生成方式,用I0和Ft0来生成t,用I1和Ft1来生成t,再将两个生成图加权求到It。这里的alpha权值不是一个值,这里权值的计算要考虑时间一致性和遮挡问题。所谓时间一致性,就是t离哪一帧近就更像哪一帧;遮挡问题,文章认为t中的像素,0和1中至少有一帧中会出现。所以设计了以下公式


公式2

公式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

这里用公式3求Ft-1的光流,这里前提条件是光流场局部平滑,应该是指局部区域的光流是一样的意思,不然感觉公式3推导有问题。


公式4

通过公式三,将三的两个公式加权求和得到最终的Ft-1,同理求到Ft-0
这样就通过F1-0和F0-1求到了Ft-1和Ft-1。

网络构建

image.png

整个网络的构成就是如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损失

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)

以上是个人阅读论文笔记,如有错误,希望大家批评指正,谢谢

你可能感兴趣的:(Super SloMo: High Quality Estimation of Multiple Intermediate Frames for Video Interpolation论文阅读)