面部识别:案例研究

489 阅读35分钟

什么是面部识别?

面部识别是一种生物识别技术,它使用人工智能 (AI) 和机器学习算法来分析和识别人脸的独特特征,例如眼睛之间的距离、下巴的形状和嘴唇的弧度. 近年来,这项技术变得越来越流行,并被用于各种应用程序,包括安全系统、社交媒体和移动设备。

面部识别的工作原理是捕捉人脸的图像或视频,并将其与已知面孔的数据库进行比较,以确定是否匹配。该技术可用于多种用途,从解锁智能手机到识别刑事调查中的嫌疑人。

然而,面部识别技术引起了人们对隐私和公民自由的担忧,因为它有可能被用于大规模监控,并可能导致误报和其他错误。因此,谨慎对待面部识别工具的开发和使用并考虑其实施的伦理影响非常重要。

传统的面部识别方法

最初,我确实阅读了Lalitha 人脸识别技术分析案例研究中的这篇案例研究论文。P、Dr.R.Nedunchelianr和我了解了面部识别工作的基本知识以及涉及的步骤。此外,这篇论文帮助我增强了面部识别方法的知识,该论文名为“面部识别技术研究:调查 Madan Lal、Kamlesh Kumar、Karachi、R​​afaqat Hussain Arain、Abdullah Maitlo、Sadaqat Ali Ruk、Hidayatullah Shaikh”

image.png

原始论文中的图像……

人脸识别流程图

image.png

面部识别的典型阶段。

第一个实现:特征脸。

特征脸是一种基于主成分分析 (PCA) 的面部识别技术,主成分分析是一种通过寻找最重要的特征或成分来降低数据维数的数学方法。在特征脸的情况下,该技术用于通过分析一组训练图像的特征来识别和识别人脸。

要创建特征脸,首先要收集并规范化大量面部图像数据集,这涉及对齐和缩放图像以确保它们具有一致的方向和大小。然后将图像转换为灰度并表示为像素值矩阵。

接下来,将 PCA 算法应用于人脸图像矩阵,以找到最重要的特征或成分,称为特征向量。这些特征向量本质上是一组特征性面部特征,例如眼睛、鼻子和嘴巴的位置和形状,这些特征在面部图像数据集中很常见。

一旦识别出特征向量,就可以将它们组合起来形成一组特征脸,这些特征脸本质上是合成图像,代表整个数据集中最重要的面部特征。然后可以使用这些特征脸来识别人脸,方法是将它们与测试图像进行比较并识别与测试图像中的特征最匹配的特征脸。

特征脸已广泛用于面部识别应用,例如安全系统和访问控制,并且即使在不同的光照条件和面部表情变化的情况下也能有效地识别面部。然而,重要的是要注意,特征脸可能无法有效地识别外观有显着变化的人脸,例如发型或面部毛发的变化。

image.png

PCA 如何转换数据的代表性图像

EigenFaces涉及的步骤:

  1. 数据收集:Eigenfaces 的第一步是收集一组面部图像,用于训练算法。这些图像应该被归一化,这样所有的脸都有相同的大小和方向。
  2. 预处理:下一步是预处理图像以减少需要分析的数据量。这是通过将每个图像转换为灰度图像,然后应用平滑滤波器来消除任何噪声来完成的。
  3. 特征提取:在这一步中,算法从每个图像中提取一组特征。这些特征是区分一张脸和另一张脸的最重要特征。一种常见的方法是使用一种称为主成分分析 (PCA) 的技术来提取这些特征。
  4. PCA:PCA 是一种分析数据的协方差矩阵以找到最大方差方向的技术。在特征脸的情况下,它用于查找训练集中人脸变化最大的方向。这些方向称为主成分。
  5. 特征脸:上一步得到的主成分称为特征脸。它们代表面孔变化最大的方向。每个特征脸都是一个向量,可以将其视为面部特定特征的模板。
  6. 人脸识别:为了识别人脸,算法首先使用 PCA 提取输入人脸的特征。然后,它将这些特征与在训练阶段获得的特征脸进行比较。输入人脸被分类为特征脸与输入人脸特征最匹配的人。
import cv2 
import zipfile 
import numpy as np 
 
faces = {} 
with zipfile.ZipFile( "I PG.zip" ) as facezip: 
    for filename in facezip.namelist(): 
        if  not filename.endswith( ".jpg" ): 
            continue  #不是
        带有facezip 的面部图片。open (filename) as image: 
            # 如果我们从 zip 中提取文件,我们可以使用 cv2.imread(filename) 而不是
            faces[filename] = cv2.imdecode(np.frombuffer(image.read(), np.uint8), cv2 .IMREAD_GRAYSCALE)

image.png

上一个代码块的输出

facematrix = [] 
facelabel = [] 
for key,val in faces.items(): 
    if key.startswith( "s40/" ): 
        continue  # 这是我们的测试集
    if key == "s39/10.pgm" : 
        continue  # 这是我们的测试集
    facematrix.append(val.flatten()) 
    facelabel.append(key.split( "/" )[ 0 ]) 
 
# 创建面矩阵为 (n_samples,n_pixels)
矩阵# 应用 PCA从sklearn.decomposition中
提取特征脸import PCA pca = PCA().fit(facematrix)
​
 
​
# 取前K个主成分作为特征脸
n_components = 30
 eigenfaces = pca.components_[:n_components] 
 
