本文会介绍cGAN和pix2pix,并在 TensorFlow 中使用 pix2pix 模型。
一、cGAN原理
使用GAN可以无监督生成全新的图片,比如使用GAN生成MNIST数字,虽然可以生成数字,但是不能生成确定的数字。如果希望控制生成的结果,例如生成数字1,此时就要用到cGAN了。
cGAN 的全称为 Conditional Generative Adversarial Networks, 即条件对抗生成网 络,它为生成器、判别器都额外加入了一个条件 y, 这个条件实际是希望生成的标签 。 生成器 G 必须要生成和条件 y 匹配的样本,判别器不仅要判别图像是否真实,还要判别图像和条件y是否匹配。cGAN的输入输出为:
(1)生成器 G, 输入一个噪声 z, 一个条件 y,输出符合该条件的图像 G(z[y)。
(2)判别器 D ,输入一张图像、一个条件 y,输出该图像在该条件下的真实概率 D(x[y)。
cGAN损失定义:在GAN的优化目标中加入条件y,即:
二、pix2pix模型的原理
在自然语言处理领域,机器翻译,也就是将中文译成英文,对应的,在图像领域,也有图像翻译。例如:将白天的图片转换为夜晚的图片、将街景的标注图像变为真实图片。使用pix2pix可以处理这类问题,模型结构如下:
它是一种特殊的cGAN,设要将 Y类型的图像转换为 X类型的图像, G、 D 的任务分别为 :
(1)G 的输入是一个 Y类图像y (条件y) ,输出为生成图像 G(y)。
(2)D 的输入为一个X类图像x,一个 y类图像y。D需要判断x图像是否是真正的y对应的图像,并输出一个概率。
模型损失定义为:L1+cGAN。实验中发现,在生成图像和真实图像中加入L1/L2损失,可以加速模型收敛以及提高准确率。
在pix2pix2提出一种PatchGAN的思想:PatchGAN 对图片中每个 NxN 的小块计算概率, 然后再将这些概率求平均值作为整体的输出,这样做可以加快计算速度以及加快收敛。
三、TensorFlow中的pix2pix模型
1、执行已有数据集
Facades数据集包含了建筑的外观图像和建筑的标注 。 建筑的标注同样是图像形式,用不同颜色的色块表示不同的类别。Facades 数据集将建筑外观分为墙壁、窗户、门、檐口等 12 个类别。下载Facades 数据集:python tools/download-dataset.py facades
所有的样本图像都是两张图片拼接起来的,训练时,可以将A类图像翻译成B类图像。本例将标注图像生成真实图像,运行命令:
python pix2pix.py --mode train # 表示从头训练模型 test表示用已有模型测试 --output_dir facades_train # 保存模型的位置 --max_epochs 200 # epoch数 --input_dir facades/train/ # 训练数据 --which_direction BtoA # 翻译方向
训练结束后,使用命令进行测试:
python pix2pix.py --mode test # 用已有模型测试 --output_dir facades_test # 保存所有图片的测试结果 --input_dir facades/val # 训练数据 --checkpoint facades_train # 之前保存模型的位置,表示从此处恢复模型
执行测试后,在 facades_test 文件夹下,会产生一个 index.html 文件 。 打开后可以看到一个可视化展示生成结果的网页。
2、创建自己的数据集
通过程序,将训练数据也整理为之前所说的 A、 B 图像并列排列的形式,用对应的指令进行训练和测试,相应代码在process.py文件中。
(1)将图片缩放到同样的大小
def resize(src): height, width, _ = src.shape dst = src if height != width: if a.pad: size = max(height, width) # pad to correct ratio oh = (size - height) // 2 ow = (size - width) // 2 dst = im.pad(image=dst, offset_height=oh, offset_width=ow, target_height=size, target_width=size) else: # crop to correct ratio size = min(height, width) oh = (height - size) // 2 ow = (width - size) // 2 dst = im.crop(image=dst, offset_height=oh, offset_width=ow, target_height=size, target_width=size) assert(dst.shape[0] == dst.shape[1]) size, _, _ = dst.shape if size > a.size: dst = im.downscale(images=dst, size=[a.size, a.size]) elif size < a.size: dst = im.upscale(images=dst, size=[a.size, a.size]) return dst
(2)转换图像井合并
对 A 类图像做某种操作以生成对应的 B 类图像,并将转焕后的图像合起来变成一个训练样本。比如将A图像挖去一部分成为B图像,再合并。
# 填充部分图像为空白 def blank(src): height, width, _ = src.shape if height != width: raise Exception("non-square image") image_size = width size = int(image_size * 0.3) offset = int(image_size / 2 - size / 2) dst = src dst[offset:offset + size,offset:offset + size,:] = np.ones([size, size, 3]) return dst
# 合并 def combine(src, src_path): if a.b_dir is None: raise Exception("missing b_dir") # find corresponding file in b_dir, could have a different extension basename, _ = os.path.splitext(os.path.basename(src_path)) for ext in [".png", ".jpg"]: sibling_path = os.path.join(a.b_dir, basename + ext) if os.path.exists(sibling_path): sibling = im.load(sibling_path) break else: raise Exception("could not find sibling image for " + src_path) # make sure that dimensions are correct height, width, _ = src.shape if height != sibling.shape[0] or width != sibling.shape[1]: raise Exception("differing sizes") # convert both images to RGB if necessary if src.shape[2] == 1: src = im.grayscale_to_rgb(images=src) if sibling.shape[2] == 1: sibling = im.grayscale_to_rgb(images=sibling) # remove alpha channel if src.shape[2] == 4: src = src[:,:,:3] if sibling.shape[2] == 4: sibling = sibling[:,:,:3] return np.concatenate([src, sibling], axis=1)
(3)分割数据集,把数据集分割为训练集和验证集