本文经授权转自公众号:读芯术
目前,人脸识别的使用率正在不断上升,随之而来关于面部识别道德问题的争论也愈发激烈。从机场到社交媒体,面部识别的应用无处不在。因此,想让自己的脸不被扫描几乎是不可能的。
对面部识别的理想化攻击是看起来毫不知情的伪装。在Hyperface项目的启发下,本文将介绍一个可穿戴式对抗实例的全过程——详细介绍创建对抗图象愚弄选定类型的面部识别,以及如何在面罩上实现实例演示。
面部检测 VS 面部识别
面部检测示例(左)vs面部识别示例(右)
对此项目进行深入研究之前,很有必要先明确一下面部检测与面部识别之间的区别。面部检测指的是检测图象中是否出现人脸的能力。而面部识别首先依靠面部检测确定图象中是否出现了人脸,然后进一步确定是谁。
对此项目,我们选择主攻面部检测,这主要是因为它更易于检测。为了更好地测试面部识别,选用面部识别数据库最好不过了。
面部检测模型
下一步是选择建立对抗实例的面部检测模型。目前使用的面部检测模型有很多。Vikas Gupta在“Learn OpenCV”平台上对面部检测模型及其应用做了全面深入的入门介绍。在此,仅进行简要回顾。
深度神经网络(DNNs):深度神经网络可以使用输入的数据集进行训练,以在各个不同的方向检测人脸。基于人脸检测的一种流行的深度神经网络检测方法是单次多盒检测器。深度神经网络准确性高且具有普适性。
卷积神经网络(CNNs):卷积神经网络是一种深度神经网络,旨在标注出图象中不同部分的重要程度。它十分强大,但是在CPU上运行很慢。
Haar级联分类器:Haar级联使用一个数据集和大量标记过的积极和消极示例图片进行训练。Haar级联分类器的主要缺点是只能识别正面照片。由于神经网络更为通用,所以它们无法广泛应用。
定向梯度直方图(HOG):定向梯度直方图是一种面部检测方法,在将结果输入支持向量机之前,将经过处理的输入图象划分为具有梯度方向的单元格。定向梯度直方图检测快速轻便,但不适用于某些不常见的的面部角度。
将人脸建模为dlib中定向梯度直方图的示例(感觉可以删去数据库,我查了一下感觉和数据库有区别)
最简单的攻击候选模型是定向梯度直方图。最值得注意的是,定向梯度直方图的预期输入易于可视化并反馈到面部检测模型中。定向梯度直方图还有一个优点就是,它的面部可视化结果不是人类观察者轻易能看出来的人脸。
Python中的定向梯度直方图面部检测
附带的GitHub储存库中提供了具有显示结果功能的扩展代码示例。
GitHub库传送门:https://github.com/BruceMacD/Adversarial-Faces
测试这些例子需要一个基于定向梯度直方图(HOG)进行简单的面部检测。幸运地是,dlib库中的正面人脸探测器内置了HOG面部检测器。
import dlib
import cv2
cv2.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表示所输入图片会放大一次。放大令图像变得更大,并且便于检测人脸。正面人脸检测的结果是一个边界框列表,每个边界框对应一个被检测的人脸。
可视化HOG中使用dlib库检测人脸的结果
运用随机优化创建对抗设计
由于认识到目前HOG预期输入的可视化内容会被检测为正面人脸假阳性,所以需要创建一个设计,打印在看起来不显眼的口罩上。然而,影响设计的因素有很多,我们并不知道如何优化。人脸的位置、方向和大小都会影响图像中检测到的面部数量。也可以尝试不同的设计,直到找到一个好的设计,但是让学习模型使得这些艰苦工作做起来更加有趣。
研究了强化学习、生成对抗网络和Q—学习后,最终决定使用随机优化的模拟退火,因为它最适合解决我们的问题,能找到与dlib库检测到最多次的人脸相匹配的输入。
使用Python图象库(PIL)和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])
从初始状态开始,milrose需要一个一维数组。这意味着必须使用一个黑客解决方案,赋予不同的数组位置不同的意义(详见索引解释)。选择优化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))))
利用适应度和初始状态集配置milrose进行模拟退火很简单。分配输入内容,并让它运行直到找到一个最佳结果。做几次这样的测试,以找到视觉上看起来有趣的结果。
模拟退火状态下有趣的输出结果
最后,在这个有趣的输出中添加了一些最后的润色,以模糊面部设计——其意图是愚弄人类。
模糊面部结构的最终设计
在面罩上测试此设计
使用对抗面部设计的原型面罩上检测到的面部
完成最终的设计后,我设计了一些模拟面具来测试HOG面部检测如何评估它们。初步结果似乎很有希望,上述设计返回结果始终为4-5个错误检测到的面孔。