# 显示前16个特征脸
fig, axes = plt.subplots( 4 , 4 ,sharex= True ,sharey= True ,figsize=( 8 , 10 )) 
for i in  range ( 14 ): 
    axes[i% 4 ][i// 4 ].imshow(eigenfaces[i].reshape(faceshape), cmap= "gray" ) 
plt.show() 
#将权重生成为 KxN 矩阵,其中 K 是特征脸的数量,N 是样本的数量
weights = eigenfaces @ (facematrix - pca.mean_).T
权重
weights = [] 
for i in  range (facematrix.shape[ 0 ]): 
    weight = [] 
    for j in  range (n_components): 
        if (j> 13 ): 
            break
         w = eigenfaces[j] @ (facematrix[i] - pca.mean_) 
        weight.append(w) 
        
    weights.append(weight) 
weights 
​
​
... 
# 测试现有类的样本外图像
query = faces[ "22234/22234.jpg" ].reshape( 1 ,- 1 )
query_weight = eigenfaces @ (query - pca.mean_).T 
euclidean_distance = np.linalg.norm(weights - query_weight, axis= 0 ) 
print ( 'a = ' ,euclidean_distance) 
best_match = np.argmin(euclidean_distance)
打印( 'b = ' ,best_match) 
print ( "Best match %s with Euclidean distance %f" % (facelabel[best_match], euclidean_distance[best_match])) 
# 可视化
fig, axes = plt.subplots( 1 , 2 ,sharex= True ,sharey = True ,figsize=( 8 , 6 ))
轴[ 0.imshow(query.reshape(faceshape), cmap= "gray" ) 
axes[ 0 ].set_title( "Query" ) 
axes[ 1 ].imshow(facematrix[best_match].reshape(faceshape), cmap= "gray" ) 
axes[ 1 ].set_title( "最佳匹配" ) 
plt.show()

image.png

创建的平均面孔和 PCA 提取

image.png

最终搜索结果

Eigen Faces 无法正常工作,因为一个核心原因是没有足够的数据集。对于每个单独的类,我们只有一个类,因此无法正确生成权重。此外,PCA 也失败了。平均面孔没有任何特征。

PCA 中的另一个问题是,请参见此处:

image.png

Eigen Faces 中的问题

我尝试了另一个数据集,其中查询照片不属于数据集,但 eigenfaces 确实预测了一张相似的脸。我们必须找到一个阈值,但不可能选择一个最佳阈值

第二次实施:VGG-16

然后我确实尝试通过阅读这篇论文(PDF) Transfer learning using VGG-16 with Deep Convolutional Neural Network for Classifying Images (researchgate.net) 来实现VGG-16 来识别我的脸

我使用了迁移学习的概念,并试图让模型识别出我的脸。

VGG-16 是一种卷积神经网络 (CNN) 架构,由牛津大学视觉几何组 (VGG) 于 2014 年推出。它是图像分类、目标检测和分割等计算机视觉任务中广泛使用和具有影响力的架构。下面是对 VGG-16 架构的详细解释:

  1. 输入层:网络的输入是尺寸为 224x224x3 的彩色图像。
  2. 卷积层:VGG-16 的前 13 层由卷积层组成,其中每一层学习一组过滤器,将输入图像转换为一组更高级别的特征。这些过滤器应用于输入图像的小区域,称为感受野。
  3. 最大池化层:在每个卷积层之后,有一个最大池化层,可将特征图的空间大小减少 2 倍。这有助于使网络对图像中对象位置的变化更加稳健。
  4. 全连接层:VGG-16 的最后 3 层是全连接层,它们采用卷积层的输出并产生分类结果。前两个全连接层各有 4,096 个神经元,最后一层有 1,000 个神经元,对应于 ImageNet 数据集中的 1,000 个类。
  5. Softmax 激活函数:最后一层的输出通过 softmax 激活函数,将输出转换为 1,000 个类的概率分布。
  6. Dropout:为了防止过拟合,在前两个全连接层之后应用了dropout正则化。Dropout 在训练期间随机将层中的一些激活设置为零,这有助于防止网络变得过于依赖任何单个神经元。

总的来说,VGG-16 是一种深度神经网络架构,可以有效地从图像中学习高级特征。它已被广泛使用,并作为许多计算机视觉任务的基准。

image.png

这是我的模型实现代码:

从keras.layers导入输入、Lambda、Dense、Flatten
从keras.models从keras.applications.vgg16导入模型
从keras.applications.vgg16导入VGG16
从keras.preprocessing导入preprocess_input
从keras.preprocessing.image导入图像
从keras导入ImageDataGenerator .models import Sequential import numpy as np from glob import glob import
​
​
​
matplotlib.pyplot as plt 
​
# 将所有图像调整为这个
IMAGE_SIZE = [ 224 , 224 ] 
​
train_path = 'E:/face recoginition/Face-Recognition-in-Live-Stream-using-VGG-16-main/Face- Recognition-in-Live-Stream-using-VGG-16-main/train/'
 valid_path = 'E:/face recoginition/Face-Recognition-in-Live-Stream-using-VGG-16-main/Face-Recognition- 人脸识别in-Live-Stream-using-VGG-16-main/test/'# 在VGG前面添加预处理层
vgg = VGG16(input_shape=IMAGE_SIZE + [ 3 ], weights= 'imagenet' , include_top= False ) 
​
for layer在 vgg.layers中:
  layer.trainable = Falsefolders = glob( '/Users/msc/Desktop/Recognition Using Cnn/data/train/*' ) 
print ( len (folders)) 
​
# 层数
x = Flatten()(vgg.output)
预测= Dense( 2 , activation= 'softmax' )(x) 
​
# 创建模型对象
model = Model(inputs=vgg.input , outputs=prediction) 
​
# 查看模型的结构
model.summary() 
​
# 编译模型
model . 编译( 
  loss= 'categorical_crossentropy' , 
  optimizer= 'adam' , 
  metrics=['精度' ] 
) 
​
from keras.preprocessing.image import ImageDataGenerator 
​
train_datagen = ImageDataGenerator(rescale = 1. / 255 , 
                                   shear_range = 0.2 , 
                                   zoom_range = 0.2 , 
                                   horizontal_flip = True ) 
​
test_datagen = ImageDataGenerator(rescale = 1. / 255 ) 
​
training_set = train_datagen .flow_from_directory( '/Users/subham/Desktop/Recognition Using Cnn/data/train' ,
                                                 target_size = ( 224 , 224 ), 
                                                 batch_size = 32 , 
                                                 class_mode = 'categorical' ) 
​
test_set = test_datagen.flow_from_directory( '/Users/subham/Desktop/Recognition Using Cnn/data/test' , 
                                            target_size = ( 224 , 224 ), 
                                            batch_size = 32 , 
                                            class_mode = '分类' )
打印( len (training_set)) 
​
print ( len (test_set)) 
​
# 拟合模型
r = model.fit_generator( 
  training_set, 
  validation_data=test_set, 
  epochs= 2 , 
  steps_per_epoch= len (training_set), 
  validation_steps= len (test_set) 
) 
''' 
# loss 
plt.plot(r.history['loss'], label='train loss') 
plt.plot(r.history['val_loss'], label='val loss') 
plt.legend() 
plt.show () 
plt.savefig('LossVal_loss') 
​
# 精度
plt.plot(r.history['acc'], label='train acc')
plt.plot(r.history['val_acc'], label='val acc') 
plt.legend() 
plt.show() 
plt.savefig('AccVal_acc')''' 
​
import tensorflow as tf 
​
from keras.models import load_model 
​
##保存模型
model.save( 'final_model.h5' )

image.png

我的模型训练验证(损失)图

模型输出代码:

# 人脸识别#从PIL
​
导入库import Image from keras.applications.vgg16 import preprocess_input import base64 from io import BytesIO import json import random import cv2 from keras.models import load_model import numpy as np from keras.preprocessing import image model = load_model ( 'final_model.h5' ) # 加载级联
​
​
​
​
​
​
​
​
​
​
​
​
​
​
face_cascade = cv2.CascadeClassifier( 'haarcascade_frontalface_default.xml' ) 
​
def  face_extractor ( img ): 
    # 函数检测人脸并返回裁剪后的人脸
    # 如果没有检测到人脸,则返回输入图像
​
    faces = face_cascade.detectMultiScale(img, 1.3 , 5 ) 
​
    if faces is (): 
        return  None 
​
    # Crop all faces found 
    for (x,y,w,h) in faces: 
        #adding rectangle around your face
         cv2.rectangle(img,(x,y),(x+w,y)复制代码+h),( 0 , 255 , 255 ),2 ) 
        cropped_face = img[y:y+h, x:x+w] 
​
    return cropped_face 
​
​
​
#Face Recognition with the webcam 
'''Extract test face , resize the image to feed to VGG16(224x224x3) , 转换为nd-array, predict'''
 video_capture = cv2.VideoCapture( 0 ) 
while  True : 
    _, frame = video_capture.read() 
​
​
    face=face_extractor(frame)
    如果 类型(脸)是np.ndarray: 
        face = cv2.resize(face, ( 224 , 224 )) 
        im = Image.fromarray(脸, 'RGB' )
​
           #Resizing into 128x128 因为我们用这个图像大小训练模型。
        img_array = np.array(im) 
​
        img_array = np.expand_dims(img_array, axis= 0 ) 
        pred = model.predict(img_array) 
        print (pred) 
​
        name= "无匹配" 
​
        if (pred[ 0 ][ 0 ]> 0.5 ) : 
            name= 'Sai Mathura krishnan'
        
        
         cv2.putText(frame,name, ( 50 , 50 ), cv2.FONT_HERSHEY_COMPLEX, 1 , ( 0 , 255 , 0 ), 2 )
    其他: 
        cv2.putText(frame, "No face found" , ( 50 , 50 ), cv2.FONT_HERSHEY_COMPLEX, 1 , ( 0 , 255 , 0 ), 2 ) 
    cv2.imshow( 'Video' , frame) 
    if cv2.waitKey( 1 ) & 0xFF == ord ( 'q' ):
        中断
video_capture.release() 
cv2.destroyAllWindows()

image.png

我的 VGG 模型的输出能够识别我的脸。

但是同样,当我试图为 3 个类实现相同的模型时,问题确实发生了。训练确实进行得很顺利,但由于某种原因,误报和漏报的发生率非常高,这让我放弃了这个模型。我的结论是,如果它正在为 3 节课而苦苦挣扎,那么它就是第一名。类的增加,错误分类的问题会发生更多。

之后,我确实更加沉迷于学习核心深度学习概念,如 CNN、激活函数和各种优化器。

CNN简介:

卷积神经网络 (CNN) 是一种人工神经网络,广泛用于图像分类和对象识别任务。CNN 旨在模仿人脑处理视觉信息的方式。

CNN 的架构由三种主要类型的层组成:卷积层、池化层和全连接层。卷积层是 CNN 的构建块。它们由一组可学习的过滤器组成,这些过滤器在输入图像上滑动,应用称为卷积的数学运算。此操作一次计算过滤器和输入图像的一小块之间的点积,生成一个特征图,突出显示图像中的特定模式或特征。

池化层用于对卷积层的输出进行下采样,在保留重要信息的同时降低特征图的维数。最常用的池化操作是最大池化,它在特征图的一个小区域内取最大值。

最后,池化层的输出被展平并送入一系列全连接层,这类似于传统人工神经网络中使用的层。这些层根据卷积层和池化层提取的特征学习对输入图像进行分类。CNN 使用反向传播进行训练,反向传播是一种优化算法,可调整过滤器和全连接层的权重,以最小化预测输出与预测输出之间的差异网络的实际输出。图像分类任务最常用的损失函数是交叉熵损失。

CNN 已被证明在图像分类任务中非常有效,在多个基准数据集上取得了最先进的结果。它们还用于其他应用,例如对象检测、分割,甚至自然语言处理。

什么是卷积?

卷积是一种数学运算,常用于信号处理、图像处理等领域。在卷积神经网络 (CNN) 的上下文中,卷积是卷积层中使用的关键操作。

在高层次上,卷积涉及将一个称为内核或过滤器的小矩阵滑动到一个称为输入的较大矩阵上,然后执行逐元素乘法并对结果求和。内核通常比输入矩阵小得多,并且一次在输入矩阵上滑动一个小区域。

卷积运算的输出是一个特征图,突出显示输入中的特定模式或特征。这些模式由内核的权重决定,内核的权重是在训练过程中学习到的。

例如,在图像处理中,内核可能被设计为检测图像中的边缘或线条。通过将此内核应用于图像,生成的特征图将突出显示图像中包含边缘或线条的区域。

卷积运算也可以扩展到更高维的矩阵,例如用于处理视频数据或体积医学图像的 3D 卷积。

总的来说,卷积是一种强大的操作,它使 CNN 能够自动学习并从输入数据中提取特征,这对于许多机器学习任务至关重要。

image.png

二维卷积的工作

image.png

与核卷积。

深度卷积神经网络(DCNN)

与普通神经网络一样,卷积神经网络与其非常相似,并且具有可学习的权重和偏差的神经元,并且每个神经元接收一些输入,执行点积并可选地跟随它进行非线性处理。在深度卷积神经网络的架构中,整个网络从一端的原始图像像素到另一端的类别分数。在最后一层,它们具有完全连接的损失函数(例如 Softmax)。

image.png

传统的卷积神经网络……

在下图中,CNN 中的每个神经元在空间上仅连接到输入体积中的局部区域,但连接到整个深度。有多个神经元和深度,所有神经元都在输入中查看同一个区域。在右侧,神经网络章节中的神经元保持不变:它们仍然计算其权重与输入后跟非线性的点积,但它们的连接性现在仅限于局部空间。

image.png

CNN 的典型工作。

TensorFlow 的工作原理:

张量这个词是高维向量和矩阵的概括,或者我们可以说它是一个多维数组。可以按照array()的构造函数定义为列表的列表。在一般情况下,我们可以说张量是排列在具有可变轴数的规则网格上的数字数组。

下面是张量的表示,

image.png

张量的类型

最新版本的 TensorFlow 版本 2.0 带来了大量新功能。其中一个主要更新是子类化的引入,它提供了一种创建模型的新方法。此外,Tensorflow 2.0 包括多项生活质量改进,使其更加用户友好和高效。

Tensorflow 2.0 中最重要的更新之一是默认启用即时执行。此功能允许立即记录错误并简化代码,同时仍通过类似 NumPy 的库提供对 GPU 加速和自动微分的支持。Eager Execution 还兼容原生 Python 调试工具,我们不再需要启动图形会话来执行张量操作。

Tensorflow 是一个跨平台库,可以在 Linux、macOS 和 Windows 上运行,并且能够在 CPU、GPU 和 TPU 上运行。此外,Tensorflow 具有足够的通用性,可以在移动和嵌入式平台上工作,模块化的 CUDA 和 SYCL 扩展可用于图形处理单元上的通用计算。Tensorflow 中的计算表示为有状态数据流图。

Tensorflow 生态系统庞大且不断扩展,拥有众多可用于数据预处理、模型选择和部署等任务的工具和库。下图说明了 thTensorFlowow 生态系统的各个组件。

image.png

生态系统 TensorFlow。

数据流

在并行计算中,TensorFlow 中的 Dataflow 是一种通用的编程语言,其中数据流图的节点代表计算单元,边代表计算消耗或产生的数据。

image.png

数据流 iTensorFlowow。

第三种实现:Inception Resnet V2

Inception-ResNet-v2 是谷歌研究人员于 2016 年推出的深度神经网络架构。它是图像分类、对象检测和分割任务的最先进模型。

Inception-ResNet-v2 模型结合了 Inception 架构和 ResNet 架构,这是用于图像识别任务的两种最流行和最成功的神经网络架构。Inception 架构旨在从输入图像中提取不同大小的特征,而 ResNet 架构旨在通过使用层与层之间的快捷连接来解决梯度消失问题。

Inception-ResNet-v2 架构由 Inception 模块和 ResNet 模块的重复块组成,开头是一个词干,结尾是一个分类器。stem 由卷积层、池化层和归一化层组成,它们从输入图像中提取初始特征。

每个 Inception-ResNet-v2 块由四个分支组成,每个分支执行不同类型的卷积运算:1x1、3x3、5x5 或池化运算。然后将这些分支的输出连接起来并送入下一个块。除了 Inception 模块之外,每个块还包含一个 ResNet 模块,其中包含一组残差连接,可以让梯度更容易地在网络中流动。

Inception-ResNet-v2 模型有超过 164 层和超过 5600 万个参数。它在 ImageNet 数据集上进行了训练,该数据集包含 1,000 个类别的超过一百万张图像,并且分别达到了 19.9% 和 4.9% 的前 1 和前 5 错误率。

Inception-ResNet-v2 架构的好处之一是尽管它的尺寸很大,但它的计算效率很高。这是通过使用瓶颈层来实现的,瓶颈层减少了 3x3 和 5x5 卷积层的输入通道数量,并使用分解将 5x5 卷积层分成两个 3x3 卷积层。

总的来说,Inception-ResNet-v2 模型是一个强大的深度神经网络架构,在广泛的图像识别任务上实现了最先进的性能。它结合了 Inception 和 ResNet 架构,以及其他创新设计选择,使其成为研究人员和从业者等的绝佳选择。

Inception-ResNet-v2 模型是一个非常深且复杂的神经网络架构,由许多层组成。以下是每一层的简要概述:

  1. 输入层:该层接收输入图像,通常是固定大小的 3 通道 RGB 图像。
  2. 茎:茎是网络中的第一组卷积层。它由几个 3x3 卷积层组成,然后是最大池化层和归一化层。stem旨在从输入图像max-pooling中提取基本特征

image.png

最大池图

批归一化涉及通过减去批均值并除以批标准差来对层的输出进行归一化。这有助于将输入数据居中和缩放,从而使后续层更容易学习有用的特征。此外,批量归一化可以减少“协变量偏移”问题的影响,这种问题在输入数据的分布随时间变化时发生。

在 Inception-ResNet-v2 模型的主干中,归一化在卷积层之后和池化层之前执行。这有助于确保输入数据在被网络进一步处理之前被归一化。通过从输入图像中提取基本特征并对数据进行归一化,词干作为网络其余部分构建和学习更复杂特征的起点。

image.png

  1. Inception-ResNet-A:这是网络中几个 Inception-ResNet 块中的第一个。它由多个初始模块组成,后跟一个 ResNet 模块。每个初始模块由几个并行的卷积分支组成,它们提取不同尺度的特征。

例如,典型的 resnet-12 结构如下所示:

image.png

tharchitecturere 中 resnet 块的插图。

  1. Reduction-A:这是一个特殊的块,旨在减少特征mas的空间维度,同时增加通道数。它由池化层、卷积层和归一化层的组合组成。

最大池化涉及将输入特征图划分为不重叠的区域,并从每个区域中取最大值。这有助于对特征图进行下采样并减小其空间维度,从而使后续层更容易处理。此外,最大池化有助于提高网络对输入数据中的小空间平移和失真的鲁棒性。

在“Reduction-A”块中,最大池化操作之后通常是一系列卷积层和归一化层,这有助于增加特征图中的通道数量。通过增加通道数量和减少特征图的空间维度,“Reduction-A”块有助于从输入数据中提取更高级别的特征并提高网络在复杂任务上的性能。

image.png

特征图的样子

一个

image.png

卷积工作的实际例证

  1. Inception-ResNet-B:该块类似于Inception-ResNet-,但inception模块和ResNet模块的配置不同。

  2. Reduction-B:这是另一个特殊的块,旨在进一步减少特征图的空间维度,同时增加通道数。

  3. Inception-ResNet-C:该块类似于Inception-ResNet-B,但初始模块和ResNet模块的配置不同。

  4. 最终层:最后一个缩减块的输出被送入几个完全连接的层,这些层使用 softmax 将输入图像最终分类为预定义类别之一。

image.png

softmax现场演示。

Inception-ResNet-v2 模型中的每一层都有特定的用途,并有助于提高网络的整体性能。初始模块特别重要,因为它们允许网络在多个尺度上提取特征并在图像识别任务上实现更高水平的准确性。ResNet 模块也很重要,因为它们有助于解决梯度消失问题并允许网络进行更深入的训练。

image.png

inception-resnet-v2架构

从tensorflow.keras中导入tensorflow作为tf从 tensorflow.keras.preprocessing.image中导入层、模型从tensorflow.keras.optimizers.legacy中导入ImageDataGenerator从tensorflow.keras.applications.inception_resnet_v2中导入Adam从tensorflow.keras.applications.inception_resnet_v2中导入InceptionResNetV2从tensorflow.keras导入preprocess_input从tensorflow.keras.applications导入层、模型、正则化器导入
​
​
​
​
​
​
resnet_v2 
from tensorflow.keras.layers import Layer 
import matplotlib.pyplot as plt 
import seaborn as sns 
import numpy as np 
import os 
​
​
# 定义图像的输入形状
input_shape = ( 160 , 160 , 3 ) 
# 定义类数
num_classes = 14 
​
def  create_facenet_model ( num_classes = 14 ): 
    # 加载预训练的 Inception-ResNet-v2 模型
    base_model = InceptionResNetV2(weights= 'imagenet' , include_top= False , input_shape=input_shape) 
​
    # 冻结基础模型中的所有层
    for layer in base_model.layers: 
        layer.trainable = False 
​
    # 为迁移学习添加自定义顶层
    x = base_model.输出
    x = tf.keras.layers.GlobalAveragePooling2D()(x) 
    x = tf.keras.layers.Dense( 1024 , activation= 'relu' )(x) 
    predictions = tf.keras.layers.Dense(num_classes, activation= 'softmax' )(x) 
​
    # 创建迁移学习模型
    model = tf.keras.models.Model(inputs=base_model.input , outputs=predictions) 
​
    return model 
​
# 创建 FaceNet 模型的实例
model = create_facenet_model() 
# # 使用指定的损失函数和优化器编译模型
# model. compile(optimizer=optimizer, 
#loss='categorical_crossentropy', 
#metrics=['accuracy']) # model
模型。compile (optimizer= 'adam' , loss= 'categorical_crossentropy' , metrics=[ 'accuracy' ]) 
​
root_dir = "/content/gdrive/MyDrive/dataset"# 创建一个 ImageDataGenerator 对象来执行数据增强
# 并重新缩放
datagen = tf.keras.preprocessing.image.ImageDataGenerator( 
    rescale= 1. / 255 , 
    preprocessing_function=preprocess_input, 
    rotation_range= 30 , 
    width_shift_range= 0.2 , 
    height_shift_range= 0.2 , 
    shear_range= 0.2 , 
    zoom_range= 0.2 , 
    horizontal_flip= True , 
    vertical_flip= True , 
    validation_split= 0.2
 )
​
# 创建训练集生成器
train_generator = datagen.flow_from_directory( 
    root_dir, 
    target_size=(input_shape[: 2 ]), 
    batch_size= 32 , 
    class_mode= 'categorical' , 
    subset= 'training' , 
    shuffle= True
 ) 
​
# 创建验证集生成器
val_generator = datagen.flow_from_directory( 
    root_dir, 
    target_size=(input_shape[: 2 ]), 
    batch_size= 32 , 
    class_mode= 'categorical' , 
    subset= 'validation' ,
    shuffle= True
 ) 
​
# 用递增的 epochs 训练模型
history = model.fit(train_generator, 
                    epochs= 30 , 
                    validation_data=val_generator)

该模型能够有效地预测,并且我从测试数据集中手动加载了随机照片并且能够正确预测。

image.png

第一类预测

image.png

2类预测

image.png

3级预测

在实时摄像头中,预测人脸的步骤是:

  1. 人脸检测
  2. 人脸提取
  3. 人脸识别

按顺序,我们必须执行所有这些步骤。

对于实时提要中的人脸检测,我们使用称为 MTCNN() 的神经网络

MTCNN(多任务级联卷积网络)是一种流行的人脸检测算法,它使用深度卷积神经网络从图像中检测和提取人脸。它是一个多阶段框架,涉及三个阶段:检测、对齐和嵌入。

以下是每个阶段的简要说明:

  1. 检测:MTCNN 的第一阶段涉及检测输入图像中的人脸。它使用深度 CNN 来预测图像中所有面部的边界框。CNN 在包含人脸和非人脸的大型数据集上进行训练,并学会区分两者。
  2. 对齐:检测到人脸的边界框后,MTCNN 的第二阶段涉及将人脸与规范姿势对齐。这很重要,因为它有助于消除可能影响后续阶段性能的姿势、光照和面部表情的变化。MTCNN 使用一组面部标志将面部对齐到标准化位置。

MTCNN 是人脸检测的热门选择,因为它准确、快速并且可以处理多张人脸图像。它已被证明在各种数据集上优于其他人脸检测算法,如 Haar Cascades 和 HOG+SVM。

image.png

mtcnn的工作

我使用 collab 来运行我的模型并通过非常基本的方法找到输出。

image.png

协作中的工作人脸识别

简单的想法是创建一个画布元素并使用 javascript 模块调用 VM 的相机输出。转换在相机提要中捕获的图像并将其转换为 base 64 代码。

我们拿这个,图像并将它发送到我们的模型以预测它会返回嵌入。我们再次创建一个边界框并将这些值转换为 base64 并更新输出画布的下一帧。

这是在 google collab 中运行我们的模型并在具有(0.5 秒到 1 秒延迟)的实时摄像头馈送中获得输出的最佳方式。

image.png

base64 图像示例

image.png

在 google colab 中运行模型。这是输出图像。

14个类的实现:

选择这个模型后,我收集了 14 个成员的数据集(因为我的班级强度是 14)。对于每个人,我总共收集了每人 200 张图像。

此外,数据集目录如下所示,

image.png

文件夹排列

但它并没有按计划进行,因为存在一个巨大的问题。

它在我的数据集中。由于这个唯一原因,我的模型无法准确提取特征并学习面孔,因此捕获的所有图像都处于昏暗的灯光环境中。训练很糟糕。验证集中的模型性能越来越差。我确实尝试增加我的数据,比如提高对比度和调整光照,但它们没有任何改进。我尝试添加提前停止和多个参数调整,但它们都没有带来任何改变。

image.png

训练验证准确性

它被视为随着准确性的提高而验证准确性不断下降。

显然,该模型没有提取任何有用的特征,而只是对训练数据过度拟合。

image.png

训练验证损失

在这里,您可以从一开始就看到验证损失达到峰值而不是减少。经过几天的研究,我了解了数据集中的问题。然后我又打算收集数据集。请注意,我在这里训练了 3 个时期的模型。我使用的优化器是 Adam ()

image.png

adam() 优化器的图示(红色)

image.png

模型评估图

image.png

混淆矩阵

您可以看到测试数据集中发生的错误分类数量。甚至有些课程根本没有正确的预测。(在某些对角线条目完全为 0 的地方可以看到)。

尝试 2:收集新数据集

本次收集数据集时采取的步骤:

  1. 增加房间内的照明。
  2. 可以肯定的是,抖动的图像非常少。(更清晰)

这次我为每个班级收集了 210-220 张可靠的图像,文件夹结构再次如下所示,

image.png

数据集文件夹排列

在此之后,我再次开始训练,使用我之前定义的相同的人脸网络模型()。这次我训练了 30Epochs,再次使用 Adam() 优化器。

最初,我确实尝试了 15 个 epoch,但没有给我任何好的结果,所以我又转而训练 30 个 epoch。

希望最好的我完全等待培训完成。

但即使在收集了一个新的数据集之后,也没有任何运气。

image.png

新数据集训练验证精度图。

验证准确率从 1% 略微提高到 15%。但是在那之后,它一点都没有改善。

image.png

训练验证损失曲线

损失确实减少了,但在第 30 个 epoch 之后,损失达到了一个稳定水平,并且没有下降,准确率也没有下降。

在那里,测试数据也没有希望。

image.png

性能测量。

所有的性能测量值都非常糟糕。

还有混淆矩阵,

image.png

新数据集上的混淆矩阵

该模型在测试数据集中表现更差,它将所有图像归为一类。

经过这一切,我明白了一件事,

image.png

即使在多次失败之后,我仍然想尝试更多的东西来克服这个问题。我确实知道问题是因为图像分辨率稍低。

但我也了解到,不仅分辨率是问题所在,而且人脸本身也太相似了,这就是模型无法对图像进行分类的地方。

所以我确实花了很长时间在互联网上冲浪,寻找可以解决这个问题的东西,然后我遇到了这个美丽的数学概念,叫做 arcface。

解决方案:ARCFACE

我得到了这篇论文,即ArcFace:Deep Face Recognition 的 Additive Angular Margin Loss

ArcFace:用于深度人脸识别的附加角边距损失最近,人脸识别的一个流行研究方向是在成熟的 softmax 损失中采用边距……arxiv.org

这可能听起来令人困惑,但弧面不是损失函数而是一个层,就像 softmax 但比它好得多。

ArcFace 背后的核心思想是学习一个特征嵌入空间,在这个空间中,来自相同身份的人脸紧密地聚集在一起,而来自不同身份的人脸被很大的距离分开。这是通过修改分类任务中使用的传统 softmax 损失函数来实现的,以包括鼓励网络学习更多判别特征的角度边缘惩罚。

ArcFace 方法使用深度卷积神经网络 (CNN) 从人脸图像中提取特征。网络架构通常由多个卷积层和池化层组成,然后是全连接层,为每张人脸图像生成高维特征向量。然后使用 L2 归一化将特征向量归一化并映射到超球体。

ArcFace 方法的最后一步是将基于边距的 softmax 损失函数应用于特征向量。该损失函数最大化同一身份人脸特征向量之间的余弦相似度,同时最小化不同身份人脸特征向量之间的余弦相似度。角边距惩罚确保特征向量在超球体中被一个大的边距分开。

ArcFace 在包括 LFW、CFP 和 AgeDB 在内的多个人脸识别基准测试中取得了最先进的性能。该方法广泛应用于监控、访问控制和身份验证等实际应用中。

通常,Arcface 或 Additive Angular Margin Loss 是最先进的人脸识别任务中使用的损失函数。它能够处理数百亿个类别(个人面孔)。这意味着截至 2021 年世界人口接近 80 亿,如果你有足够的资源,你可以训练一个地球级别的面部识别系统来识别每个人。

与欧氏距离不同,ArcFace 损失计算使用超球体上的测地距离,超球体是比欧氏距离更高维的空间。让我们再次想象一下,想象每个球体现在都是一本书,而 512 的维度将是一个装满书的架子。理论上应该比使用Softmax + Cross-Entropy能够获得高判别力的特征。

欧氏距离和测地线的另一个区别是欧氏距离在寻找从起点到终点的路径时完全忽略了形状。而 Geodesic 将被限制为给定的形状。

image.png

差异 b/w 测地距离,欧几里得。

公式:

image.png

softmax函数

这是我们正常的 softmax 函数。

其中 x 表示第 i^th 个样本的特征向量,属于第 yi 个类。W 和 b 分别是权重和偏差。批量大小和类别数分别为 N 和 n。Softmax loss 没有明确优化特征嵌入以强制类内样本具有更高的相似性和类间样本的多样性,这导致在大的类内外观变化(例如姿势变化,年龄差距)下深度人脸识别的性能差距,以及大规模测试场景(例如,百万或万亿对))。

