,也就是恢复受损图像或填充缺失部分的过程。下面就是图像修复的例子:输入的图像有一些白色缺失,经过处理这些缺失被补足了。
解决完定义的问题后我们再提一点:除了本文之外,我们还准备了一个 GitHub 帐户,其中包含你需要的所有内容实现,以及 Jupyter Notebook“mask2face.ipynb”,你可以在其中运行本文提到的所有内容。只需单击几下,即可训练你自己的神经网络。
首先,我们来为Python项目准备虚拟环境。你可以使用你喜欢的任何虚拟环境,只要确保从 environment.yml 和 requirements.txt 安装所有必需的依赖项即可。如果你熟悉 Conda,还可以在克隆的 GitHub 项目目录中运行以下命令来初始化 Conda 环境:
现在,你已经有了一个带有所有必需依赖项的环境,接下来我们来定义目标和目的。对于这个项目,我们想要创建一个 ML 模型,该模型可以向我们展示戴口罩的人摘下口罩的样子。我们的模型有一个输入——戴口罩的人的图像;一个输出——摘下口罩的人的图像。
我们从一个带有预先计算的面部界标的面部数据集开始,该数据集是通过口罩生成器处理的,它使用这些界标将口罩放在脸上。现在我们有了带有成对图像(戴和不戴口罩)的数据集,我们就可以继续定义 ML 模型的架构了。管道的最后一部分是找到最佳损失函数和组成各个部分的所有必要脚本,以便我们可以训练和评估模型。
要想训练这个深度学习模型,我们需要采用大量数据,也就是大量输入和输出的图像对。当然,要收集每个人戴口罩 / 不戴口罩的输入和输出图像是不切实际的。
当前,市面上有很多人脸图像数据集,主要用于训练人脸。我们可以采用这样的数据集,在人脸上绘制口罩——于是我们就有了图像对。
我们尝试了两个数据集。其中一个数据库是马萨诸塞大学[1] 的现实世界人脸标记数据集。这里是它的 104MB gzip 压缩 tar 文件,其中包含整个数据集,超过 5,000 张图片。这个数据集非常适合我们的情况,因为它包含的图像主要都是人脸。但对于最终结果,我们使用了 CelebA 数据集,它更大(200,000 个样本),并且包含更高质量的图像。
接下来,我们需要定位面部界标,以便将口罩放置在正确的位置。为此,我们使用了一个预训练的 dlib 面部界标检测器。你可以使用其他任何类似的数据集,只要确保你可以找到预计算的面部界标点或自己计算界标。
一开始,我们做了一个口罩生成器的简单实现,将一个多边形放置在脸上,使多边形顶点与面部界标的距离随机化。这样我们就可以快速生成一个简单的数据集,并测试项目背后的想法是否可行。一旦确定它确实有效,我们就开始寻找一种更强大的解决方案,以更好地反映现实场景。
GitHub 上有一个很棒的项目 Mask The Face,已经解决了口罩生成问题。它从脸部界标点估计口罩位置,估计脸部倾斜角度以从数据库中选择最合适的口罩,最后将口罩放置在脸上。可用的口罩数据库包括了手术口罩、有各种颜色和纹理的布口罩、几种呼吸器,甚至是防毒面罩。
现在我们已经准备好了数据集,是时候搭建深度神经网络模型架构了。在这项工作中,绝对没有人可以声称有一个客观的“最佳”选项。
选择合适架构的过程总是取决于许多因素,例如时间要求(你要实时处理视频还是要离线预处理一批图像?)、硬件需求(模型应在搭载高性能GPU的群集上运行,还是要在低功耗移动设备上运行?)等等。每次你都要寻找正确的参数,并针对你的具体情况进行设置。
卷积神经网络(CNN)是一种利用卷积核过滤器的神经网络架构。它适用于各种问题,例如时间序列分析、自然语言处理和推荐系统,但主要用于各种图像相关的用途,例如对象分类、图像分割、图像分析和图像修复。
CNN 的核心是能够检测输入图像视觉特征的卷积层。当我们一层层堆叠多个卷积层时,它们倾向于检测不同的特征。第一层通常会提取更复杂的特征,例如边角或边缘。当你深入 CNN 时,卷积层将开始检测更高级的特征,例如对象、面部等。
上图显示了用于图像检测的 CNN 示例。这并不是我们要解决的问题,但 CNN 架构是任何修复架构的必要组成部分。
在讨论修复架构之前,我们先来谈谈起作用的构建块,它们称为 ResNet 块或残差块。在传统的神经网络或 CNN 中,每一层都连接到下一层。在具有残差块的网络中,每一层也会连接到下一层,但还会再连接两层或更多层。我们引入了 ResNet 块以进一步提高性能,后文会具体介绍。
神经网络能够估计任何函数,我们可以认为增加层数可以提高估计的准确性。但由于诸如梯度消失或维数诅咒之类的问题,层数增加到一定程度就不会继续提升性能了,甚至会让性能。这就是为什么有很多研究致力于解决这些问题,而性能最好的解决方案之一就是残差块。
残差块允许使用跳过连接或标识函数,将信息从初始层传递到下一层。通过赋予神经网络使用标识函数的能力,相比单纯地增加层数,我们可以构建性能更好的网络。你可以在参考资料中阅读有关 ResNet 及其变体的更多信息。[8]
编码器 - 架构由两个单独的神经网络组成:编码器提取输入(嵌入)的一个固定长度表示,而从该表示生成输出。
你会注意到,编码器部分与上一节中描述的 CNN 非常相似。经过验证的分类 CNN 通常用作编码器的基础,甚至直接用作编码器,只是没有最后一个(分类)层。这个架构可以用来生成新图像,这正是我们所需要的。但它的性能却不是那么好,因此我们来看一下更好的东西。
U-net 最初是为图像分割而开发的卷积神经网络架构[2],但它在其他许多任务(例如图像修复或图像着色)中也展示了自己的能力。
我们前面之所以提到 ResNet 块有一个重要原因。事实上,将 ResNet 块与 U-net 架构结合使用对整体性能的影响最大。你可以在下图中看到添加 ResNet 块的架构。
当你将上面的 U-net 架构与上一节中的编码器 - 架构进行比较时,它们看起来非常相似,但有一个关键的区别:U-net 实现了一种称为“跳过连接”的功能,该功能将 identity 从反卷积块传播到另一侧对应的上采样块(上图中的灰色箭头)。这是对编码器 - 架构的两处显著改进。
首先,已知跳过连接可以加快学习过程并帮助解决梯度消失问题[5];其次,它们可以将信息从编码器直接传递到,从而有助于减少下采样期间的信息丢失。我们可以认为它们能传播我们希望保持不变的口罩外部图像的所有部分,同时还有助于生成口罩下面的脸部图像。
这正是我们所需要的!跳过连接将有助于保留我们要传播到输出的部分输入,而 U-net 的编码器 - 部分将检测到口罩并将其替换为下面的嘴部图像。
如何选择损失函数是你需要解决的最重要的问题之一,使用正确的损失函数可能会得到性能出色的模型,反之就会得到令人失望的模型。这就是我们花很多时间选择最佳模型的原因所在。下面,我们来讨论几种选项。
MSE 和 MAE 都是损失函数,都基于我们模型生成的图像将口罩应用到面部之前。这似乎正是我们所需要的,但我们并不打算训练可以像素级完美重现口罩下隐藏内容的模型。
我们希望我们的模型理解口罩下面是嘴巴和鼻子,甚至可能要理解来自那些未被隐藏的事物(例如眼睛)所包含的情感,从而生成悲伤的、快乐的或可能是惊讶的面孔。这意味着,即使不能完美地捕获每个像素,实际上也可以产生一个很好的结果;更重要的是,它可以学习如何在任何脸部上泛化,而不仅仅是对训练数据集中的面孔进行泛化。
SSIM 是用于度量两个图像之间相似度的度量标准,由 Wang 等人在 2004 年提出[3]。它专注于解决 MSE/MAE 所存在的问题。它提供了一个数值表达式,用来展示两张图像之间的相似度。它通过对比图像之间的三个测量值来做到这一点:亮度、对比度和结构。最终得分是所有三个测量值的加权组合,分数从 0 到 1,1 表示图像完全相同。
MSE 存在的问题:左上图是未经修改的原始图像;其他图像均有不同形式的失真。原始图像与其他图像之间的均方误差大致相同(大约 480),而 SSIM 的变化很大。例如,模糊图像和分割后的图像与原始图像的相似度绝对不如其他图像,但 MSE 几乎相同——尽管面部特征和细节丢失了。另一方面,偏色图像和对比度拉伸的图像与人眼中的原始图像非常相似(SSID 指标也是一样),但 MSE 表示不同意这个结论。
我们使用 ADAM 优化器和 SSIM 损失函数,通过 U-net 架构对模型进行训练,将数据集分为测试部分(1,000 张图像)、训练部分(其余 80%的数据集)和验证部分(其余 80%的数据集)。我们的第一个实验为几张测试图像生成了不错但不太清晰的输出图像。是时候尝试使用架构和损失函数来提高性能了,下面是我们尝试的一些更改:卷积过滤器的层数和大小。
更多的卷积过滤器和更深的网络意味着更多的参数(大小为[8、8、256] 的 2D 卷积层具有 59 万个参数,大小为[4、4、512] 的层具有 230 万个参数)和更多的训练时间。由于每层中过滤器的深度和数量是我们模型架构的构造器的输入参数,因此使用不同的值进行实验非常容易。
如你所见,给定的网络在我们的测试数据上生成了很好的结果。这个网络具有泛化能力,并且似乎可以很好地识别情绪,从而生成微笑或悲伤的面孔。另一方面,这里当然也有改进的空间。
虽说使用 ResNet 块的 U-net 网络效果很好,但我们也可以看到生成的摘口罩图像不是很清晰。一种解决方法是用一个提炼网络扩展我们的网络,如[4] 中和下图中所述。此外还可以进行其他一些改进。
根据我们实验中获取的经验。数据集的选择可以对结果产生重大影响。下一步,我们将合并不同的数据集以使样本具有更大的多样。