21 人脸识别

发布时间 2023-10-08 17:09:20作者: 王哲MGG_AI
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Conv2D, ZeroPadding2D, Activation, Input, concatenate
from tensorflow.keras.layers import BatchNormalization, MaxPooling2D, AveragePooling2D, Concatenate
from tensorflow.keras.layers import Lambda, Flatten, Dense
from tensorflow.keras.initializers import GlorotUniform
#from tensorflow.keras.engine.topology import Layer
from tensorflow.keras import backend as K
K.set_image_data_format('channels_last')
import imageio
import os
import numpy as np
from numpy import genfromtxt
#import pandas as pd

from fr_utils import *
from inception_blocks_v2 import *

%matplotlib inline
#%load_ext autoreload
#%autoreload 2

#np.set_printoptions(threshold=np.nan)

这段代码是一个TensorFlow的深度学习模型的导入和配置部分。

  1. 导入了TensorFlow的相关库。
  2. 导入了自定义的一些模块,例如fr_utilsinception_blocks_v2,这些模块可能包含了一些自定义的深度学习层或函数。
  3. 设置了TensorFlow的图像数据格式为'channels_last'。
  4. 导入了一些其他的Python库,如NumPy和imageio。

这段代码还包含了一些Jupyter Notebook的魔法命令,例如%matplotlib inline,它用于在Jupyter Notebook中显示绘图。

此外,代码中还有一些注释,但是由于没有提供fr_utils.pyinception_blocks_v2.py的代码,因此无法确定这些模块的具体内容和功能。这些模块可能包含了与人脸识别或相关任务有关的深度学习模型和功能。

FRmodel = faceRecoModel(input_shape=(96, 96, 3))

这行代码创建了一个名为FRmodel的人脸识别模型。这个模型的输入形状是(96, 96, 3),表示输入图像的尺寸为96x96像素,通道数为3(对应RGB彩色图像)。

faceRecoModel可能是一个自定义的人脸识别模型,根据输入的图像进行人脸识别任务。该模型可能由多个卷积层、池化层、全连接层等组成,以便对输入图像进行特征提取和识别。

print("Total Params:", FRmodel.count_params())

这行代码打印了FRmodel模型的总参数数量,即模型中可学习的参数的数量。参数数量是衡量深度学习模型复杂度的一种指标,通常与模型的规模和容量相关。

通过打印参数数量,可以了解模型的大小以及需要训练的参数数量。这对于模型的内存占用和计算资源要求等方面的估计非常有用。

# 实现三元组损失函数

def triplet_loss(y_true, y_pred, alpha = 0.2):
    """
    参数:
    y_true -- 这个参数我们暂时不用
    y_pred -- 这是一个python列表,里面包含了3个对象,分别是A,P,N的编码。
    
    返回值:
    loss -- 损失值
    """
    
    anchor, positive, negative = y_pred[0], y_pred[1], y_pred[2]

    # 计算A与P的编码差异,就是公式中标(1)的那块
    pos_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, positive)))
    # 计算A与N的编码差异,就是公式中标(2)的那块
    neg_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, negative)))
    # 根据两组编码差异来计算损失
    basic_loss = tf.add(tf.subtract(pos_dist, neg_dist), alpha)
    loss = tf.maximum(tf.reduce_mean(basic_loss), 0.0)
    
    return loss

这段代码实现了一个三元组损失函数 triplet_loss,用于训练人脸识别模型。这种损失函数通常用于将同一个人的人脸图像编码后的特征向量更近地靠拢,而将不同人的人脸图像编码后的特征向量更远地分开。

  • y_true:这个参数目前没有被使用,通常在Keras的损失函数中需要,但在这个实现中没有使用。

  • y_pred:这个参数是一个Python列表,其中包含3个对象,分别是anchorpositivenegative的编码。这三个编码分别表示锚定图像(anchor image)、正样本图像(positive image)和负样本图像(negative image)的特征编码。

  • alpha:这是一个超参数,用于控制编码之间的间隔。它表示在计算编码之间的差异时,允许正样本和负样本之间的距离小于alpha

