用时尚的口罩迷惑人脸识别算法 | 翻译征文 | 雷锋字幕组

null

Pavel Anoshin拍摄并发布于Unsplash

Accompanying GitHub repository: //github.com/BruceMacD/Adversarial-Faces

人脸识别的使用越来越多。伴随着最近关于人脸识别伦理的争论,对人脸识别算法进行潜在的对抗攻击萦绕在我的脑海中。从机场到社交媒体,人脸识别无处不在。你几乎不可能逃过各种人脸扫描。

一个理想的对人脸识别的攻击像是一件在不明真相的人看来并不显眼的衣服。在Hyperface项目的启发下,我决定研究并实现一个可穿戴对抗的例子。在这篇文章中,我将详细介绍创建一个对抗图像来欺骗选定类型的人脸检测算法的过程,以及我如何在一个口罩上实现一个实际的例子。

人脸检测 vs. 人脸识别

null人脸检测(左)与人脸识别(右)的对比

在深入研究这个项目之前,首先要注意的是人脸检测和人脸识别之间的区别。人脸检测指的是在存在人脸的图像中发现人脸的能力。人脸识别依赖于人脸检测,但更进一步,试图确认这是谁的脸。

对于这个项目,我将关注人脸检测。主要是因为人脸检测更容易测试。要正确进行人脸识别测试,访问人脸数据库是一个常见的方法。

人脸检测模型

下一步是选择在其上建立对抗样本的人脸检测模型。目前有很多不同的人脸检测模型在使用。Vikas Gupta 在“学习 OpenCV”上对人脸检测模型及其实现进行了深入的解释。我在这里简单介绍一下。

人脸检测-OpenCV, Dlib和深度学习|学习OpenCV

  • 深度神经网络(DNNs): 深度神经网络可以使用输入数据集训练神经网络,以检测不同方向的人脸。一种常用的基于深度神经网络的人脸检测的方法是SSD(Single Shot MultiBox Detector)模型。深度神经网络准确且通用。

  • 卷积神经网络(CNN): 卷积神经网络是一种深度神经网络,设计用来对图像的不同部分赋予重要性。它是健壮的,但在 CPU 上相当慢。

  • 哈尔级联分类器(Haar Cascade Classifiers): 哈尔级联分类器使用带有大量标记的正例和负例图像的数据集进行训练。哈尔级联分类器的主要缺点是它们只能识别正面人脸。由于神经网络更加通用,哈尔级联分类器不再被广泛使用。

  • 方向梯度直方图(HOG): HOG 是将经过处理的输入图像分割成具有方向梯度的单元,然后将结果送入支持向量机的一种人脸检测方法。HOG 检测是快速和轻量级的,但对一些不常见的人脸角度不起作用。

null

来自dlib的一个基于方向梯度直方图的人脸建模示例

最简单的攻击候选模型是方向梯度直方图。最值得注意的是,HOG 的期望输入可以很容易地可视化并反馈到面部检测模型中。将一张脸可视化为一个有方向梯度的直方图也有一个优点,那就是对于人类观察者来说,它不是一张明显的脸。

Python 中基于方向梯度直方图的人脸检测

Note: Expanded code samples with the functionality to display results are available on the accompanying GitHub repository.

为了测试这些例子,我需要一个简单的基于 HOG 的人脸检测实现。幸运的是,dlib 库在其frontal_face_detector中内置了一个 HOG 面部检测器。

import dlib  
import cv2cv2.imread("path/to/input_img.png")  
frontal_face_detector = dlib.get_frontal_face_detector()  
upscaling_factor = 1  
detected_faces = frontal_face_detector(img, upscaling_factor)

该正面人脸检测器运行时使用一张输入图像和一个放大因子。放大因子为 1 表示输入图像将被放大一次。放大后的图像更大,更容易识别人脸。正面人脸检测的结果是一个边界框列表,每个边界框对应一个被检测到的人脸。

null

使用 dlib 在我们的可视化 HOG 中检测人脸的结果

通过可视化 HOG 的预期输入,您可以看到它被检测为一张脸。太棒了!我们有了进行对抗攻击的基础。

使用随机优化创建对抗性设计

现在我知道可视化的 HOG 的预期输入是将被检测为假阳性的正向的脸部,即我创建的一个打印在口罩上的看起来不明显的设计。但是影响设计的因素还有很多,我不知道如何去优化。人脸的位置、方向和大小都会影响图像中检测到的人脸数量。我本可以简单地尝试不同的设计,直到找到一个好的设计,但是让一个学习模型为我做困难的工作似乎更有趣且不那么乏味。

我考虑了几个不同的模型来寻找最优的输入。我研究了强化学习、生成对抗网络和 Q-learning。最终我决定使用带有随机优化的模拟退火算法,因为它最适合我的问题,即找到可以使 dlib 检测到最多人脸的相对应的输入。我使用 PIL (Python Imaging Library)和 mlrose(一个用于随机优化的 Python 库)来生成一个图像并找到最佳状态。使用 mlrose 进行优化需要初始状态和适应度函数。在我的例子中,找到这个最优状态需要非常大量的计算,因为生成的状态需要以图像的形式保存到磁盘中,以便找到检测到的人脸数量。

# indexes:
# 0 % 4 = pos_x
# 1 % 4 = pos_y
# 2 % 4 = rotation
# 3 % 4 = scale
initial_state = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

从初始状态开始,mlrose 需要一个一维数组(据我所知)。这意味着我不得不使用一些简单的解决方案来给不同的数组位置赋予不同的意义(参见索引解释)。我选择优化一个 6 个人脸的输入,因为我总是可以复制设计来增加它的大小。

def detected_max(state):
    # converts the 1D state array into images
    get_img_from_state(state)
    return len(detect_faces(cv2.imread(OUTPUT)))

我的适应度函数只是将状态转换为图像,然后检测图像中的人脸数量。人脸的数量越多,适应度越高。我还尝试修改适应度函数,使其根据输入的 HOG 人脸图像的大小变得更高。这可能更好,因为在现实生活中更容易发现大的人脸。然而,我发现把脸的大小考虑进去会导致更长的计算时间,得到的结果在视觉上却是相似的。

fitness = mlrose.CustomFitness(detected_max)
problem = mlrose.DiscreteOpt(length=24, fitness_fn=fitness,
                             maximize=True, max_val=scale_factor)
schedule = mlrose.ExpDecay()
best_state, max_faces = mlrose.simulated_annealing(problem, schedule=schedule, max_attempts=10, max_iters=1000,
                                          init_state=initial_state, random_state=1)
print('Optimal state found: ', best_state)
print('Max fitness found: ', max_faces)
# save the optimal found
get_img_from_state(best_state)
print("Number of faces in output: ", len(detect_faces(cv2.imread(OUTPUT))))

有了适应度和初始状态集,配置 mlrose 进行模拟退火就很简单了。我只需要指定我们的输入,让它运行,直到找到一个最优的结果。我运行了几次找到了一个视觉上有趣的结果。

null模拟退火算法的一个有趣的输出

最后,通过这个有趣的输出,我添加了一些最后的润色来模糊它的面部设计。我认为用人工方法做这件事更合适,因为我的意图是欺骗人类。

null 最终的设计模糊了面部结构

测试口罩的设计

null

在一个原型口罩上检测到的人脸,口罩上覆盖着对抗性人脸的设计

最终设计完成后,我创建了一些模拟的面具设计,以测试它们是如何被 HOG 人脸检测算法评估的。最初的结果似乎很乐观。上述设计一致返回 4-5 个错误检测的人脸。

原文链接