这是我们的弧形脸,

在上面的 softmax 损失中,如果我们使 b = 0,然后将 logit 转换为

image.png

简单余弦距离

其中 θ 是权重 W 和特征 x 之间的角度。使用 L2 范数将权重归一化为 1。

image.png

转化公式(中间步骤)

然后,在 W 和 x 之间添加一个附加的角度边缘惩罚 'm' 以增强类内紧凑性和类间差异。

由于所提出的附加角度余量惩罚等于归一化超球体中的测地距离余量惩罚,因此将其命名为 ArcFace。

image.png

圆弧面公式

人们发现从头开始收敛 ArcFace 模型很困难。它需要在开始时进行良好的嵌入,然后使用 ArcFace 对其进行微调。

从这一点来看,还有一件更重要的事情是,

简而言之,当你的班级较小时,将 s*** 设置得较低,将**m设置得较高*

如何?

这个损失函数和所有这些附加的角度余量如何加强类内紧凑性和类间差异?简单来说,它背后的数学解释/直觉是什么?

arc face paper 有很多解释,但没有人详细讨论 HOW。我想花点时间解释一下:

  • 余弦函数随着自变量或 theta 的增加而减小。
  • 指数函数单调递增。
  • 因此,当我们向角度 btw 特征向量 x 和它 (x) 所属类别的权重添加附加角度余量时,角度的余弦减小,余弦的指数减小(因为's'为正)并且由于 Loss 为负号,因此整体 Loss 增加。
  • 因此,要减小损失函数,必须减小 xᵢ 与相应权重之间的角度。这意味着权重和 xᵢ 之间的角度随着训练的进行而减小,从而帮助模型提供有区别的特征向量/嵌入。