接下来,损失函数计算了以下几个步骤:

  1. 计算锚定图像(A)和正样本图像(P)的编码差异(pos_dist):它是两个特征向量之间的平方欧氏距离。

  2. 计算锚定图像(A)和负样本图像(N)的编码差异(neg_dist):同样是两个特征向量之间的平方欧氏距离。

  3. 计算基本损失(basic_loss):这是pos_distneg_dist之间的差异,再加上alpha。这个差异表示了正样本和负样本之间的距离差异,并且保证了正样本之间的距离要小于alpha

  4. 最后,通过使用 tf.maximum 将基本损失与0比较,确保损失值不小于0。然后,计算平均损失值。

这个三元组损失函数的目标是最小化同一个人的人脸编码之间的距离(锚定图像与正样本图像),并最大化不同人的人脸编码之间的距离(锚定图像与负样本图像)。通过调整超参数alpha,可以控制编码之间的间隔。

tf.random.set_seed(1)
y_true = (None, None, None)
y_pred = (tf.random.normal([3, 128], mean=6, stddev=0.1),
          tf.random.normal([3, 128], mean=1, stddev=1),
          tf.random.normal([3, 128], mean=3, stddev=4))
loss = triplet_loss(y_true, y_pred)
    
tf.print("loss = ", loss)

这段代码使用了之前定义的triplet_loss函数来计算三元组损失。以下是代码的执行过程和结果:

  1. tf.random.set_seed(1):这行代码设置了TensorFlow的随机种子,以确保随机生成的数值在每次执行时保持一致。

  2. y_true = (None, None, None):在这里,y_true被设置为None,但在triplet_loss函数中没有使用,因为该函数的实现中并没有使用y_true参数。

  3. y_pred:这个变量包含了三个特征编码向量,分别代表锚定图像、正样本图像和负样本图像的编码。这些编码是通过使用tf.random.normal来生成的,具有不同的均值和标准差。

  4. loss = triplet_loss(y_true, y_pred):这一行代码调用了triplet_loss函数来计算三元组损失,传递了y_truey_pred作为参数。

  5. tf.print("loss = ", loss):最后,通过tf.print打印了计算得到的损失值。

这段代码的目的是演示如何使用triplet_loss函数来计算三元组损失,但由于y_true没有在函数中使用,因此实际上并没有使用真正的标签。在实际应用中,y_true通常包含真实的标签信息,以便进行监督学习任务。

您可以根据自己的需求替换y_truey_pred,以计算实际人脸识别任务中的三元组损失。

FRmodel.compile(optimizer = 'adam', loss = triplet_loss, metrics = ['accuracy'])
load_weights_from_FaceNet(FRmodel)

这段代码涉及到人脸识别模型的编译和加载权重的过程

  1. FRmodel.compile(optimizer='adam', loss=triplet_loss, metrics=['accuracy']):这行代码编译了FRmodel模型。具体地:

    • optimizer='adam':选择了Adam优化器作为训练模型的优化器。
    • loss=triplet_loss:指定了损失函数为之前定义的triplet_loss函数。这是人脸识别任务的自定义损失函数,用于训练模型以使编码向量满足三元组约束。
    • metrics=['accuracy']:设置了评估指标,这里选择了准确率作为评估指标。
  2. load_weights_from_FaceNet(FRmodel):这行代码调用了一个自定义的函数load_weights_from_FaceNet,该函数的作用是加载预训练的权重到FRmodel模型中。这种预训练权重通常是在大规模人脸识别数据集上训练的,可以提供更好的初始参数以加速模型的收敛。

总之,这段代码准备了FRmodel模型以进行人脸识别任务的训练。它指定了优化器、损失函数和评估指标,并加载了预训练的权重,以便模型具有良好的初始参数。

database = {}
database["danielle"] = img_to_encoding("images/danielle.png", FRmodel)
database["younes"] = img_to_encoding("images/younes.jpg", FRmodel)
database["tian"] = img_to_encoding("images/tian.jpg", FRmodel)
database["andrew"] = img_to_encoding("images/andrew.jpg", FRmodel)
database["kian"] = img_to_encoding("images/kian.jpg", FRmodel)
database["dan"] = img_to_encoding("images/dan.jpg", FRmodel)
database["sebastiano"] = img_to_encoding("images/sebastiano.jpg", FRmodel)
database["bertrand"] = img_to_encoding("images/bertrand.jpg", FRmodel)
database["kevin"] = img_to_encoding("images/kevin.jpg", FRmodel)
database["felix"] = img_to_encoding("images/felix.jpg", FRmodel)
database["benoit"] = img_to_encoding("images/benoit.jpg", FRmodel)
database["arnaud"] = img_to_encoding("images/arnaud.jpg", FRmodel)

