内容简介:介绍可用于实现多种非配对图像翻译任务的CycleGAN模型,并完成性别转换任务和pix2pix不同,CycleGAN不需要严格配对的图片,只需要两类(domain)即可,例如一个文件夹都是苹果图片,另一个文件夹都是橘子图片使用A和B两类图片,就可以实现A到B的翻译和B到A的翻译
介绍可用于实现多种非配对图像翻译任务的CycleGAN模型,并完成性别转换任务
原理
和pix2pix不同,CycleGAN不需要严格配对的图片,只需要两类(domain)即可,例如一个文件夹都是苹果图片,另一个文件夹都是橘子图片
使用A和B两类图片,就可以实现A到B的翻译和B到A的翻译
论文官方网站上提供了详细的例子和介绍, junyanz.github.io/CycleGAN/ ,例如苹果和橘子、马和斑马、夏天和冬天、照片和艺术作品等
以及论文的官方Github项目, github.com/junyanz/Cyc… ,使用PyTorch实现
CycleGAN由两个生成器G和F,以及两个判别器Dx和Dy组成
G接受真的X并输出假的Y,即完成X到Y的翻译;F接受真的Y并输出假的X,即完成Y到X的翻译;Dx接受真假X并进行判别,Dy接受真假Y并进行判别
CycleGAN的损失函数和标准GAN差不多,只是写两套而已
除此之外,为了避免mode collasp问题,CycleGAN还考虑了循环一致损失(Cycle Consistency Loss)
因此CycleGAN的总损失如下,G、F、Dx、Dy分别需要min、max其中的部分损失项
实现
在论文的具体实现中,使用了两个tricks
- 使用Least-Square Loss即最小平方误差代替标准的GAN损失
- 以G为例,维护一个历史假Y图片集合,例如50张。每次G生成假Y之后将其加到集合中,再从集合中随机地取出一张假Y,和一张真Y一起输入给判别器进行判别。这样一来,假Y集合代表了G根据X生成Y的平均能力,使得训练更加稳定
使用以下项目训练CycleGAN模型, github.com/vanhuyz/Cyc… ,主要包括几个代码:
-
build_data.py
:将图片数据整理为tfrecords文件 -
ops.py
:定义了一些小的网络模块 -
generator.py
:生成器的定义 -
discriminator.py
:判别器的定义 -
model.py
:使用生成器和判别器定义CycleGAN -
train.py
:训练模型的代码 -
export_graph.py
:将训练好的模型打包成.pd
文件 -
inference.py
:使用打包好的.pb
文件翻译图片,即使用模型进行推断
生成器和判别器结构如下,如果感兴趣可以进一步阅读项目源码
性别转换
使用CelebA中的男性图片和女性图片,训练一个实现性别转换的CycleGAN
将CelebA数据集中的图片处理成 256*256
大小,并按照性别保存至male和female两个文件夹,分别包含84434张男性图片和118165张女性图片
# -*- coding: utf-8 -*- from imageio import imread, imsave import cv2 import glob, os from tqdm import tqdm data_dir = 'data' male_dir = 'data/male' female_dir = 'data/female' if not os.path.exists(data_dir): os.mkdir(data_dir) if not os.path.exists(male_dir): os.mkdir(male_dir) if not os.path.exists(female_dir): os.mkdir(female_dir) WIDTH = 256 HEIGHT = 256 def read_process_save(read_path, save_path): image = imread(read_path) h = image.shape[0] w = image.shape[1] if h > w: image = image[h // 2 - w // 2: h // 2 + w // 2, :, :] else: image = image[:, w // 2 - h // 2: w // 2 + h // 2, :] image = cv2.resize(image, (WIDTH, HEIGHT)) imsave(save_path, image) target = 'Male' with open('list_attr_celeba.txt', 'r') as fr: lines = fr.readlines() all_tags = lines[0].strip('\n').split() for i in tqdm(range(1, len(lines))): line = lines[i].strip('\n').split() if int(line[all_tags.index(target) + 1]) == 1: read_process_save(os.path.join('celeba', line[0]), os.path.join(male_dir, line[0])) # 男 else: read_process_save(os.path.join('celeba', line[0]), os.path.join(female_dir, line[0])) # 女 复制代码
使用 build_data.py
将图片转换成tfrecords格式
python CycleGAN-TensorFlow/build_data.py --X_input_dir data/male/ --Y_input_dir data/female/ --X_output_file data/male.tfrecords --Y_output_file data/female.tfrecords 复制代码
使用 train.py
训练CycleGAN模型
python CycleGAN-TensorFlow/train.py --X data/male.tfrecords --Y data/female.tfrecords --image_size 256 复制代码
训练开始后,会生成checkpoints文件夹,并根据当前日期和时间生成一个子文件夹,例如 20180507-0231
,其中包括用于显示tensorboard的 events.out.tfevents
文件,以及和模型相关的一些文件
使用tensorboard查看模型训练细节,运行以下命令后访问6006端口即可
tensorboard --logdir=checkpoints/20180507-0231 复制代码
以下是迭代185870次之后,tensorboard的IMAGES页面
模型训练没有迭代次数限制,所以感觉效果不错或者迭代次数差不多了,便可以终止训练
使用 export_graph.py
将模型打包成 .pb
文件,生成的文件在pretrained文件夹中
python CycleGAN-TensorFlow/export_graph.py --checkpoint_dir checkpoints/20180507-0231/ --XtoY_model male2female.pb --YtoX_model female2male.pb --image_size 256 复制代码
通过 inference.py
使用模型处理图片
python CycleGAN-TensorFlow/inference.py --model pretrained/male2female.pb --input Trump.jpg --output Trump_female.jpg --image_size 256 复制代码
python CycleGAN-TensorFlow/inference.py --model pretrained/female2male.pb --input Hillary.jpg --output Hillary_male.jpg --image_size 256 复制代码
在代码中使用模型处理多张图片
# -*- coding: utf-8 -*- import tensorflow as tf import numpy as np from model import CycleGAN from imageio import imread, imsave import glob import os image_file = 'face.jpg' W = 256 result = np.zeros((4 * W, 5 * W, 3)) for gender in ['male', 'female']: if gender == 'male': images = glob.glob('../faces/male/*.jpg') model = '../pretrained/male2female.pb' r = 0 else: images = glob.glob('../faces/female/*.jpg') model = '../pretrained/female2male.pb' r = 2 graph = tf.Graph() with graph.as_default(): graph_def = tf.GraphDef() with tf.gfile.FastGFile(model, 'rb') as model_file: graph_def.ParseFromString(model_file.read()) tf.import_graph_def(graph_def, name='') with tf.Session(graph=graph) as sess: input_tensor = graph.get_tensor_by_name('input_image:0') output_tensor = graph.get_tensor_by_name('output_image:0') for i, image in enumerate(images): image = imread(image) output = sess.run(output_tensor, feed_dict={input_tensor: image}) with open(image_file, 'wb') as f: f.write(output) output = imread(image_file) maxv = np.max(output) minv = np.min(output) output = ((output - minv) / (maxv - minv) * 255).astype(np.uint8) result[r * W: (r + 1) * W, i * W: (i + 1) * W, :] = image result[(r + 1) * W: (r + 2) * W, i * W: (i + 1) * W, :] = output os.remove(image_file) imsave('CycleGAN性别转换结果.jpg', result) 复制代码
视频性别转换
对一段视频,识别每一帧可能包含的人脸,检测人脸对应的性别,并使用CycleGAN完成性别的双向转换
使用以下项目实现性别的检测, github.com/yu4u/age-ge… ,通过Keras训练模型,可以检测出人脸的性别和年龄
举个例子,使用OpenCV获取摄像头图片,通过dlib检测人脸,并得到每一个检测结果对应的年龄和性别
# -*- coding: utf-8 -*- from wide_resnet import WideResNet import numpy as np import cv2 import dlib depth = 16 width = 8 img_size = 64 model = WideResNet(img_size, depth=depth, k=width)() model.load_weights('weights.hdf5') def draw_label(image, point, label, font=cv2.FONT_HERSHEY_SIMPLEX, font_scale=1, thickness=2): size = cv2.getTextSize(label, font, font_scale, thickness)[0] x, y = point cv2.rectangle(image, (x, y - size[1]), (x + size[0], y), (255, 0, 0), cv2.FILLED) cv2.putText(image, label, point, font, font_scale, (255, 255, 255), thickness) detector = dlib.get_frontal_face_detector() cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, image_np = cap.read() image_np = cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB) img_h = image_np.shape[0] img_w = image_np.shape[1] detected = detector(image_np, 1) faces = [] if len(detected) > 0: for i, d in enumerate(detected): x0, y0, x1, y1, w, h = d.left(), d.top(), d.right(), d.bottom(), d.width(), d.height() cv2.rectangle(image_np, (x0, y0), (x1, y1), (255, 0, 0), 2) x0 = max(int(x0 - 0.25 * w), 0) y0 = max(int(y0 - 0.45 * h), 0) x1 = min(int(x1 + 0.25 * w), img_w - 1) y1 = min(int(y1 + 0.05 * h), img_h - 1) w = x1 - x0 h = y1 - y0 if w > h: x0 = x0 + w // 2 - h // 2 w = h x1 = x0 + w else: y0 = y0 + h // 2 - w // 2 h = w y1 = y0 + h faces.append(cv2.resize(image_np[y0: y1, x0: x1, :], (img_size, img_size))) faces = np.array(faces) results = model.predict(faces) predicted_genders = results[0] ages = np.arange(0, 101).reshape(101, 1) predicted_ages = results[1].dot(ages).flatten() for i, d in enumerate(detected): label = '{}, {}'.format(int(predicted_ages[i]), 'F' if predicted_genders[i][0] > 0.5 else 'M') draw_label(image_np, (d.left(), d.top()), label) cv2.imshow('gender and age', cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)) if cv2.waitKey(25) & 0xFF == ord('q'): cap.release() cv2.destroyAllWindows() break 复制代码
将以上项目和CycleGAN应用于视频的双向性别转换,首先提取出视频中的人脸,记录人脸出现的帧数、位置以及对应的性别,视频共830帧,检测出721张人脸
# -*- coding: utf-8 -*- from wide_resnet import WideResNet import numpy as np import cv2 import dlib import pickle depth = 16 width = 8 img_size = 64 model = WideResNet(img_size, depth=depth, k=width)() model.load_weights('weights.hdf5') detector = dlib.get_frontal_face_detector() cap = cv2.VideoCapture('../friends.mp4') pos = [] frame_id = -1 while cap.isOpened(): ret, image_np = cap.read() frame_id += 1 if len((np.array(image_np)).shape) == 0: break image_np = cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB) img_h = image_np.shape[0] img_w = image_np.shape[1] detected = detector(image_np, 1) if len(detected) > 0: for d in detected: x0, y0, x1, y1, w, h = d.left(), d.top(), d.right(), d.bottom(), d.width(), d.height() x0 = max(int(x0 - 0.25 * w), 0) y0 = max(int(y0 - 0.45 * h), 0) x1 = min(int(x1 + 0.25 * w), img_w - 1) y1 = min(int(y1 + 0.05 * h), img_h - 1) w = x1 - x0 h = y1 - y0 if w > h: x0 = x0 + w // 2 - h // 2 w = h x1 = x0 + w else: y0 = y0 + h // 2 - w // 2 h = w y1 = y0 + h face = cv2.resize(image_np[y0: y1, x0: x1, :], (img_size, img_size)) result = model.predict(np.array([face])) pred_gender = result[0][0][0] if pred_gender > 0.5: pos.append([frame_id, y0, y1, x0, x1, h, w, 'F']) else: pos.append([frame_id, y0, y1, x0, x1, h, w, 'M']) print(frame_id + 1, len(pos)) with open('../pos.pkl', 'wb') as fw: pickle.dump(pos, fw) cap.release() cv2.destroyAllWindows() 复制代码
再使用CycleGAN,将原视频中出现的人脸转换成相反的性别,并写入新的视频文件
# -*- coding: utf-8 -*- import tensorflow as tf import numpy as np from model import CycleGAN from imageio import imread import os import cv2 import pickle from tqdm import tqdm with open('../pos.pkl', 'rb') as fr: pos = pickle.load(fr) cap = cv2.VideoCapture('../friends.mp4') ret, image_np = cap.read() out = cv2.VideoWriter('../output.mp4', -1, cap.get(cv2.CAP_PROP_FPS), (image_np.shape[1], image_np.shape[0])) frames = [] while cap.isOpened(): ret, image_np = cap.read() if len((np.array(image_np)).shape) == 0: break frames.append(cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB)) image_size = 256 image_file = 'face.jpg' for gender in ['M', 'F']: if gender == 'M': model = '../pretrained/male2female.pb' else: model = '../pretrained/female2male.pb' graph = tf.Graph() with graph.as_default(): graph_def = tf.GraphDef() with tf.gfile.FastGFile(model, 'rb') as model_file: graph_def.ParseFromString(model_file.read()) tf.import_graph_def(graph_def, name='') with tf.Session(graph=graph) as sess: input_tensor = graph.get_tensor_by_name('input_image:0') output_tensor = graph.get_tensor_by_name('output_image:0') for i in tqdm(range(len(pos))): fid, y0, y1, x0, x1, h, w, g = pos[i] if g == gender: face = cv2.resize(frames[fid - 1][y0: y1, x0: x1, :], (image_size, image_size)) output_face = sess.run(output_tensor, feed_dict={input_tensor: face}) with open(image_file, 'wb') as f: f.write(output_face) output_face = imread(image_file) maxv = np.max(output_face) minv = np.min(output_face) output_face = ((output_face - minv) / (maxv - minv) * 255).astype(np.uint8) output_face = cv2.resize(output_face, (w, h)) frames[fid - 1][y0: y1, x0: x1, :] = output_face for frame in frames: out.write(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)) os.remove(image_file) cap.release() out.release() cv2.destroyAllWindows() 复制代码
生成的视频文件只有图像、没有声音,可以使用 ffmpeg
进一步处理
如果没有 ffmpeg
则下载并安装, www.ffmpeg.org/download.ht…
进入命令行,从原始视频中提取音频
ffmpeg -i friends.mp4 -f mp3 -vn sound.mp3 复制代码
将提取的音频和生成的视频合成在一起
ffmpeg -i output.mp4 -i sound.mp3 combine.mp4 复制代码
其他
项目还提供了四个训练好的模型, github.com/vanhuyz/Cyc… ,包括苹果到橘子、橘子到苹果、马到斑马、斑马到马,如果感兴趣可以尝试一下
用CycleGAN不仅可以完成两类图片之间的转换,也可以实现两个物体之间的转换,例如将一个人翻译成另一个人
可以考虑从一部电影中提取出两个角色对应的图片,训练CycleGAN之后,即可将一个人翻译成另一个人
还有一些比较大胆的尝试, 提高驾驶技术:用GAN去除(爱情)动作片中的马赛克和衣服
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 机器学习人脸性别识别
- Oracle 被指控性别与年龄歧视
- 当AI表现出种族或性别歧视
- [InnoDB]性别字段为什么不适合加索引
- 利用声纹识别技术识别400电话语音性别
- 世界经济论坛:AI行业存在严重的性别失衡
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Base64 编码/解码
Base64 编码/解码
HEX HSV 转换工具
HEX HSV 互换工具