Softmax 与 ArcFace 的比较

image.png

softmax和arcface的比较

在这里我们可以看到,当投影到超球体时,softmax 不会最大化簇内距离,但 arcface 会因为边距惩罚“m”而做到这一点。

此外的见解,

image.png

3D可视化

image.png

arcface 在训练的每一步

image.png

边距在二进制情况下如何工作。

上图可视化了二元分类情况下不同损失函数的决策余量。虚线代表决策边界,灰色区域是决策边际。此外,由于定义了明确的边界,ArcFace 的性能与其他一些方法相比表现得非常好。

最终实现:inception-resnet-v2 with Arcface

在这里,我使用了一对多数据扩充。

一对多数据增强是深度学习中用于增加训练数据多样性和提高模型性能的一种数据增强技术。与涉及通过对单个输入图像应用变换来创建新样本的一对多数据扩充不同,一对多数据扩充涉及从单个输入图像创建多个新样本。

一对多数据增强技术涉及对输入图像应用不同类型的变换,例如旋转、平移、缩放、翻转和裁剪,以创建原始图像的多种变体。这些变化中的每一个在训练期间都被视为一个单独的样本,这增加了训练集的多样性并帮助模型更好地泛化到新数据。

当训练集的大小有限时,一对多数据增强特别有用,因为它允许模型从更大、更多样化的示例集中学习。它在减少过度拟合方面也很有效,因为模型在训练期间会暴露于更广泛的输入数据变化。