这段代码创建了一个名为database的字典,用于存储人脸图像的编码向量。每个键(如"danielle"、"younes"等)代表一个人的名字,对应的值是该人脸图像的编码向量。编码向量是通过调用img_to_encoding函数生成的,该函数将人脸图像转换为特定模型(FRmodel)生成的编码向量。

这个数据库似乎用于存储已知人脸的编码,以便在后续的人脸识别任务中使用。具体来说,每个人的编码向量将用于与输入的人脸图像进行比对,以确定输入图像中是否存在已知的人脸。

# 验证人脸

def verify(image_path, identity, database, model):
    """
    
    参数:
    image_path -- 需要被验证的人脸图片地址,也就是摄像头拍摄到的人脸图片
    identity -- 人名,也就是扫描ID后得到的人名
    database -- 人脸数据库
    model -- 前面加载的已经训练好了的FaceNet模型
    
    返回值:
    dist -- 返回image_path的人脸编码与identity指定的人名的人脸编码的差异
    door_open -- 如果返回True, 就表示image_path和identity是同一个人,那么验证通过,门自动为他开启
    """
    
    encoding = img_to_encoding(image_path, model)
    
    # 对比identity关联的图片编码与image_path关联的图片编码的差异
    dist = np.linalg.norm(encoding-database[identity])
    
    # 差异小于0.7,就表明是同一个人。
    # 当然,因为我们的程序的目的是教学,所以精度不高,实际应用中会取比0.7更小的阈值
    if dist < 0.7:
        print("It's " + str(identity) + ", welcome home!")
        door_open = True
    else:
        print("It's not " + str(identity) + ", please go away")
        door_open = False
        
    return dist, door_open

这段代码定义了一个名为verify的函数,用于验证输入的人脸图像是否属于指定的身份(identity)。

  • image_path:需要被验证的人脸图像的文件路径,通常是通过摄像头拍摄到的人脸图像。

  • identity:人名,即扫描ID后得到的人名,表示要验证的身份。

  • database:人脸数据库,存储了已知人脸的编码向量,用于与输入图像进行比对。

  • model:已经加载并训练好的FaceNet模型,用于将输入图像转换为编码向量。

函数的主要工作包括:

  1. 使用img_to_encoding函数将输入的人脸图像(image_path)转换为编码向量(encoding)。

  2. 计算输入图像的编码向量与指定身份的编码向量之间的欧氏距离(dist)。

  3. 判断欧氏距离是否小于0.7(这个阈值可以根据需要调整)。如果距离小于0.7,函数会打印验证通过的消息,并返回door_open为True,表示门应该自动打开。否则,函数会打印验证失败的消息,并返回door_open为False,表示门不会打开。

总之,这个函数用于验证输入的人脸图像是否与指定身份的人脸匹配。如果匹配成功,门会自动打开;如果匹配失败,门不会打开。

verify("images/camera_0.jpg", "younes", database, FRmodel)

这行代码调用了之前定义的verify函数,用于验证位于文件路径"images/camera_0.jpg"的人脸图像是否属于身份"younes"。下面是代码的执行过程和可能的结果:

  1. 首先,函数使用img_to_encoding函数将输入的人脸图像"images/camera_0.jpg"转换为编码向量。

  2. 然后,函数计算输入图像的编码向量与数据库中身份"younes"对应的编码向量之间的欧氏距离。

  3. 如果欧氏距离小于0.7,函数会打印消息"It's younes, welcome home!",并将door_open设置为True,表示门自动打开。

  4. 如果欧氏距离大于等于0.7,函数会打印消息"It's not younes, please go away",并将door_open设置为False,表示门不会打开。

最终,函数会返回欧氏距离(dist)和门的开启状态(door_open)。

请注意,欧氏距离的阈值0.7是一个示例值,用于演示目的。在实际应用中,您可能需要根据实际情况和精度要求来调整这个阈值。

