Python 与 TensorFlow2 生成式 AI(三)(1)https://developer.aliyun.com/article/1512046
关于这个设置如何工作的详细信息如下:
- 两个自编码器在训练过程中使用反向传播学习重构它们各自的输入。
- 每个自编码器都试图最小化重构误差。在我们的情况下,我们将使用平均绝对误差(MAE)作为我们的度量标准。
- 由于两个自编码器具有相同的编码器,编码器学习理解两种类型的人脸并将它们转换为嵌入空间。
- 通过对输入图像进行对齐和变形,确保编码器能够学习两种类型人脸的表示。
- 另一方面,相应的解码器经过训练,利用嵌入来重构图像。
一旦两个编码器都训练得令我们满意,我们就进行人脸交换。让我们考虑这样一个场景:我们要将人物 B 的脸换到人物 A 的脸上:
- 我们从一个人物 B 的图像开始。输入由编码器编码为低维空间。现在,我们不再使用 B 的解码器,而是将其与 A 的解码器交换,即 Decoder-A。这本质上是使用了来自人物 B 数据集的 Autoencoder-A 的输入。
- 使用 B 作为 Autoencoder-A 的输入进行面部交换是可行的,因为 Autoencoder-A 将 B 的面部视为 A 本身的扭曲版本(因为存在公共编码器)。
- 因此,Autoencoder-A 的解码器生成一个看起来像 A 的外观,但具有 B 的特征的输出图像。
让我们利用这一理解来创建所需的自动编码器。以下代码片段展示了两种类型面孔的自动编码器:
ENCODER_DIM = 1024 IMAGE_SHAPE = (64, 64, 3) encoder = Encoder(IMAGE_SHAPE,ENCODER_DIM) decoder_A = Decoder() decoder_B = Decoder() optimizer = Adam(lr=5e-5, beta_1=0.5, beta_2=0.999) #orig adam 5e-5 x = Input(shape=IMAGE_SHAPE) autoencoder_A = Model(x, decoder_A(encoder(x))) autoencoder_B = Model(x, decoder_B(encoder(x))) autoencoder_A.compile(optimizer=optimizer, loss='mean_absolute_error') autoencoder_B.compile(optimizer=optimizer, loss='mean_absolute_error')
我们有两个接受 3 通道输入图像的自动编码器,每个图像大小为 64x64。编码器将这些图像转换为大小为 8x8x512 的嵌入,而解码器使用这些嵌入来重建形状为 64x64x3 的输出图像。在下一节中,我们将训练这些自动编码器。
训练我们自己的面部交换程序
现在我们已经准备好了自动编码器,我们需要准备一个自定义训练循环来一起训练这两个网络。然而,在进行训练循环之前,我们需要定义一些其他实用程序。
我们为两种个性创建的输入数据集包含它们在不同的光照条件、面部位置和其他设置下的面部。然而这些并不是穷尽的。为了确保我们捕获每种面孔的更大变化,我们将使用一些增强方法。以下代码片段展示了一个向输入图像施加随机变换的函数:
def random_transform(image, rotation_range, zoom_range, shift_range, random_flip): h, w = image.shape[0:2] rotation = np.random.uniform(-rotation_range, rotation_range) scale = np.random.uniform(1 - zoom_range, 1 + zoom_range) tx = np.random.uniform(-shift_range, shift_range) * w ty = np.random.uniform(-shift_range, shift_range) * h mat = cv2.getRotationMatrix2D((w // 2, h // 2), rotation, scale) mat[:, 2] += (tx, ty) result = cv2.warpAffine(image, mat, (w, h), borderMode=cv2.BORDER_REPLICATE) if np.random.random() < random_flip: result = result[:, ::-1] return result
random_transform
函数帮助我们生成同一输入面部的不同扭曲。这种方法确保我们有足够的变化来训练我们的网络。
下一个需要的函数是批量生成器。由于我们处理图像和大型网络,务必记住资源要求。我们利用诸如yield
这样的延迟执行实用程序来尽可能保持内存/GPU 要求低。以下代码片段显示了我们训练过程的批量生成器:
def minibatch(image_list, batchsize): length = len(image_list) epoch = i = 0 shuffle(image_list) while True: size = batchsize if i + size > length: shuffle(image_list) i = 0 epoch += 1 images = np.float32([read_image(image_list[j]) for j in range(i, i + size)]) warped_img, target_img = images[:, 0, :, :, :], images[:, 1, :, :, :] i += size yield epoch, warped_img, target_img def minibatchAB(image_list, batchsize): batch = minibatch(image_list, batchsize) for ep1, warped_img, target_img in batch: yield ep1, warped_img, target_img
现在我们有了批量生成器和增强函数,让我们准备一个训练循环。下面是展示的:
def train_one_step(iter,batch_genA,batch_genB,autoencoder_A,autoencoder_B): epoch, warped_A, target_A = next(batch_genA) epoch, warped_B, target_B = next(batch_genB) loss_A = autoencoder_A.train_on_batch(warped_A, target_A) loss_B = autoencoder_B.train_on_batch(warped_B, target_B) print("[#{0:5d}] loss_A: {1:.5f}, loss_B: {2:.5f}".format(iter, loss_A, loss_B)) ctr = 10000 batchsize = 64 save_interval = 100 model_dir = "models" fn_imgA = get_image_paths('nicolas_face') fn_imgB = get_image_paths('trump_face') batch_genA = minibatchAB(fn_imgA, batchsize) batch_genB = minibatchAB(fn_imgB, batchsize) for epoch in range(0, ctr): save_iteration = epoch % save_interval == 0 train_one_step(epoch,batch_genA,batch_genB,autoencoder_A,autoencoder_B) if save_iteration: print("{}/{}".format(epoch,ctr)) save_weights('models',encoder,decoder_A,decoder_B)
我们训练两个自动编码器大约 10,000 步,或者直到损失稳定。我们使用批量大小为 64 并且每 100 个周期保存检查点权重。读者可根据其基础架构设置自由调整这些参数。
结果和局限性
现在,我们已经为尼古拉斯·凯奇(自动编码器-A)和唐纳德·特朗普(自动编码器-B)分别训练了对应的自动编码器。最后一步是将尼古拉斯·凯奇转换为唐纳德·特朗普。我们之前描述了这些步骤;我们将使用唐纳德·特朗普的自动编码器,输入为尼古拉斯·凯奇,从而生成一个看起来像尼古拉斯·凯奇版本的唐纳德·特朗普的输出。
但在进入输出生成任务之前,我们需要一些额外的实用程序。我们讨论了一个称为混合的额外步骤。这一步是在输出生成后执行的,以确保生成的替换和原始面孔无缝地融合成一幅图像。回头看图 8.8,对于混合概念的视觉提醒。对于我们的任务,我们准备了一个名为Convert
的混合类。该类在以下片段中呈现:
class Convert(): def __init__(self, encoder, blur_size=2, seamless_clone=False, mask_type="facehullandrect", erosion_kernel_size=None, **kwargs): self.encoder = encoder self.erosion_kernel = None if erosion_kernel_size is not None: self.erosion_kernel = cv2.getStructuringElement( cv2.MORPH_ELLIPSE, (erosion_kernel_size, erosion_kernel_size)) self.blur_size = blur_size self.seamless_clone = seamless_clone self.mask_type = mask_type.lower() def patch_image(self, image, face_detected): size = 64 image_size = image.shape[1], image.shape[0] # get face alignment matrix mat = np.array(get_align_mat(face_detected)).reshape(2, 3) * size # perform affine transformation to # transform face as per alignment matrix new_face = self.get_new_face(image, mat, size) # get face mask matrix image_mask = self.get_image_mask(image, new_face, face_detected, mat, image_size) return self.apply_new_face(image, new_face, image_mask, mat, image_size, size)
patch_image
方法依赖于该类中还定义的几个实用函数,get_new_face
、apply_new_face
和get_image_mask
:
def apply_new_face(self, image, new_face, image_mask, mat, image_size, size): base_image = np.copy(image) new_image = np.copy(image) # perform affine transformation for better match cv2.warpAffine(new_face, mat, image_size, new_image, cv2.WARP_INVERSE_MAP, cv2.BORDER_TRANSPARENT) outimage = None if self.seamless_clone: masky, maskx = cv2.transform(np.array([size / 2, size / 2]).reshape(1, 1, 2), cv2.invertAffineTransform(mat)).reshape(2).astype(int) outimage = cv2.seamlessClone(new_image.astype(np.uint8), base_image.astype(np.uint8), (image_mask * 255).astype(np.uint8), (masky, maskx), cv2.NORMAL_CLONE) else: # apply source face on the target image's mask foreground = cv2.multiply(image_mask, new_image.astype(float)) # keep background same background = cv2.multiply(1.0 - image_mask, base_image.astype(float)) # merge foreground and background components outimage = cv2.add(foreground, background) return outimage def get_new_face(self, image, mat, size): # function to align input image based on # base image face matrix face = cv2.warpAffine(image, mat, (size, size)) face = np.expand_dims(face, 0) new_face = self.encoder(face / 255.0)[0] return np.clip(new_face * 255, 0, 255).astype(image.dtype) def get_image_mask(self, image, new_face, face_detected, mat, image_size): # function to get mask/portion of image covered by face face_mask = np.zeros(image.shape, dtype=float) if 'rect' in self.mask_type: face_src = np.ones(new_face.shape, dtype=float) cv2.warpAffine(face_src, mat, image_size, face_mask, cv2.WARP_INVERSE_MAP, cv2.BORDER_TRANSPARENT) hull_mask = np.zeros(image.shape, dtype=float) if 'hull' in self.mask_type: hull = cv2.convexHull(np.array(face_detected.landmarksAsXY()).reshape( (-1, 2)).astype(int)).flatten().reshape((-1, 2)) cv2.fillConvexPoly(hull_mask, hull, (1, 1, 1)) if self.mask_type == 'rect': image_mask = face_mask elif self.mask_type == 'faceHull': image_mask = hull_mask else: image_mask = ((face_mask * hull_mask)) # erode masked image to improve blending if self.erosion_kernel is not None: image_mask = cv2.erode(image_mask, self.erosion_kernel, iterations=1) # blur masked image to improve blending if self.blur_size != 0: image_mask = cv2.blur(image_mask, (self.blur_size, self.blur_size)) return image_mask
该类接收多个参数以改善混合结果。这些参数控制诸如模糊核大小、补丁类型(如矩形或多边形)和侵蚀核大小等方面。该类还接收编码器作为输入。类方法patch_image
在实例化期间使用来自 cv2 库的变换函数和我们设置的参数来进行其神奇操作。我们使用以下convert
函数来处理每个 A 类型输入面孔并将其转换为 B 类型:
def convert(converter, item,output_dir): try: (filename, image, faces) = item image1 = None for idx, face in faces: image1 = converter.patch_image(image, face) if np.any(image1): output_file = output_dir+"/"+str(filename).split("/")[-1] cv2.imwrite(str(output_file), image1) except Exception as e: print('Failed to convert image: {}. Reason: {}'.format(filename, e)) Convert class and an inference loop to generate output:
conv_name = "Masked" swap_model = False blur_size = 2 seamless_clone = False mask_type = "facehullandrect" erosion_kernel_size = None smooth_mask = True avg_color_adjust = True faceswap_converter = Convert(model_swapper(False,autoencoder_A,autoencoder_B), blur_size = blur_size, seamless_clone = seamless_clone, mask_type = mask_type, erosion_kernel_size = erosion_kernel_size, smooth_mask = smooth_mask, avg_color_adjust = avg_color_adjust ) list_faces=get_list_images_faces('nicolas_face', 'nicolas_ref.png',extractor) for item in list_faces: #print(item) convert(faceswap_converter, item,'face_swaps_trump')
生成的输出如下图所示:
图 8.16: 尼古拉斯·凯奇变成唐纳德·特朗普
交换后的输出面孔令人鼓舞,但并不像我们预期的那样无缝。但我们可以看到模型已学会识别和交换面部的正确部分。混合步骤还尝试匹配肤色、面部姿势和其他方面,使结果尽可能真实。
这似乎是一个良好的开始,但留下了很多需要改进的地方。以下是我们设置的几个限制:
- 交换输出的质量直接与训练的自动编码器的能力相关。由于没有组件来跟踪重建输出的真实性,很难将自动编码器引导到正确的方向。使用 GAN 可能是提供正面反馈以增强整个训练过程的一种可能的增强。
- 输出有点模糊。这是由于实际输入分辨率和生成的输出(64x64)之间的差异。造成模糊输出的另一个原因是使用 MAE 作为简单的损失函数。研究表明,复合和复杂的损失有助于提高最终的输出质量。
- 有限的数据集是输出质量受限的另一个原因。我们利用增强技术来解决限制,但这并不能替代更大的数据集。
在本节中,我们从零开始开发了一个人脸交换 deepfake 架构。我们以逐步的方式来理解将尼古拉斯·凯奇与唐纳德·特朗普交换的整体流程中的每个组件和步骤。在下一节中,我们将使用更复杂的设置来尝试不同的操作模式。
本节中的代码基于原始 deepfake 工作以及 Olivier Valery 的代码的简化实现,该代码可在 GitHub 的以下链接找到:github.com/OValery16/swap-face
。
现在我们已经训练好了自己的人脸交换器,演示了替换模式的操作,我们可以继续进行再现模式。
使用 pix2pix 进行再现
再现是深度假像设置的另一种操作模式。与替换模式相比,它据说更擅长生成可信的虚假内容。在前面的章节中,我们讨论了执行再现的不同技术,即通过关注凝视、表情、嘴巴等。
我们还在第七章,使用 GAN 进行风格转移中讨论了图像到图像翻译体系结构。特别是,我们详细讨论了 pix2pix GAN 是一种强大的架构,可以实现成对翻译任务。在本节中,我们将利用 pix2pix GAN 从零开始开发一个人脸再现设置。我们将努力构建一个网络,我们可以使用我们自己的面部、嘴巴和表情来控制巴拉克·奥巴马(前美国总统)的面部。我们将逐步进行每一步,从准备数据集开始,到定义 pix2pix 架构,最后生成输出再现。让我们开始吧。
数据集准备
我们将使用 pix2pix GAN 作为我们当前再现任务的骨干网络。虽然 pix2pix 是一个训练样本非常少的强大网络,但有一个限制,即需要训练样本成对出现。在本节中,我们将利用这个限制来达到我们的目的。
由于目标是分析目标面孔并使用源面孔进行控制,我们可以利用不同面孔之间的共同之处来为我们的用例开发数据集。不同面孔之间的共同特征是面部地标的存在和位置。在关键特征集部分,我们讨论了如何使用诸如 dlib、cv2 和 MTCNN 等库构建简单易用的面部地标检测模块。
对于我们当前的用例,我们将准备成对的训练样本,包括一对地标和其对应的图像/照片。要生成再现内容,我们只需提取源脸部/控制实体的面部地标,然后使用 pix2pix 基于目标人物生成高质量的实际输出。
在我们的情况下,源/控制人格可以是您或任何其他人,而目标人格是巴拉克·奥巴马。
为了准备我们的数据集,我们将从视频中提取每帧的帧和相应的标志。由于我们希望训练我们的网络能够基于标志输入生成高质量的彩色输出图像,我们需要一段 Barack Obama 的视频。您可以从互联网上的各种不同来源下载此视频。请注意,此练习再次仅用于学术和教育目的。请谨慎使用任何视频。
生成一个标志和视频帧的配对数据集是面部标志 部分给出的代码片段的直接应用。为了避免重复,我们将其留给读者作为练习。请注意,本书的代码仓库中提供了完整的代码。我们从 Barack Obama 的一次演讲中生成了近 400 个配对样本。图 8.17 展示了其中的一些样本:
图 8.17:由面部标志和相应视频帧组成的配对训练样本
我们可以看到标志如何捕捉头部的位置以及嘴唇、眼睛和其他面部标志的移动。因此,我们几乎可以立即生成配对的训练数据集。现在让我们继续进行网络设置和训练。
Pix2pix GAN 设置和训练
我们在第七章 用 GAN 进行风格转移 中详细讨论了 pix2pix 架构及其子组件和目标函数。在本节中,我们将简要介绍它们以确保完整性。
build_generator, that prepares the generator network:
def build_generator(img_shape,channels=3,num_filters=64): # Image input input_layer = Input(shape=img_shape) # Downsampling down_sample_1 = downsample_block(input_layer, num_filters, batch_normalization=False) # rest of the downsampling blocks have batch_normalization=true down_sample_2 = downsample_block(down_sample_1, num_filters*2) down_sample_3 = downsample_block(down_sample_2, num_filters*4) down_sample_4 = downsample_block(down_sample_3, num_filters*8) down_sample_5 = downsample_block(down_sample_4, num_filters*8) down_sample_6 = downsample_block(down_sample_5, num_filters*8) down_sample_7 = downsample_block(down_sample_6, num_filters*8) # Upsampling blocks with skip connections upsample_1 = upsample_block(down_sample_7, down_sample_6, num_filters*8) upsample_2 = upsample_block(upsample_1, down_sample_5, num_filters*8) upsample_3 = upsample_block(upsample_2, down_sample_4, num_filters*8) upsample_4 = upsample_block(upsample_3, down_sample_3, num_filters*8) upsample_5 = upsample_block(upsample_4, down_sample_2, num_filters*2) upsample_6 = upsample_block(upsample_5, down_sample_1, num_filters) upsample_7 = UpSampling2D(size=2)(upsample_6) output_img = Conv2D(channels, kernel_size=4, strides=1, padding='same', activation='tanh')(upsample_7) return Model(input_layer, output_img)
请注意,我们正在重用作为第七章 用 GAN 进行风格转移 的一部分准备的实用函数。与具有特定设置的生成器不同,pix2pix 的判别器网络是一个相当简单的实现。我们在以下片段中介绍了判别器网络:
def build_discriminator(img_shape,num_filters=64): input_img = Input(shape=img_shape) cond_img = Input(shape=img_shape) # Concatenate input and conditioning image by channels # as input for discriminator combined_input = Concatenate(axis=-1)([input_img, cond_img]) # First discriminator block does not use batch_normalization disc_block_1 = discriminator_block(combined_input, num_filters, batch_normalization=False) disc_block_2 = discriminator_block(disc_block_1, num_filters*2) disc_block_3 = discriminator_block(disc_block_2, num_filters*4) disc_block_4 = discriminator_block(disc_block_3, num_filters*8) output = Conv2D(1, kernel_size=4, strides=1, padding='same')(disc_block_4) return Model([input_img, cond_img], output) discriminator network uses repeating blocks consisting of convolutional, LeakyReLU, and batch normalization layers. The output is a *patch-GAN* kind of setup that divides the whole output into several overlapping patches to calculate fake versus real. The patch-GAN ensures high-quality outputs that feel more realistic.
我们使用这两个函数来准备我们的生成器、判别器和 GAN 网络对象。对象的创建如下片段所示:
IMG_WIDTH = 256 IMG_HEIGHT = 256 discriminator = build_discriminator(img_shape=(IMG_HEIGHT,IMG_WIDTH,3), num_filters=64) discriminator.compile(loss='mse', optimizer=Adam(0.0002, 0.5), metrics=['accuracy']) generator = build_generator(img_shape=(IMG_HEIGHT,IMG_WIDTH,3), channels=3, num_filters=64) source_img = Input(shape=(IMG_HEIGHT,IMG_WIDTH,3)) cond_img = Input(shape=(IMG_HEIGHT,IMG_WIDTH,3)) fake_img = generator(cond_img) discriminator.trainable = False output = discriminator([fake_img, cond_img]) gan = Model(inputs=[source_img, cond_img], outputs=[output, fake_img]) gan.compile(loss=['mse', 'mae'], loss_weights=[1, 100], optimizer=Adam(0.0002, 0.5))
训练循环很简单;我们利用三个网络对象(判别器、生成器和整体 GAN 模型),并交替训练生成器和判别器。请注意,面部标志数据集用作输入,而视频帧则是此训练过程的输出。训练循环如下片段所示:
def train(generator, discriminator, gan, patch_gan_shape, epochs, path='/content/data', batch_size=1, sample_interval=50): # Ground truth shape/Patch-GAN outputs real_y = np.ones((batch_size,) + patch_gan_shape) fake_y = np.zeros((batch_size,) + patch_gan_shape) for epoch in range(epochs): print("Epoch={}".format(epoch)) for idx, (imgs_source, imgs_cond) in enumerate(batch_generator(path=path, batch_size=batch_size, img_res=[IMG_HEIGHT, IMG_WIDTH])): # train discriminator # generator generates outputs based on # conditioned input images fake_imgs = generator.predict([imgs_cond]) # calculate discriminator loss on real samples disc_loss_real = discriminator.train_on_batch([imgs_source, imgs_cond], real_y) # calculate discriminator loss on fake samples disc_loss_fake = discriminator.train_on_batch([fake_imgs, imgs_cond], fake_y) # overall discriminator loss discriminator_loss = 0.5 * np.add(disc_loss_real, disc_loss_fake) # train generator gen_loss = gan.train_on_batch([imgs_source, imgs_cond], [real_y, imgs_source]) # training updates every 50 iterations if idx % 50 == 0: print ("[Epoch {}/{}] [Discriminator loss: {}, accuracy: {}] [Generator loss: {}]".format(epoch, epochs, discriminator_loss[0], 100*discriminator_loss[1], gen_loss[0])) # Plot and Save progress every few iterations if idx % sample_interval == 0: plot_sample_images(generator=generator, path=path, epoch=epoch, batch_num=idx, output_dir='images') with paired training examples. Pix2pix is a highly optimized GAN which requires very few resources overall. With only 400 samples and 200 epochs, we trained our landmarks-to-video frame GAN.
图 8.18 和 8.19 展示了此设置的训练进度:
图 8.18:用于面部再现的 pix2pix GAN 的训练进度(第 1 个纪元)
图 8.19:用于面部再现的 pix2pix GAN 的训练进度(第 40 个纪元)
正如我们在前面的图中所看到的,模型能够捕捉关键的面部特征及其位置,以及背景细节。在初始迭代中(图 8.18),模型似乎在生成嘴部区域方面遇到了困难,但随着训练的进行,它学会了用正确的一组细节填充它(图 8.19)。
现在我们已经为所需任务训练了我们的 GAN,让我们在下一节中进行一些再现。
结果和局限性
到目前为止,我们在本章中主要处理图像或照片作为输入。由于 pix2pix GAN 是一个非常高效的实现,它可以用来在几乎实时地生成输出。因此,这种能力意味着我们可以使用这样一个训练好的模型来使用实时视频来进行再现。换句话说,我们可以使用自己的实时视频来再现巴拉克·奥巴马的面部动作和表情。
get_landmarks and get_obama functions:
CROP_SIZE = 256 DOWNSAMPLE_RATIO = 4 def get_landmarks(black_image,gray,faces): for face in faces: detected_landmarks = predictor(gray, face).parts() landmarks = [[p.x * DOWNSAMPLE_RATIO, p.y * DOWNSAMPLE_RATIO] for p in detected_landmarks] jaw = reshape_for_polyline(landmarks[0:17]) left_eyebrow = reshape_for_polyline(landmarks[22:27]) right_eyebrow = reshape_for_polyline(landmarks[17:22]) nose_bridge = reshape_for_polyline(landmarks[27:31]) lower_nose = reshape_for_polyline(landmarks[30:35]) left_eye = reshape_for_polyline(landmarks[42:48]) right_eye = reshape_for_polyline(landmarks[36:42]) outer_lip = reshape_for_polyline(landmarks[48:60]) inner_lip = reshape_for_polyline(landmarks[60:68]) color = (255, 255, 255) thickness = 3 cv2.polylines(black_image, [jaw], False, color, thickness) cv2.polylines(black_image, [left_eyebrow], False, color, thickness) cv2.polylines(black_image, [right_eyebrow], False, color, thickness) cv2.polylines(black_image, [nose_bridge], False, color, thickness) cv2.polylines(black_image, [lower_nose], True, color, thickness) cv2.polylines(black_image, [left_eye], True, color, thickness) cv2.polylines(black_image, [right_eye], True, color, thickness) cv2.polylines(black_image, [outer_lip], True, color, thickness) cv2.polylines(black_image, [inner_lip], True, color, thickness) return black_image def get_obama(landmarks): landmarks = (landmarks/127.5)-1 landmarks = tf.image.resize(landmarks, [256,256]).numpy() fake_imgs = generator.predict(np.expand_dims(landmarks,axis=0)) return fake_imgs
这些函数帮助从给定帧中提取并绘制面部标志,并使用这些标志来使用 pix2pix GAN 生成彩色帧的输出。
下一步是使用这些函数处理实时视频并生成再现的输出样本。这种操作足够快速,以增强虚假内容的可信度。以下代码段展示了操作循环:
cap = cv2.VideoCapture(0) fps = video.FPS().start() k = 0 display_plots = True display_cv2 = True while True: k += 1 ret, frame = cap.read(0) if np.all(np.array(frame.shape)): frame_resize = cv2.resize(frame, None, fx=1 / DOWNSAMPLE_RATIO, fy=1 / DOWNSAMPLE_RATIO) gray = cv2.cvtColor(frame_resize, cv2.COLOR_BGR2GRAY) faces = detector(gray, 1) black_image = np.zeros(frame.shape, np.uint8) landmarks = get_landmarks(black_image.copy(),gray,faces) img_tgt = (landmarks/127.5)-1 img_tgt = tf.image.resize(img_tgt, [256,256]).numpy() obama = generator.predict(np.expand_dims(img_tgt,axis=0))[0] try: obama = 0.5 * obama + 0.5 gen_imgs = np.concatenate([np.expand_dims(cv2.cvtColor(rescale_frame(frame_resize), cv2.COLOR_RGB2BGR),axis=0), np.expand_dims(rescale_frame(obama),axis=0), np.expand_dims(rescale_frame(landmarks),axis=0)]) if display_plots: titles = ['Live', 'Generated', 'Landmarks'] rows, cols = 1, 3 fig, axs = plt.subplots(rows, cols) for j in range(cols): if j!=1: axs[j].imshow(gen_imgs[j].astype(int)) else: axs[j].imshow(gen_imgs[j]) axs[j].set_title(titles[j]) axs[j].axis('off') plt.show() if display_cv2: cv2.imshow('synthetic obama', cv2.cvtColor(gen_imgs[1], cv2.COLOR_BGR2RGB)) #cv2.imshow('landmark', rescale_frame(landmarks)) except Exception as ex: print(ex) fps.update() if cv2.waitKey(1) & 0xFF == ord('q'): break fps.stop() print('[INFO] elapsed time (total): {:.2f}'.format(fps.elapsed())) print('[INFO] approx. FPS: {:.2f}'.format(fps.fps())) cap.release() cv2.destroyAllWindows() using the pix2pix GAN. Upon executing the video capture and manipulation loop, we are able to generate some promising results. Some of the re-enactments are depicted in the following figure:
图 8.20: 使用实时视频作为源,奥巴马作为目标,使用 pix2pix GAN 进行再现
图 8.20展示了整体设置如何无缝工作。我们能够捕捉到实时视频,将其转换为面部标志,然后使用 pix2pix GAN 生成再现。在实时视频中,背景中没有物体,但我们的网络能够正确生成美国国旗。样本还展示了模型如何很好地捕捉表情和头部倾斜。
尽管结果令人鼓舞,但远远不能被认为是真实或可信的。以下是我们在本节讨论的方法中所涉及的一些限制:
- 图 8.20中的输出有点模糊。如果头部倾斜得太多,或者直播视频中的人离摄像头太近或太远,它们会完全变成空白或难以理解。这个问题主要是因为 pix2pix GAN 学会了相对大小和位置的面部标志物,相对于训练数据集。通过进行面部对齐并在输入和推理阶段使用更紧凑的裁剪,可以改善这一问题。
- 模型生成的内容高度依赖于训练数据。由于我们的训练数据集来自演讲,因此头部移动有限,面部表情也非常有限。因此,如果你试图移动头部太多或展示训练数据集中不存在的表情,模型会做出非常糟糕的猜测。更大的数据集和更多的变化可以帮助解决这个问题。
我们已经看到了一个强大的图像到图像翻译 GAN 架构可以被重新用于再现任务。
在前两节中,我们介绍了一些有趣的从零开始开发替换和再现架构的实际练习。我们讨论了我们的设置中的一些问题以及如何改进它们。在接下来的一节中,我们将讨论与深度伪造系统相关的一些挑战。
挑战
在本节中,我们将讨论与深度伪造架构相关的一些常见挑战,首先简要讨论与这项技术相关的道德问题。
道德问题
尽管生成虚假内容并不是一个新概念,但“deepfake”一词在 2017 年成为众所关注的焦点,那时 Reddit 用户 u/deepfakes 发布了用深度学习将名人面孔叠加在色情视频上的虚假视频。这些视频的质量和用户能够轻松生成它们的方式在全球新闻频道上掀起了轩然大波。很快,u/deepfakes 发布了一个名为FakeApp的易于设置的应用程序,使用户能够在对深度学习工作原理几乎一无所知的情况下生成此类内容。这导致了大量虚假视频和令人反感的内容。结果,人们开始对涉及身份盗用、冒充、假新闻等问题产生关注。
很快,学术界对此产生了浓厚兴趣,这不仅有助于改善技术,还坚持其道德使用。尽管一些恶意和令人反感的内容创作者利用这些技术,但也有许多工业和研究项目正在进行中,以检测此类虚假内容,如微软的深度伪造检测工具和 Deepware。^(14 15)
技术挑战
尽管道德问题暂且不提,让我们也讨论一下典型深度伪造设置中明显存在的一些挑战:泛化、遮挡和时间问题。
泛化
深度伪造架构本质上是生成式模型,高度依赖于使用的训练数据集。这些架构通常也需要大量的训练样本,这可能很难获得,特别是对于目标(或在恶意使用的情况下的受害者)而言。另一个问题是配对的训练设置。通常针对一个源和目标配对训练的模型不易用于另一对源和目标人物。
研究的一个活跃领域是致力于全新高效架构的开发,这些架构能够使用更少的训练数据。CycleGAN 和其他无配对翻译架构的发展也有助于克服配对训练的瓶颈。
遮挡
源或目标输入可能围绕它们存在妨碍某些特征的残留物。这可能是由于手部运动、头发、眼镜或其他物体造成的。另一种遮挡发生在口部和眼部区域的动态变化上。这可以导致不一致的面部特征或奇怪的裁剪图像。某些作品正在致力于通过使用分割、修补和其他相关技术来避免这些问题。其中一项作品的例子是 Siarohin 等人的First Order Motion Model for Image Generation¹⁶
时间问题
深度伪造架构基于逐帧处理(对于视频输入)。这导致了视频帧之间的抖动、闪烁,或者完全不连贯。我们在上一节使用 pix2pix GAN 进行再现练习时看到了一个例子。该模型无法为未见过的场景生成连贯的输出。为了改进这一点,一些研究人员正尝试使用带有 GANs 的 RNNs(循环神经网络)来生成连贯的输出。这方面的例子包括:
- MoCoGAN:分解运动和内容以进行视频生成¹⁷
- Video-to-Video Synthesis¹⁸
现成的实现
在本章中,我们介绍了一种逐步开发用于替换和再现的两种不同深度伪造架构的方法。尽管这些实现易于理解和执行,但它们需要相当多的理解和资源来生成高质量的结果。
自 2017 年发布 u/deepfakes 内容以来,已经推出了许多开源实现以简化这项技术的使用。尽管危险,这些项目大多强调了道德意义和警告开发人员以及普通用户不要恶意采用这些项目。虽然这超出了本章的范围,但我们在本节列举了一些设计良好并受欢迎的实现。鼓励读者查看特定项目以获取更多细节。
- FaceSwap¹⁹ 该项目的开发人员声称这一实现接近 u/deepfakes 的原始实现,并经过多年的改进以提高输出内容质量。该项目提供了详细的文档和逐步指南,用于准备训练数据集和生成虚假内容。他们还分享了用于加速培训过程的预训练网络。该项目拥有一个图形界面,适合完全新手用户。
- DeepFaceLab²⁰ 这是互联网上最全面、详细和受欢迎的深伪造项目之一。该项目基于 2020 年 5 月提出的同名论文。该项目包括详细的用户指南、视频教程、非常成熟的 GUI、预训练模型、Colab 笔记本、数据集,甚至是用于快速部署的 Docker 镜像。
- FaceSwap-GAN²¹ 采用了一种简单而有效的技术,使用了 ED+GAN 设置。该项目提供了用于快速训练自己模型的实用程序和现成的笔记本。该项目还提供了预训练模型,供直接使用或迁移学习。
有许多 Android 和 iOS 应用程序都以同样的方式工作,并将入门门槛降到最低。如今,几乎任何拥有智能手机或一点技术概念的人都可以轻松使用或训练这些设置。
摘要
Deepfakes在伦理和技术上都是一个复杂的课题。在本章中,我们首先讨论了 deepfake 技术总体情况。我们概述了 deepfakes 的内容以及简要介绍了一些有益的和恶意的使用案例。我们详细讨论了不同 deepfake 设置的不同操作模式以及这些操作模式如何影响生成内容的整体可信度。虽然 deepfakes 是一个与视频、图像、音频、文本等等相关的全面术语,但在本章中我们只关注视觉使用案例。
在我们的范围内,我们讨论了在这一领域中不同作品中利用的各种特征集。特别是,我们讨论了面部表情编码系统(FACS)、3D 可塑模型(3DMM)和面部标志。我们还讨论了如何使用诸如 dlib 和 MTCNN 之类的库进行面部标志检测。然后,我们介绍了一种要在 deepfakes 管道中执行的高级任务流程。与此同时,我们还讨论了一些开发这种系统所广泛使用的常见架构。
本章的第二部分利用这一理解,提出了两个实际操作的练习,从零开始开发 deepfake 管道。我们首先致力于开发基于自动编码器的人脸交换架构。通过这项练习,我们逐步进行了准备数据集、训练网络,并最终生成交换输出的步骤。第二个练习涉及使用 pix2pix GAN 执行重新演绎,将实时视频用作源,将巴拉克·奥巴马用作目标。我们讨论了每个实施的问题以及克服这些问题的方法。
在最后一节中,我们提出了有关 deepfake 架构相关的伦理问题和挑战的讨论。我们还简要介绍了一些流行的现成项目,这些项目允许任何拥有计算机或智能手机的人生成虚假内容。
我们在本章中涵盖了很多内容,并且涉及一些非常令人兴奋的使用案例。重要的是要再次强调,当我们使用这样强大的技术时,要非常小心。所涉及的影响和后果对涉及的实体可能非常危险,因此我们应该注意如何使用这些知识。
虽然本章主要关注视觉方面,但我们将转变方向,接下来的两章将讨论文本内容。自然语言处理领域涌现了一些激动人心的研究和应用案例。我们将重点关注一些开创性的文本生成作品。敬请期待。
参考文献
- BuzzFeedVideo. (2018 年 4 月 17 日)。 你绝对不会相信这个视频中奥巴马说了什么!😉 [视频]. YouTube。
www.youtube.com/watch?v=cQ54GDm1eL0&ab_channel=BuzzFeedVideo
- Lee, D. (2019 年 5 月 10 日)。 Deepfake 萨尔瓦多·达利与博物馆参观者自拍. The Verge.
www.theverge.com/2019/5/10/18540953/salvador-dali-lives-deepfake-museum
- Malaria Must Die. (2020 年)。 没有疟疾的世界. Malaria Must Die.
malariamustdie.com/
- Lyons, K. (2020 年 2 月 18 日)。 一名印度政治家使用 AI 将演讲翻译成其他语言,以吸引更多选民. The Verge.
www.theverge.com/2020/2/18/21142782/india-politician-deepfakes-ai-elections
- Dietmar, J. (2019 年 5 月 21 日)。 GAN 和深假技术可能会引领时尚业的革命。福布斯。
www.forbes.com/sites/forbestechcouncil/2019/05/21/gans-and-deepfakes-could-revolutionize-the-fashion-industry/?sh=2502d4163d17
- Statt, N. (2020 年 8 月 27 日)。 罗纳德·里根在最新的使命召唤:黑色行动冷战预告片中派你去犯战争罪。The Verge。
www.theverge.com/2020/8/27/21403879/call-of-duty-black-ops-cold-war-gamescom-2020-trailer-ronald-reagan
- Cole, S. (2017 年 12 月 11 日)。 AI 辅助的深假色情已经出现,我们都完蛋了。Vice。
www.vice.com/en/article/gydydm/gal-gadot-fake-ai-porn
- dfaker & czfhhh. (2020 年). df. GitHub 代码库.
github.com/dfaker/df
- Pumarola, A., Agudo, A., Martinez, A.M., Sanfeliu, A., & Moreno-Noguer, F. (2018 年). GANimation: 来自单个图像的解剖学感知面部动画。ECCV 2018。
arxiv.org/abs/1807.09251
- Naruniec, J., Helminger, L., Schroers, C., & Weber, R.M. (2020 年). 用于视觉效果的高分辨率神经面部交换。Eurographics 渲染研讨会 2020。
s3.amazonaws.com/disney-research-data/wp-content/uploads/2020/06/18013325/High-Resolution-Neural-Face-Swapping-for-Visual-Effects.pdf
- Geng, Z., Cao, C., & Tulyakov, S. (2019). 3D 引导的细粒度面部操作。arXiv。
arxiv.org/abs/1902.08900
- Blanz, V., & Vetter, T. (1999). 用于合成 3D 面部的可塑模型。SIGGRAPH '99:第 26 届计算机图形学和交互技术年会。187-194。
cseweb.ucsd.edu/~ravir/6998/papers/p187-blanz.pdf
- Kaipeng,Z.,Zhang,Z.,Li,Z.和 Qiao,Y. (2016). 使用多任务级联卷积网络进行联合面部检测和对齐。IEEE 信号处理通信(SPL),第 23 卷,第 10 期,pp. 1499-1503,2016。
kpzhang93.github.io/MTCNN_face_detection_alignment/
- Burt, T., & Horvitz, E. (2020 年 9 月 1 日). 打击虚假信息的新步骤。微软博客。
blogs.microsoft.com/on-the-issues/2020/09/01/disinformation-deepfakes-newsguard-video-authenticator/
- Deepware。 (2021). Deepware - 使用简单工具扫描和检测深度伪造视频。
deepware.ai/
- Siarohin, A., Lathuiliere, S., Tulyakov, S., Ricci, E., & Sebe, N. (2019). 图像动画的一阶运动模型。NeurIPS 2019。
aliaksandrsiarohin.github.io/first-order-model-website/
- Tulyakov, S., Liu, M-Y., Yang, X., & Kautz, J. (2017). MoCoGAN:视频生成的运动和内容分解。arXiv。
arxiv.org/abs/1707.04993
- Wang, T-C., Liu, M-Y., Zhu, J-Y., Liu, G., Tao, A., Kautz, J., Catanzaro, B. (2018). 视频到视频的合成。NeurIPS,2018。
arxiv.org/abs/1808.06601
- torzdf 和 77 位其他贡献者。 (2021). faceswap。GitHub 仓库。
github.com/Deepfakes/faceswap
- iperov 和其他 18 位贡献者。 (2021). DeepFaceLab。GitHub 仓库。
github.com/iperov/DeepFaceLab
- shaoanlu,silky,clarle 和 Ja1r0。 (2019). faceswap-GAN。GitHub 仓库。
github.com/shaoanlu/faceswap-GAN