应用扩充后的数据集:

image.png

数据扩充

Arcface模型代码:

从keras导入后端作为K
从keras.layers导入图层
从keras导入正则化器
​
导入tensorflow作为tf
​
​
类 ArcFace(层):
    def  __init__(self,n_classes= 14,s= 16.0,m= 0.20,regularizer= None,**kwargs ): 
        super (ArcFace, self).__init__(**kwargs) 
        self.n_classes = n_classes 
        self.s = s 
        self.m = m
        self.regularizer = regularizers.get(regularizer) 
​
    def  build ( self, input_shape ): 
        super (ArcFace, self).build(input_shape[ 0 ]) 
        self.W = self.add_weight(name= 'W' , 
                                shape=(input_shape [ 0 ][- 1 ], self.n_classes), 
                                initializer= 'glorot_uniform' , 
                                trainable= True , 
                                regularizer=self.regularizer) 
​
    def  call ( self, inputs ): 
        x, y = 输入
        c = K.shape(x)[- 1 ] 
        # 归一化特征
        x = tf.nn.l2_normalize(x, axis= 1 ) 
        # 归一化权重
        W = tf.nn.l2_normalize(self.W, axis= 0 ) 
        # 点product
         logits = x @ W 
        # add margin 
        # clip logits 以防止向后时零除
        theta = tf.acos(K.clip(logits, - 1.0 + K.epsilon(), 1.0 - K.epsilon())) 
        target_logits = tf.cos(theta + self.m) 
        # sin = tf.sqrt(1 - logits**2) 
        # cos_m = tf.cos(logits) 
        # sin_m = tf.sin(logits) 
        # target_logits = logits * cos_m - sin * sin_m 
        #
        logits = logits * ( 1 - y) + target_logits * y 
        # 特征重新缩放
        logits *= self.s 
        out = tf.nn.softmax(logits) 