verify("images/camera_2.jpg", "kian", database, FRmodel)

这行代码调用了verify函数,用于验证位于文件路径"images/camera_2.jpg"的人脸图像是否属于身份"kian"。根据verify函数的逻辑,以下是可能的结果:

  • 如果人脸图像"images/camera_2.jpg"与身份"kian"的编码非常相似(欧氏距离小于0.7),则函数会打印消息"It's kian, welcome home!",并将door_open设置为True,表示门应该自动打开。

  • 如果人脸图像"images/camera_2.jpg"与身份"kian"的编码差异较大(欧氏距离大于等于0.7),则函数会打印消息"It's not kian, please go away",并将door_open设置为False,表示门不会打开。

根据具体的欧氏距离,上述的两种情况中的一种将会发生。

请注意,这个代码示例用于演示人脸验证的基本原理,实际应用中可能需要更复杂的流程和更精确的距离阈值来确保安全性和准确性。

# 人脸识别

def who_is_it(image_path, database, model):
    """    
    参数:
    image_path -- 需要被识别的人脸图像的地址
    database -- 有权开门的人脸数据库
    model -- 前面加载的已经训练好了的FaceNet模型
    
    Returns:
    min_dist -- 最小差异。我们会一个个与数据库中的人脸编码进行对比,找到与待识别的图像差异最小的那个图像
    identity -- 差异最小的人脸图像关联的那个人的名字
    """
    
    encoding = img_to_encoding(image_path, model)
    
    min_dist = 100
    
    # 与数据库中的人脸一个个的进行对比
    for (name, db_enc) in database.items():
        
        # 进行对比
        dist = np.linalg.norm(encoding-db_enc)

        # 保存差异最小的那个
        if dist < min_dist:
            min_dist = dist
            identity = name
    
    if min_dist > 0.7:
        print("Not in the database.")
    else:
        print ("it's " + str(identity) + ", the distance is " + str(min_dist))
        
    return min_dist, identity

这段代码定义了一个名为who_is_it的函数,用于识别输入的人脸图像属于数据库中的哪个人。

  • image_path:需要被识别的人脸图像的文件路径。

  • database:包含有权开门的人脸数据库,其中每个键是一个人的名字,对应的值是该人的人脸编码向量。

  • model:已经加载并训练好的FaceNet模型,用于将输入图像转换为编码向量。

函数的主要工作包括:

  1. 使用img_to_encoding函数将输入的人脸图像(image_path)转换为编码向量(encoding)。

  2. 初始化min_dist为一个较大的值(例如100),用于保存差异最小的人脸编码的欧氏距离。

  3. 遍历数据库中的每个人脸编码,计算输入图像的编码向量与数据库中每个编码向量的欧氏距离。

  4. 如果找到了欧氏距离小于min_dist的编码,就将min_dist更新为找到的最小距离,并将identity设置为对应的人名。

  5. 最后,根据最小距离(min_dist)判断输入图像是否属于数据库中的某个人。如果最小距离大于0.7,函数会打印"Not in the database."的消息;否则,会打印"it's [identity]"以及距离信息。

函数返回最小距离(min_dist)和被识别的人的名字(identity)。

请注意,欧氏距离的阈值0.7是一个示例值,用于演示目的。在实际应用中,您可能需要根据具体情况来调整此阈值以获得更好的识别精度。

who_is_it("images/camera_0.jpg", database, FRmodel)

这行代码调用了who_is_it函数,用于识别位于文件路径"images/camera_0.jpg"的人脸图像属于数据库中的哪个人。根据who_is_it函数的逻辑,以下是可能的结果:

  • 如果人脸图像"images/camera_0.jpg"与数据库中的某个人脸编码非常相似(欧氏距离小于0.7),函数会打印消息"it's [identity]",其中[identity]是与输入图像最相似的人的名字,以及距离信息。

  • 如果人脸图像"images/camera_0.jpg"与数据库中的所有人脸编码都差异较大(欧氏距离大于等于0.7),函数会打印消息"Not in the database.",表示没有找到匹配的人脸。

请注意,欧氏距离的阈值0.7是一个示例值,用于演示目的。在实际应用中,您可能需要根据具体情况来调整此阈值以获得更好的识别精度。