第二部分来看一下模型部分
论文解析:https://blog.csdn.net/bofu_sun/article/details/89206531
一、调用库文件和构建元组
1.使用collections.namedtuple构建命名元组,这里的元组包括编码器、图片高度、图片宽度、batchsize、线程数、每次的数量、是否生成立体图、模式、是否使用反卷积神经网络、图片损失函数阿尔法值、梯度损失权重、学习率损失权重等key。
from __future__ import absolute_import, division, print_function
from collections import namedtuple
import numpy as np
import tensorflow as tf
import tensorflow.contrib.slim as slim
from bilinear_sampler import *
monodepth_parameters = namedtuple('parameters',
'encoder, '
'height, width, '
'batch_size, '
'num_threads, '
'num_epochs, '
'do_stereo, '
'wrap_mode, '
'use_deconv, '
'alpha_image_loss, '
'disp_gradient_loss_weight, '
'lr_loss_weight, '
'full_summary')
二、MonodepthModel模型类
1.初始化函数
class MonodepthModel(object):
"""monodepth model"""
def __init__(self, params, mode, left, right, reuse_variables=None, model_index=0):
self.params = params # 参数
self.mode = mode # 模式
self.left = left # 左图
self.right = right # 右图
self.model_collection = ['model_' + str(model_index)] #设置模型名
self.reuse_variables = reuse_variables
self.build_model() # 建立模型
self.build_outputs() # 建立输出
if self.mode == 'test':
return
self.build_losses() # 建立损失
self.build_summaries() # 建立总结
三、图像预处理
1.[:-1]其实就是去除了这行文本的最后一个字符(换行符)后剩下的部分。
[1:]其实就是去除了这行文本的第一个字符(换行符)后剩下的部分。
2.tf.image.resize_area:使用区域插值调整images的大小。
tf.image.resize_nearest_neighbor:使用临近点插值的方法扩大图片的尺寸。
3.scale-space pyramid:其实就是一个尺度金字塔,本文中通过对原图像进行多次下采样降低分辨率来实现金字塔,第一次下采样尺度ds1 = 2S,第二次下采样尺度ds2 = ds1/2,本文中的S设为2,也就是ds1=4,ds2=2,假设原图XCV分辨率,则做第一ds1下采样得到的分辨率为(X/ds1)(C/ds1)(V/ds1),在本文中就是(X/4)(C/4)(V/4),第二次下采样得到的分辨率为(X/2)(C/2)(V/2)
4.预处理方法
金字塔图像:生成这样的尺度空间为我们提供了更多信息,我们打算利用这些信息来改进我们的特征提取。
梯度图像:梯度图像描述了强度景观中每个点的陡度,更具体地说是景观的局部“表面”是如何倾斜的。 在每个像素处,确定两个度量:斜率的方向,也称为梯度; 斜率的大小-陡度。 因此,梯度图像由两个值的映射组成,即方向和斜率。
参考:https://blog.csdn.net/u012285175/article/details/84962012
# 定义x方向的梯度,获得x方向的图像梯度图
def gradient_x(self, img):
gx = img[:,:,:-1,:] - img[:,:,1:,:]
return gx
# 定义y方向的梯度,获得y方向的图像梯度图
def gradient_y(self, img):
gy = img[:,:-1,:,:] - img[:,1:,:,:]
return gy
# 插值扩大图片尺寸
def upsample_nn(self, x, ratio):
s = tf.shape(x)
h = s[1] # 获取图片长度
w = s[2] # 获取图片宽度
return tf.image.resize_nearest_neighbor(x, [h * ratio, w * ratio]) #使用临近点插值的方法扩大图片的尺寸,长宽都变为原来扽ratio倍
# 将图片变为金字塔形状
def scale_pyramid(self, img, num_scales):
scaled_imgs = [img]
s = tf.shape(img)
h = s[1] # 获取图片长度
w = s[2] # 获取图片宽度
for i in range(num_scales - 1): # 不断缩小图片
ratio = 2 ** (i + 1)
nh = h // ratio
nw = w // ratio
scaled_imgs.append(tf.image.resize_area(img, [nh, nw])) #不断添加缩小后的图片,生成金字塔图片
return scaled_imgs # 返回金字塔图
slim.avg_pool2d函数:
def avg_pool2d(inputs,
kernel_size,
stride=2,
padding='VALID',
data_format=DATA_FORMAT_NHWC,
outputs_collections=None,
scope=None):
"""Adds a 2D average pooling op.
"""Adds a 2D Max Pooling op.
Args:
inputs: 一个4-D tensor,形状为[batch_size, height, width, channels]或者[batch_size, channels, height, width]
kernel_size: 池化核的尺寸: [kernel_height, kernel_width],如果两个值相同的话可以为一个整数值。
stride: 池化步长 [stride_height, stride_width].如果两个值相同的话可以为一个整数值。
padding: 填充方式,为 'VALID' 或者 'SAME'.
data_format: 数据格式,支持 `NHWC` (default)和 `NCHW`
outputs_collections: 输出被添加到的集合
scope: 可选的name_scope.
Returns:
返回池化操作后的tensor.
论文中使用:
# 通过左图视差图和右图原图生成左图原图
def generate_image_left(self, img, disp):
return bilinear_sampler_1d_h(img, -disp)
# 通过右图视差图和左图原图生成右图原图
def generate_image_right(self, img, disp):
return bilinear_sampler_1d_h(img, disp)
6.关于SSIM
详见:https://blog.csdn.net/leviopku/article/details/84635897
# 找到重建的左图或者右图与原图label的相似度,这里是计算SSIM,进一步计算第一个损失函数loss
def SSIM(self, x, y):
C1 = 0.01 ** 2 #两个常数项
C2 = 0.03 ** 2
mu_x = slim.avg_pool2d(x, 3, 1, 'VALID') # 计算x的均值,对x图片进行pooling层操作,3*3*的kernalsize,步长为1
mu_y = slim.avg_pool2d(y, 3, 1, 'VALID') # 计算x的均值,对y图片进行pooling层操作,3*3*的kernalsize,步长为1
sigma_x = slim.avg_pool2d(x ** 2, 3, 1, 'VALID') - mu_x ** 2 # 计算x的方差
sigma_y = slim.avg_pool2d(y ** 2, 3, 1, 'VALID') - mu_y ** 2 # 计算x的方差
sigma_xy = slim.avg_pool2d(x * y , 3, 1, 'VALID') - mu_x * mu_y # 计算两张图片的协防差
SSIM_n = (2 * mu_x * mu_y + C1) * (2 * sigma_xy + C2)
SSIM_d = (mu_x ** 2 + mu_y ** 2 + C1) * (sigma_x + sigma_y + C2)
SSIM = SSIM_n / SSIM_d #计算SSIM即两张图片的相似度
#tf.clip_by_value(A, min, max):输入一个张量A,把A中的每一个元素的值都压缩在min和max之间。
return tf.clip_by_value((1 - SSIM) / 2, 0, 1)
# 第二个损失函数,使得生成的视差图能够尽可能的连续
def get_disparity_smoothness(self, disp, pyramid):
disp_gradients_x = [self.gradient_x(d) for d in disp] # 记录四个视差图中x方向的梯度值
disp_gradients_y = [self.gradient_y(d) for d in disp] # 记录四个视差图中y方向的梯度值
image_gradients_x = [self.gradient_x(img) for img in pyramid] # 记录金字塔图中x方向的梯度值
image_gradients_y = [self.gradient_y(img) for img in pyramid] # 记录金字塔图中y方向的梯度值
weights_x = [tf.exp(-tf.reduce_mean(tf.abs(g), 3, keep_dims=True)) for g in image_gradients_x]
# 首先计算金字塔图中四个尺寸的x方向梯度的均值,然后取它们的指数幂
weights_y = [tf.exp(-tf.reduce_mean(tf.abs(g), 3, keep_dims=True)) for g in image_gradients_y]
# 计算金字塔图中四个尺寸的y方向梯度的均值,然后取它们的指数幂
smoothness_x = [disp_gradients_x[i] * weights_x[i] for i in range(4)]
smoothness_y = [disp_gradients_y[i] * weights_y[i] for i in range(4)]
# 两个方向指数幂与视差图梯度相称
return smoothness_x + smoothness_y # 返回不连续程度
- numpy.floor():
按照元素的方式返回输入的下限。
标量x的底部是最大的整数i,使得i <= x。
例:
import numpy as np
a = np.array([-1.7, -1.5, -0.2, 0.2, 1.5, 1.7, 2.0])
np.floor(a)
输出:array([-2., -2., -1., 0., 1., 1., 2.])
8.tf.pad()就是扩充维度,比如
t=tf.constant([1,2,3])
这是一个一维向量,可以通过print(t.get_shape())看维度
先介绍一个概念,paddings=[a,b,c,d],分别从不同维度加0,
因为是一维向量,所以tf.pad(t,[[1,1]])只能从左右加0输出结果为[[0 1 2 3 0]],
当t=tf.constant([[1,2,3]])时,则是一个二维矩阵,就加了一个[],shape=(1,3)
如果a=[1,1],b=[2,2],则上下加一排0,左右加2排0,结果为
[[0 0 0 0 0 0 0]
[0 0 1 2 3 0 0]
[0 0 0 0 0 0 0]]
在二维上面加0,当c=[1,1],
t=tf.constant([[[1,2,3], [2,3,4],[2,1,4]],
[[1,2,3], [2,3,4],[2,1,4]],
[[1,2,3], [2,3,4],[2,1,4]]])
时,即paddings=[[1,1],[2,2],[1,1]]输出为下面所示,shape=(3,3,3)变成了(5,7,5)
详见:https://blog.csdn.net/qwe2508/article/details/79725701
也就是说后面的那个数组参数代表在第几维的上下或左右加几行或列0。
#获得视差图
def get_disp(self, x):
disp = 0.3 * self.conv(x, 2, 3, 1, tf.nn.sigmoid) # 使用3*3的卷积和,步长为1,激活函数为sigmoid函数,进行卷积层,同时将特征图中像素乘0.3
return disp
# 定义卷积层,第一个参数x是输入的图,第二个参数是卷积核的个数,第三个参数是步长,第四个参数是激活参数,默认为elu函数
def conv(self, x, num_out_layers, kernel_size, stride, activation_fn=tf.nn.elu):
p = np.floor((kernel_size - 1) / 2).astype(np.int32)
p_x = tf.pad(x, [[0, 0], [p, p], [p, p], [0, 0]]) # 其他维度不变,图片的尺寸进行padding,使得经过卷积层后图片的尺寸不变
return slim.conv2d(p_x, num_out_layers, kernel_size, stride, 'VALID', activation_fn=activation_fn) #返回卷积后的特征图
# 卷积模块,在这里图片将经过两个卷积层,图片尺寸变为原来的一半
def conv_block(self, x, num_out_layers, kernel_size):
conv1 = self.conv(x, num_out_layers, kernel_size, 1) # 第一个卷积层,步长为1,输出特征图尺寸不变
conv2 = self.conv(conv1, num_out_layers, kernel_size, 2) # 第二个卷积层,步长为2,输出特征图尺寸变为原来的一半
return conv2
# 最大池化层,图片尺寸变为原来的一半
def maxpool(self, x, kernel_size):
p = np.floor((kernel_size - 1) / 2).astype(np.int32)
p_x = tf.pad(x, [[0, 0], [p, p], [p, p], [0, 0]]) # 其他维度不变,图片的尺寸进行padding,使得经过池化层后图片的尺寸变为原来的一半
return slim.max_pool2d(p_x, kernel_size) # 池化层步长默认为2
# 带残差神经网络中shortcut结构的三个卷积层
def resconv(self, x, num_layers, stride):
do_proj = tf.shape(x)[3] != num_layers or stride == 2 # 判断一下卷积层后的输出图尺寸是否改变
shortcut = [] # 定义shortcut中间变量
conv1 = self.conv(x, num_layers, 1, 1) # 第一个卷积层使用的是1*1的卷积核,步长为1,这个操作主要用于减小计算量
conv2 = self.conv(conv1, num_layers, 3, stride) # 第二个卷积层使用的是3*3的卷积核,步长为stride
conv3 = self.conv(conv2, 4 * num_layers, 1, 1, None) # 第三个卷积层使用的还是1*1的卷积核,只是数量变为原来的4倍
if do_proj:
shortcut = self.conv(x, 4 * num_layers, 1, stride, None) # 如果输出图尺寸变化,则将输入图经过1*1的卷积核,通道数变为原来的四倍,直接送到输出处叠加
else:
shortcut = x # 如果输出图尺寸不变,则将输入直接送到输出处
return tf.nn.elu(conv3 + shortcut) # 最后经过elu激活函数
# 带shortcut结构的卷积层模块
def resblock(self, x, num_layers, num_blocks):
out = x
for i in range(num_blocks - 1): # 进行(num_blocks - 1)次输出尺寸不变的卷积层
out = self.resconv(out, num_layers, 1)
out = self.resconv(out, num_layers, 2) # 经过一次卷积层,尺寸变为原来的一半
return out
# 反卷积神经网络,扩大输入图片尺寸
def upconv(self, x, num_out_layers, kernel_size, scale):
upsample = self.upsample_nn(x, scale) # 使用临近点插值的方法扩大图片的尺寸
conv = self.conv(upsample, num_out_layers, kernel_size, 1) # 经过一个卷积层,输出尺寸不变
return conv
# 反卷积层,输出指定大小尺寸图片
def deconv(self, x, num_out_layers, kernel_size, scale):
p_x = tf.pad(x, [[0, 0], [1, 1], [1, 1], [0, 0]]) # padding填充0
conv = slim.conv2d_transpose(p_x, num_out_layers, kernel_size, scale, 'SAME') #反卷积
#conv2d_transpose 中会计算 output_shape 能否通过给定的参数计算出 inputs的维度,如果不能,则报错
return conv[:,3:-1,3:-1,:]
9.VGG网络
# 建立VGG网络
def build_vgg(self):
#set convenience functions
conv = self.conv # 定义卷积层简便函数
if self.params.use_deconv:
upconv = self.deconv # 定义反卷积层简便函数,决定使用上采样反卷积层还是插值反卷积层
else:
upconv = self.upconv
# 进行变量管理,设置作用域,编码器由十四个卷积层组成,每经过两个卷积层,图片尺寸变为原来的一半
# tf.variable_scope(): 可以让变量有相同的命名
with tf.variable_scope('encoder'):
conv1 = self.conv_block(self.model_input, 32, 7) # H/2,两次32个7*7的卷积核
conv2 = self.conv_block(conv1, 64, 5) # H/4,两次64个5*5的卷积核
conv3 = self.conv_block(conv2, 128, 3) # H/8,两次123个3*3的卷积核
conv4 = self.conv_block(conv3, 256, 3) # H/16,两次256个3*3的卷积核
conv5 = self.conv_block(conv4, 512, 3) # H/32,两次512个3*3的卷积核
conv6 = self.conv_block(conv5, 512, 3) # H/64,两次512个3*3的卷积核
conv7 = self.conv_block(conv6, 512, 3) # H/128,两次512个3*3的卷积核
# 设置skip作用域,便于将编码过程的部分特征图直接拼接到解码过程中
with tf.variable_scope('skips'):
skip1 = conv1
skip2 = conv2
skip3 = conv3
skip4 = conv4
skip5 = conv5
skip6 = conv6
# 设置解码器作用域
with tf.variable_scope('decoder'):
upconv7 = upconv(conv7, 512, 3, 2) # H/64,图片长宽都变为原来的二倍
concat7 = tf.concat([upconv7, skip6], 3) # upconv7的输出与conv6输出的尺寸相同,将他们叠加
iconv7 = conv(concat7, 512, 3, 1) # 再经过3*3卷积核的卷积层
upconv6 = upconv(iconv7, 512, 3, 2) # H/32,图片长宽都变为原来的二倍
concat6 = tf.concat([upconv6, skip5], 3) # upconv6的输出与conv5输出的尺寸相同,将他们叠加
iconv6 = conv(concat6, 512, 3, 1) # 再经过3*3卷积核的卷积层
upconv5 = upconv(iconv6, 256, 3, 2) # H/16,图片长宽都变为原来的二倍
concat5 = tf.concat([upconv5, skip4], 3) # upconv5的输出与conv4输出的尺寸相同,将他们叠加
iconv5 = conv(concat5, 256, 3, 1) # 再经过3*3卷积核的卷积层
upconv4 = upconv(iconv5, 128, 3, 2) # H/8,图片长宽都变为原来的二倍
concat4 = tf.concat([upconv4, skip3], 3) # upconv4的输出与conv3输出的尺寸相同,将他们叠加
iconv4 = conv(concat4, 128, 3, 1) # 再经过128个3*3卷积核的卷积层
self.disp4 = self.get_disp(iconv4) # 获得第四个视差图,单边尺寸为输入图片的1/8
udisp4 = self.upsample_nn(self.disp4, 2) # 获得第四个叠加视差图,单边尺寸为输入图片的1/4
upconv3 = upconv(iconv4, 64, 3, 2) # H/4,图片长宽都变为原来的二倍
concat3 = tf.concat([upconv3, skip2, udisp4], 3) # upconv3的输出与conv2输出和第四个叠加视差图的尺寸相同,将他们叠加
iconv3 = conv(concat3, 64, 3, 1) # 再经过64个3*3卷积核的卷积层
self.disp3 = self.get_disp(iconv3) # 获得第三个视差图,单边尺寸为输入图片的1/4
udisp3 = self.upsample_nn(self.disp3, 2) # 获得第三个叠加视差图,单边尺寸为输入图片的1/2
upconv2 = upconv(iconv3, 32, 3, 2) # H/2,图片长宽都变为原来的二倍
concat2 = tf.concat([upconv2, skip1, udisp3], 3) # upconv2的输出与conv1输出和第三个叠加视差图的尺寸相同,将他们通道叠加
iconv2 = conv(concat2, 32, 3, 1) # 再经过32个3*3卷积核的卷积层
self.disp2 = self.get_disp(iconv2) # 获得第二个视差图,单边尺寸为输入图片的1/2
udisp2 = self.upsample_nn(self.disp2, 2) # 获得第二个叠加视差图,单边尺寸为输入图片
upconv1 = upconv(iconv2, 16, 3, 2) # H,图片长宽都变为原来的二倍
concat1 = tf.concat([upconv1, udisp2], 3) # upconv2的输出与第二个叠加视差图的尺寸相同,将他们通道叠加
iconv1 = conv(concat1, 16, 3, 1) # 再经过16个3*3卷积核的卷积层
self.disp1 = self.get_disp(iconv1) # 获得第一个视差图,单边尺寸为输入图片
10.搭建残差神经网络
# 建立残差神经网络
def build_resnet50(self):
#set convenience functions
conv = self.conv # 定义卷积层简便函数,与上面一样
if self.params.use_deconv:
upconv = self.deconv
else:
upconv = self.upconv
# 编码器作用域
with tf.variable_scope('encoder'):
conv1 = conv(self.model_input, 64, 7, 2) # H/2 - 64D,卷积层,7*7卷积核64个,步长为2,输出为输入尺寸的一半
pool1 = self.maxpool(conv1, 3) # H/4 - 64D,最大池化层,kernal_size为3*3,步长为2,输出为输入尺寸的一半
conv2 = self.resblock(pool1, 64, 3) # H/8 - 256D,经过一个残差模块,循环3次尺寸不变的残差卷积层,尺寸变为原来的一半
conv3 = self.resblock(conv2, 128, 4) # H/16 - 512D,经过一个残差模块,循环4次尺寸不变的残差卷积层,尺寸变为原来的一半
conv4 = self.resblock(conv3, 256, 6) # H/32 - 1024D,经过一个残差模块,循环6次尺寸不变的残差卷积层,尺寸变为原来的一半
conv5 = self.resblock(conv4, 512, 3) # H/64 - 2048D,经过一个残差模块,循环6次尺寸不变的残差卷积层,尺寸变为原来的一半
# 设置skip作用域,便于将编码过程的部分特征图直接拼接到解码过程中
with tf.variable_scope('skips'):
skip1 = conv1
skip2 = pool1
skip3 = conv2
skip4 = conv3
skip5 = conv4
#解码作用域,与VGG16一样
# DECODING
with tf.variable_scope('decoder'):
upconv6 = upconv(conv5, 512, 3, 2) #H/32
concat6 = tf.concat([upconv6, skip5], 3)
iconv6 = conv(concat6, 512, 3, 1)
upconv5 = upconv(iconv6, 256, 3, 2) #H/16
concat5 = tf.concat([upconv5, skip4], 3)
iconv5 = conv(concat5, 256, 3, 1)
upconv4 = upconv(iconv5, 128, 3, 2) #H/8
concat4 = tf.concat([upconv4, skip3], 3)
iconv4 = conv(concat4, 128, 3, 1)
self.disp4 = self.get_disp(iconv4)
udisp4 = self.upsample_nn(self.disp4, 2)
upconv3 = upconv(iconv4, 64, 3, 2) #H/4
concat3 = tf.concat([upconv3, skip2, udisp4], 3)
iconv3 = conv(concat3, 64, 3, 1)
self.disp3 = self.get_disp(iconv3)
udisp3 = self.upsample_nn(self.disp3, 2)
upconv2 = upconv(iconv3, 32, 3, 2) #H/2
concat2 = tf.concat([upconv2, skip1, udisp3], 3)
iconv2 = conv(concat2, 32, 3, 1)
self.disp2 = self.get_disp(iconv2)
udisp2 = self.upsample_nn(self.disp2, 2)
upconv1 = upconv(iconv2, 16, 3, 2) #H
concat1 = tf.concat([upconv1, udisp2], 3)
iconv1 = conv(concat1, 16, 3, 1)
self.disp1 = self.get_disp(iconv1)
# 建立模型
def build_model(self):
# slim.arg_scope常用于为tensorflow里的layer函数提供默认值以使构建模型的代码更加紧凑苗条,这里是使用elu作为激活函数
with slim.arg_scope([slim.conv2d, slim.conv2d_transpose], activation_fn=tf.nn.elu):
with tf.variable_scope('model', reuse=self.reuse_variables):
# 左图为四层金字塔图
self.left_pyramid = self.scale_pyramid(self.left, 4)
# 如果训练,右图也为四层金字塔图
if self.mode == 'train':
self.right_pyramid = self.scale_pyramid(self.right, 4)
if self.params.do_stereo:
# 如果生成立体图,将左图和右图一起在channel通道concat
self.model_input = tf.concat([self.left, self.right], 3)
else:
# 否则输入是左图
self.model_input = self.left
# build model
# 建立模型
if self.params.encoder == 'vgg':
self.build_vgg()
elif self.params.encoder == 'resnet50':
self.build_resnet50()
else:
return None
# 设置输出
def build_outputs(self):
# STORE DISPARITIES
with tf.variable_scope('disparities'):
self.disp_est = [self.disp1, self.disp2, self.disp3, self.disp4] # 四张视差图叠加在一起
# TensorFlow中,想要维度增加一维,可以使用tf.expand_dims(input, dim, name=None)函数。
self.disp_left_est = [tf.expand_dims(d[:,:,:,0], 3) for d in self.disp_est] #生成左右金字塔视差图
self.disp_right_est = [tf.expand_dims(d[:,:,:,1], 3) for d in self.disp_est]
if self.mode == 'test':
return
# GENERATE IMAGES
with tf.variable_scope('images'):
# 使用视差图和左图与右图金字塔图生成右图与左图
self.left_est = [self.generate_image_left(self.right_pyramid[i], self.disp_left_est[i]) for i in range(4)]
self.right_est = [self.generate_image_right(self.left_pyramid[i], self.disp_right_est[i]) for i in range(4)]
# LR CONSISTENCY
# 建立左右视差图的相似度
with tf.variable_scope('left-right'):
self.right_to_left_disp = [self.generate_image_left(self.disp_right_est[i], self.disp_left_est[i]) for i in range(4)]
self.left_to_right_disp = [self.generate_image_right(self.disp_left_est[i], self.disp_right_est[i]) for i in range(4)]
# DISPARITY SMOOTHNESS
# 获取左右视差图的连续性
with tf.variable_scope('smoothness'):
self.disp_left_smoothness = self.get_disparity_smoothness(self.disp_left_est, self.left_pyramid)
self.disp_right_smoothness = self.get_disparity_smoothness(self.disp_right_est, self.right_pyramid)
# 建立损失函数
def build_losses(self):
with tf.variable_scope('losses', reuse=self.reuse_variables):
# IMAGE RECONSTRUCTION
# L1
#左图、右图估计损失
self.l1_left = [tf.abs( self.left_est[i] - self.left_pyramid[i]) for i in range(4)] # 获取金字塔左图四个图的损失函数
self.l1_reconstruction_loss_left = [tf.reduce_mean(l) for l in self.l1_left] # 获取左图四个图的平均损失
self.l1_right = [tf.abs(self.right_est[i] - self.right_pyramid[i]) for i in range(4)] # 获取金字塔左图四个图的损失函数
self.l1_reconstruction_loss_right = [tf.reduce_mean(l) for l in self.l1_right] # 获取右图四个图的平均损失
# SSIM
self.ssim_left = [self.SSIM( self.left_est[i], self.left_pyramid[i]) for i in range(4)] #获取预测金字塔左图四个图与真实label的相似度
self.ssim_loss_left = [tf.reduce_mean(s) for s in self.ssim_left] #获取相似度均值
self.ssim_right = [self.SSIM(self.right_est[i], self.right_pyramid[i]) for i in range(4)] #获取预测金字塔右图四个图与真实label的相似度
self.ssim_loss_right = [tf.reduce_mean(s) for s in self.ssim_right] #获取相似度均值
# WEIGTHED SUM
# 计算损失函数三项中的第一项,即左右图的还原程度
self.image_loss_right = [self.params.alpha_image_loss * self.ssim_loss_right[i] + (1 - self.params.alpha_image_loss) * self.l1_reconstruction_loss_right[i] for i in range(4)]
self.image_loss_left = [self.params.alpha_image_loss * self.ssim_loss_left[i] + (1 - self.params.alpha_image_loss) * self.l1_reconstruction_loss_left[i] for i in range(4)]
self.image_loss = tf.add_n(self.image_loss_left + self.image_loss_right)
# DISPARITY SMOOTHNESS
# 计算损失函数三项中的第二项,计算左右视差图的连续性程度作为第二个损失函数
self.disp_left_loss = [tf.reduce_mean(tf.abs(self.disp_left_smoothness[i])) / 2 ** i for i in range(4)]
self.disp_right_loss = [tf.reduce_mean(tf.abs(self.disp_right_smoothness[i])) / 2 ** i for i in range(4)]
self.disp_gradient_loss = tf.add_n(self.disp_left_loss + self.disp_right_loss)
# LR CONSISTENCY
# 计算损失函数三项中的第三项,计算生成的基于左图和右图的两张视差图的相关性程度
self.lr_left_loss = [tf.reduce_mean(tf.abs(self.right_to_left_disp[i] - self.disp_left_est[i])) for i in range(4)]
self.lr_right_loss = [tf.reduce_mean(tf.abs(self.left_to_right_disp[i] - self.disp_right_est[i])) for i in range(4)]
self.lr_loss = tf.add_n(self.lr_left_loss + self.lr_right_loss)
# TOTAL LOSS
# 总损失,将三个损失求和
self.total_loss = self.image_loss + self.params.disp_gradient_loss_weight * self.disp_gradient_loss + self.params.lr_loss_weight * self.lr_loss
# 定义总结函数
def build_summaries(self):
# SUMMARIES
with tf.device('/cpu:0'):
for i in range(4):
# tf.summary.scalar:一般在画loss,accuary时会用到这个函数,记录总损失和三个子损失值,同时记录左右图视差图的预测图
tf.summary.scalar('ssim_loss_' + str(i), self.ssim_loss_left[i] + self.ssim_loss_right[i], collections=self.model_collection)
tf.summary.scalar('l1_loss_' + str(i), self.l1_reconstruction_loss_left[i] + self.l1_reconstruction_loss_right[i], collections=self.model_collection)
tf.summary.scalar('image_loss_' + str(i), self.image_loss_left[i] + self.image_loss_right[i], collections=self.model_collection)
tf.summary.scalar('disp_gradient_loss_' + str(i), self.disp_left_loss[i] + self.disp_right_loss[i], collections=self.model_collection)
tf.summary.scalar('lr_loss_' + str(i), self.lr_left_loss[i] + self.lr_right_loss[i], collections=self.model_collection)
tf.summary.image('disp_left_est_' + str(i), self.disp_left_est[i], max_outputs=4, collections=self.model_collection)
tf.summary.image('disp_right_est_' + str(i), self.disp_right_est[i], max_outputs=4, collections=self.model_collection)
# 记录左右图的预测图,左右图的相似程度,左右图的差值情况
if self.params.full_summary:
tf.summary.image('left_est_' + str(i), self.left_est[i], max_outputs=4, collections=self.model_collection)
tf.summary.image('right_est_' + str(i), self.right_est[i], max_outputs=4, collections=self.model_collection)
tf.summary.image('ssim_left_' + str(i), self.ssim_left[i], max_outputs=4, collections=self.model_collection)
tf.summary.image('ssim_right_' + str(i), self.ssim_right[i], max_outputs=4, collections=self.model_collection)
tf.summary.image('l1_left_' + str(i), self.l1_left[i], max_outputs=4, collections=self.model_collection)
tf.summary.image('l1_right_' + str(i), self.l1_right[i], max_outputs=4, collections=self.model_collection)
# 记录左图右图的label图
if self.params.full_summary:
tf.summary.image('left', self.left, max_outputs=4, collections=self.model_collection)
tf.summary.image('right', self.right, max_outputs=4, collections=self.model_collection)