​
        return out 
​
    def  compute_output_shape ( self, input_shape ): 
        return ( None , self. n_classes)

数据扩充:

root_dir = "/content/gdrive/MyDrive/dataset"# 创建一个 ImageDataGenerator 对象来执行数据增强
# 和重新缩放
datagen = tf.keras.preprocessing.image.ImageDataGenerator( 
    rescale= 1. / 255 , 
    preprocessing_function=preprocess_input, 
    rotation_range= 30 , 
    width_shift_range= 0.2 , 
    height_shift_range= 0.2 , 
    shear_range= 0.2 , 
    zoom_range= 0.2 , 
    horizontal_flip= True , 
    vertical_flip= True , 
    validation_split=0.2
 ) 
​
# 创建训练集生成器
train_generator = datagen.flow_from_directory( 
    root_dir, 
    target_size=(input_shape[: 2 ]), 
    #batch_size=32,
     class_mode= 'categorical' , 
    subset= 'training' , 
    shuffle= True
 ) 
​
# 创建一个验证集生成器
val_generator = datagen.flow_from_directory( 
    root_dir, 
    target_size=(input_shape[: 2 ]), 
    #batch_size=32,
     class_mode= 'categorical' , 
    subset= 'validation' , 
    shuffle= True
)

型号定义:

def  create_facenet_model ( input_shape=( 160 , 160 , 3 ) ): 
    # 定义输入张量
    input_layer = tf.keras.layers.Input(shape=input_shape) 
    label_layer = tf.keras.layers.Input(shape=( 14 ,)) 
    
    #加载预训练的 Inception-ResNet-v2 模型
    base_model = InceptionResNetV2(weights= 'imagenet' , include_top= False , input_tensor=input_layer) 
    
    # 冻结基础模型中的所有层
    for layer in base_model.layers[:- 10 ]: 
        layer.可训练=假
        
    # 为迁移学习添加自定义顶层
    x = base_model.output 
    x = tf.keras.layers.GlobalAveragePooling2D()(x) 
    x = tf.keras.layers.BatchNormalization()(x) 
    x = tf.keras.layers.Dropout ( 0.5 )(x) 
    x = tf.keras.layers.Flatten()(x) 
    #x = tf.keras.layers.Dense(512, activation='relu')(x)
     x = Dense( 512 , kernel_initializer= 'he_normal' )(x) 
    x = tf.keras.layers.BatchNormalization()(x) 
    
    regularizer = tf.keras.regularizers.l2( 0.01 ) 
    # 应用 ArcFace 层以获得最终预测
    predictions = ArcFace(n_classes = 14 ,regularizer =regularizer)([x, label_layer])
​
    # 创建迁移学习模型
    model = tf.keras.models.Model(inputs=(input_layer, label_layer), outputs=predictions) 
    
    return model

模型创建:

# 创建 FaceNet 模型的实例
model = create_facenet_model() 

model. 编译(优化器= 'adam',损失= 'categorical_crossentropy',指标=[ 'accuracy' ])

手动添加数据集:

# 从训练集中提取输入张量 x 和目标张量 y 
for x, y in train_generator: 
    break   # 只提取一批用于演示目的
print (x,y) 
# 从验证集中提取输入张量 x 和目标张量 y 
for x, y in val_generator: 
    break   # 只提取一批用于演示目的
print (x,y)

我分别加载每个批次并将其循环发送到模型:

根据我模型的要求(因为 arcface),我的模型在训练时单独获取数据并单独标记。arcface 需要单独的标签。

# 获取每个 epoch 的步数
train_steps_per_epoch = train_generator.samples // train_generator.batch_size 
val_steps_per_epoch = val_generator.samples // val_generator.batch_size 

train_loss = [] 
train_acc = [] 
val_loss = [] 
val_acc = [] #为epoch


训练模型in range (num_epochs): print ( 'Epoch {}/{}' . format (epoch+ 1 , num_epochs)) # 在每个批次上训练for i in range (train_steps_per_epoch): # 从生成器        x_train中获取一批数据和标签, y_train =
 
    
    
    
     
        
next (train_generator) 
        
        # 在批处理上训练模型
        print ( 'TRAINING' ) 
        history = model.fit((x_train, y_train), y_train, batch_size= 32 , verbose= 1 ) 
        
        train_loss.append(history.history[ 'loss' ]) 
        train_acc.append(history.history[ 'accuracy' ]) 
        
    # 在验证集上评估模型
    val_loss_epoch, val_acc_epoch = 0 , 0 
    for i in  range (val_steps_per_epoch): 
        # 从生成器中获取一批数据和标签
        x_val , y_val =下一个(val_generator) # 在批量打印
        
        上评估模型( 'VALIDATING' )         loss, acc = model.evaluate((x_val, y_val), y_val, batch_size= 32 , verbose= 1 ) # 累积验证损失和精度        val_loss_epoch +=损失        val_acc_epoch += acc     val_loss.append(val_loss_epoch/val_steps_per_epoch)     val_acc.append(val_acc_epoch/val_steps_per_epoch) print ( 'val_loss: {:.4f} - val_acc: {:.4f}' . format (val_loss[- 1 ], val_acc [- 1 ])) # 绘制训练和验证的准确性和损失
        

        
        


        


    


plt.plot(train_loss) 
plt.plot(train_acc) 
plt.plot(val_loss) 
plt.plot(val_acc) 
plt.legend([ 'Train Loss' , 'Train Acc' , 'Val Loss' , 'Val Acc' ]) 
plt.show()

这次我使用相同的 Adam() 优化器训练了 15 个 Epochs 的模型。

正如之前所说,从一开始就收敛 arcface 肯定很难,因为从 10 到 11 个 epoch 开始,验证精度为 0。

对于前 9 个 Epoch,即使是训练精度也始终为 0。

但我能够看到的一件好事是,验证和训练损失一直呈线性下降,所以我一直在等待过程完成。

瞧,确实在 12 Epoch 结束时训练准确率增加了,验证准确率也增加了,损失也减少了。最后,我的模型给出了 85.96% 的准确率。

image.png

训练精度和训练损失。

在此图中,可以看到 train loss 从 8.5 开始不断下降,在 900 步之后的某处,准确率开始上升并达到 0.85 的值。

在此图中,验证损失和准确性不明确,所以我再次绘制了它。

image.png

验证损失

image.png

验证准确性

在我绘制的两个图中,训练的最后几个步骤就是为什么 x 轴上只有 25 的最大值。

最后,使用arcface成功训练完成。