deeplearning.37人脸识别算法实践

准备相关资料和数据

使用pycharm新建工程,然后下载相关资料和数据到工程文件夹下,并新建一个face.py进行后续编写代码。资料下载地址

人脸识别简介

人脸识别系统通常被分为两大类:

  • 人脸验证:“这是不是本人呢?”,比如说,在某些机场你能够让系统扫描您的面部并验证您是否为本人从而使得您免人工检票通过海关,又或者某些手机能够使用人脸解锁功能。这些都是1:1匹配问题。

  • 人脸识别:“这个人是谁?”,比如说,在视频中的百度员工进入办公室时的脸部识别视频的介绍,无需使用另外的ID卡。这个是1:K的匹配问题。

FaceNe(一个人脸识别网络)t可以将人脸图像编码为一个128位数字的向量从而进行学习,通过比较两个这样的向量,那么我们就可以确定这两张图片是否是属于同一个人。

导入相关的包

from keras.models import Sequential
from keras.layers import Conv2D, ZeroPadding2D, Activation, Input, concatenate
from keras.models import Model
from keras.layers import BatchNormalization
from keras.layers.pooling import MaxPooling2D, AveragePooling2D
from keras.layers.merge import Concatenate
from keras.layers.core import Lambda, Flatten, Dense
from keras.initializers import glorot_uniform
import keras.engine.saving
from keras import backend as K

#------------用于绘制模型细节,可选--------------#
from IPython.display import SVG
from keras.utils.vis_utils import model_to_dot
from keras.utils.vis_utils import plot_model
#------------------------------------------------#

K.set_image_data_format('channels_first')

import time
import cv2
import os
import numpy as np
from numpy import genfromtxt
import pandas as pd
import tensorflow as tf
import fr_utils
from inception_blocks_v2 import *

简单的人脸验证

在人脸验证中,你需要给出两张照片并想知道是否是同一个人,最简单的方法是逐像素地比较这两幅图像,如果图片之间的误差小于选择的阈值(一个误差设定值),那么则可能是同一个人。左图是数据库图像,右图是输入图像,如果误差小于设定值,那么输出预测y为1,同一个人。
在这里插入图片描述
当然,此算法的性能确实很差,因为像素值会由于光照,人脸方向,甚至头部位置的微小变化等因素而急剧变化。你会发现,可以编码f(img)而不是使用原始图像,这样对该编码进行逐元素比较就可以更准确地判断两张图片是否属于同一个人。

人脸图像编为128维向量

使用ConvNet计算编码

FaceNet模型需要大量训练数据并需要很长时间去训练。因此,按照深度学习中的常规应用做法,我们加载已经训练好的权重。可以查看文件inception_blocks.py以了解其实现方式。训练好的权重和blocks.py等文件都在下载的资料里。

关键信息如下

  • 该网络使用了96✖96的RGB图像作为输入数据,图像数量为m,输入的数据维度为(m,nc,nh,nw)=(m,3,96,96)
  • 输出为(m,128)的已经编码的m个128位的向量。

运行以下代码创建一个人脸识别的模型

#获取模型
FRmodel = faceRecoModel(input_shape=(3,96,96))

#打印模型的总参数数量
print("参数数量:" + str(FRmodel.count_params()))

输出如下
在这里插入图片描述
注意:当运行时出现跟importBatchNormLization有关的错误时,把引入代码换为如下

from keras.layers import BatchNormalization

通过使用128神经元全连接层作为最后一层,该模型确保输出是大小为128的编码向量,然后使用比较两个人脸图像的编码如下
在这里插入图片描述
因此,如果满足下面两个条件的话,编码是一个比较好的方法:

  • 同一个人的两个图像的编码非常相似。

  • 两个不同人物的图像的编码非常不同。

  • 三元组损失函数是给定三个图片,一个基本样例,一个同一人的另张相似图,一个别人的图,进行计算对比。

我们将使用三元组图像 ( A , P , N )进行训练:

A是“Anchor”,是一个人的图像。

P是“Positive”,是相对于“Anchor”的同一个人的另外一张图像。

N是“Negative”,是相对于“Anchor”的不同的人的另外一张图像。
在这里插入图片描述
注意:

  • 项(1)是给定三元组的锚示例“A”与正示例“P”之间的平方距离;期望最小化的值。
  • 项(2)是给定三元组的锚示例“A”和负示例“N”之间的平方距离,期望该值相对较大,因此在它前面有一个负号是有意义的。
  • α称为边距。它是一个超参数可以手动调节。我们将使用α=0.2。

定义三元组损失函数 并测试输出

# 定义三元组损失函数
def triplet_loss(y_true, y_pred, alpha=0.2):
    """
    根据公式(4)实现三元组损失函数

    参数:
        y_true -- true标签,当你在Keras里定义了一个损失函数的时候需要它,但是这里不需要。
        y_pred -- 列表类型,包含了如下参数:
            anchor -- 给定的“anchor”图像的编码,维度为(None,128)
            positive -- “positive”图像的编码,维度为(None,128)
            negative -- “negative”图像的编码,维度为(None,128)
        alpha -- 超参数,阈值

    返回:
        loss -- 实数,损失的值
    """
    # 获取anchor, positive, negative的图像编码
    anchor, positive, negative = y_pred[0], y_pred[1], y_pred[2]

    # 第一步:计算"anchor" 与 "positive"之间编码的距离,这里需要使用axis=-1
    pos_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, positive)), axis=-1)

    # 第二步:计算"anchor" 与 "negative"之间编码的距离,这里需要使用axis=-1
    neg_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, negative)), axis=-1)

    # 第三步:减去之前的两个距离,然后加上alpha
    basic_loss = tf.add(tf.subtract(pos_dist, neg_dist), alpha)

    # 通过取带零的最大值和对训练样本的求和来计算整个公式
    loss = tf.reduce_sum(tf.maximum(basic_loss, 0))

    return loss
#测试一下
with tf.compat.v1.Session() as test:
    tf.compat.v1.set_random_seed(1)
    y_true = (None, None, None)
    y_pred = (tf.compat.v1.random_normal([3, 128], mean=6, stddev=0.1, seed=1),
              tf.compat.v1.random_normal([3, 128], mean=1, stddev=1, seed=1),
              tf.compat.v1.random_normal([3, 128], mean=3, stddev=4, seed=1))
    loss = triplet_loss(y_true, y_pred)

    print("loss = " + str(loss.eval()))

输出如下
在这里插入图片描述

加载训练后的模型

通过最小化三元组损失来训练FaceNet。但是由于训练需要大量数据和计算,因此在这里我们不会从头开始进行训练。我们加载以前训练好的模型。使用以下单元格加载模型;这将花费几分钟才能运行。

# 加载训练好的模型
#开始时间
start_time = time.perf_counter()

#编译模型
FRmodel.compile(optimizer = 'adam', loss = triplet_loss, metrics = ['accuracy'])

#加载权值
fr_utils.load_weights_from_FaceNet(FRmodel)

#结束时间
end_time = time.perf_counter()

#计算时差
minium = end_time - start_time

print("执行了:" + str(int(minium / 60)) + "分" + str(int(minium%60)) + "秒")

输出如下
在这里插入图片描述
现在使用这个模型进行人脸识别和验证

人脸验证

我们建立一个数据库,设立人脸识别,设置能否允许人们进入某个房屋。我们使用img_to_encoding(image_path, model)生成编码,它基本上在指定的图像上运行模型的正向传播。我们这里的数据库使用的是一个字典来表示,这个字典将每个人的名字映射到他们面部的128维编码上

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

现在,当有人出现在门前刷他们的身份证的时候,你可以在数据库中查找他们的编码,用它来检查站在门前的人是否与身份证上的名字匹配。

实现 verify() 函数,该函数检查前门摄像头拍摄到的图片(image_path)是否是本人。你需要执行以下步骤:

  • 从image_path计算图像的编码
  • 计算此编码和存储在数据库中的身份图像的编码的距离
  • 如果距离小于0.7,打开门,否则不要打开。
  • 如上所述,你应该使用L2距离(np.linalg.norm)。

定义一个检验函数,并验证一下

# 定义检验函数
def verify(image_path, identity, database, model):
    """
    对“identity”与“image_path”的编码进行验证。

    参数:
        image_path -- 摄像头的图片。
        identity -- 字符类型,想要验证的人的名字。
        database -- 字典类型,包含了成员的名字信息与对应的编码。
        model -- 在Keras的模型的实例。

    返回:
        dist -- 摄像头的图片与数据库中的图片的编码的差距。
        is_open_door -- boolean,是否该开门。
    """
    # 第一步:计算图像的编码,使用fr_utils.img_to_encoding()来计算。
    encoding = fr_utils.img_to_encoding(image_path, model)

    # 第二步:计算与数据库中保存的编码的差距
    dist = np.linalg.norm(encoding - database[identity])

    # 第三步:判断是否打开门
    if dist < 0.7:
        print("欢迎 " + str(identity) + "回家!")
        is_door_open = True
    else:
        print("经验证,您与" + str(identity) + "不符!")
        is_door_open = False

    return dist, is_door_open
# 验证一下
verify("images/camera_0.jpg","younes",database,FRmodel)

人脸识别

我们将实现一个人脸识别系统,该系统将图像作为输入,并确定它是否是授权人员之一(如果是,是谁),与之前的人脸验证系统不同,我们不再将一个人的名字作为输入的一部分。
实现who_is_it(),你需要执行以下步骤:

  • 从image_path计算图像的目标编码
  • 从数据库中查找与目标编码距离最短的编码。
    • 将min_dist变量初始化为足够大的数字(100)。这将帮助你跟踪最接近输入编码的编码。
    • 遍历数据库字典的名称和编码。循环使用for (name, db_enc) in database.items()。
      • 计算目标“编码”与数据库中当前“编码”之间的L2距离。
      • 如果此距离小于min_dist,则将min_dist设置为dist,并将identity设置为name。

定义人脸识别并测试一下

# 人脸识别
def who_is_it(image_path, database, model):
    """
    根据指定的图片来进行人脸识别

    参数:
        images_path -- 图像地址
        database -- 包含了名字与编码的字典
        model -- 在Keras中的模型的实例。

    返回:
        min_dist -- 在数据库中与指定图像最相近的编码。
        identity -- 字符串类型,与min_dist编码相对应的名字。
    """
    # 步骤1:计算指定图像的编码,使用fr_utils.img_to_encoding()来计算。
    encoding = fr_utils.img_to_encoding(image_path, model)

    # 步骤2 :找到最相近的编码
    ## 初始化min_dist变量为足够大的数字,这里设置为100
    min_dist = 100

    ## 遍历数据库找到最相近的编码
    for (name, db_enc) in database.items():
        ### 计算目标编码与当前数据库编码之间的L2差距。
        dist = np.linalg.norm(encoding - db_enc)

        ### 如果差距小于min_dist,那么就更新名字与编码到identity与min_dist中。
        if dist < min_dist:
            min_dist = dist
            identity = name

    # 判断是否在数据库中
    if min_dist > 0.7:
        print("抱歉,您的信息不在数据库中。")

    else:
        print("姓名" + str(identity) + "  差距:" + str(min_dist))

    return min_dist, identity
#测试一下
who_is_it("images/camera_0.jpg", database, FRmodel)

预期出现结果如下

姓名younes  差距:0.659392
(0.65939206, 'younes')

完整代码

face.py完整代码,注意这里会因为keras,tensorflow版本问题出现很多bug。此代码在加载训练模型之前适用于keras,tensorflow2.0以上版本,后部分代码,bug太多,没有修改过来。

from keras.models import Sequential
from keras.layers import Conv2D, ZeroPadding2D, Activation, Input, concatenate
from keras.models import Model
from keras.layers import BatchNormalization
from keras.layers.pooling import MaxPooling2D, AveragePooling2D
from keras.layers.merge import Concatenate
from keras.layers.core import Lambda, Flatten, Dense
from keras.initializers import glorot_uniform
import keras.engine.saving
from keras import backend as K

#------------用于绘制模型细节,可选--------------#
from IPython.display import SVG
from keras.utils.vis_utils import model_to_dot
from keras.utils.vis_utils import plot_model
#------------------------------------------------#

K.set_image_data_format('channels_first')

import time
import cv2
import os
import numpy as np
from numpy import genfromtxt
import pandas as pd
import tensorflow as tf
import fr_utils
from inception_blocks_v2 import *


#获取模型
FRmodel = faceRecoModel(input_shape=(3,96,96))

# #打印模型的总参数数量
# print("参数数量:" + str(FRmodel.count_params()))

# 定义三元组损失函数
def triplet_loss(y_true, y_pred, alpha=0.2):
    """
    根据公式(4)实现三元组损失函数

    参数:
        y_true -- true标签,当你在Keras里定义了一个损失函数的时候需要它,但是这里不需要。
        y_pred -- 列表类型,包含了如下参数:
            anchor -- 给定的“anchor”图像的编码,维度为(None,128)
            positive -- “positive”图像的编码,维度为(None,128)
            negative -- “negative”图像的编码,维度为(None,128)
        alpha -- 超参数,阈值

    返回:
        loss -- 实数,损失的值
    """
    # 获取anchor, positive, negative的图像编码
    anchor, positive, negative = y_pred[0], y_pred[1], y_pred[2]

    # 第一步:计算"anchor" 与 "positive"之间编码的距离,这里需要使用axis=-1
    pos_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, positive)), axis=-1)

    # 第二步:计算"anchor" 与 "negative"之间编码的距离,这里需要使用axis=-1
    neg_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, negative)), axis=-1)

    # 第三步:减去之前的两个距离,然后加上alpha
    basic_loss = tf.add(tf.subtract(pos_dist, neg_dist), alpha)

    # 通过取带零的最大值和对训练样本的求和来计算整个公式
    loss = tf.reduce_sum(tf.maximum(basic_loss, 0))

    return loss
# #测试一下
# with tf.compat.v1.Session() as test:
#     tf.compat.v1.set_random_seed(1)
#     y_true = (None, None, None)
#     y_pred = (tf.compat.v1.random_normal([3, 128], mean=6, stddev=0.1, seed=1),
#               tf.compat.v1.random_normal([3, 128], mean=1, stddev=1, seed=1),
#               tf.compat.v1.random_normal([3, 128], mean=3, stddev=4, seed=1))
#     loss = triplet_loss(y_true, y_pred)
#
#     print("loss = " + str(loss.eval()))

# 加载训练好的模型
#开始时间
start_time = time.perf_counter()

#编译模型
FRmodel.compile(optimizer = 'adam', loss = triplet_loss, metrics = ['accuracy'])

#加载权值
fr_utils.load_weights_from_FaceNet(FRmodel)

#结束时间
end_time = time.perf_counter()

#计算时差
minium = end_time - start_time

print("执行了:" + str(int(minium / 60)) + "分" + str(int(minium%60)) + "秒")

# 设置人脸匹配数据库
database = {}
database["danielle"] = fr_utils.img_to_encoding("images/danielle.png", FRmodel)
database["younes"] = fr_utils.img_to_encoding("images/younes.jpg", FRmodel)
database["tian"] = fr_utils.img_to_encoding("images/tian.jpg", FRmodel)
database["andrew"] = fr_utils.img_to_encoding("images/andrew.jpg", FRmodel)
database["kian"] = fr_utils.img_to_encoding("images/kian.jpg", FRmodel)
database["dan"] = fr_utils.img_to_encoding("images/dan.jpg", FRmodel)
database["sebastiano"] = fr_utils.img_to_encoding("images/sebastiano.jpg", FRmodel)
database["bertrand"] = fr_utils.img_to_encoding("images/bertrand.jpg", FRmodel)
database["kevin"] = fr_utils.img_to_encoding("images/kevin.jpg", FRmodel)
database["felix"] = fr_utils.img_to_encoding("images/felix.jpg", FRmodel)
database["benoit"] = fr_utils.img_to_encoding("images/benoit.jpg", FRmodel)
database["arnaud"] = fr_utils.img_to_encoding("images/arnaud.jpg", FRmodel)

# 定义检验函数
def verify(image_path, identity, database, model):
    """
    对“identity”与“image_path”的编码进行验证。

    参数:
        image_path -- 摄像头的图片。
        identity -- 字符类型,想要验证的人的名字。
        database -- 字典类型,包含了成员的名字信息与对应的编码。
        model -- 在Keras的模型的实例。

    返回:
        dist -- 摄像头的图片与数据库中的图片的编码的差距。
        is_open_door -- boolean,是否该开门。
    """
    # 第一步:计算图像的编码,使用fr_utils.img_to_encoding()来计算。
    encoding = fr_utils.img_to_encoding(image_path, model)

    # 第二步:计算与数据库中保存的编码的差距
    dist = np.linalg.norm(encoding - database[identity])

    # 第三步:判断是否打开门
    if dist < 0.7:
        print("欢迎 " + str(identity) + "回家!")
        is_door_open = True
    else:
        print("经验证,您与" + str(identity) + "不符!")
        is_door_open = False

    return dist, is_door_open
# 验证一下
verify("images/camera_0.jpg","younes",database,FRmodel)

# 人脸识别
def who_is_it(image_path, database, model):
    """
    根据指定的图片来进行人脸识别

    参数:
        images_path -- 图像地址
        database -- 包含了名字与编码的字典
        model -- 在Keras中的模型的实例。

    返回:
        min_dist -- 在数据库中与指定图像最相近的编码。
        identity -- 字符串类型,与min_dist编码相对应的名字。
    """
    # 步骤1:计算指定图像的编码,使用fr_utils.img_to_encoding()来计算。
    encoding = fr_utils.img_to_encoding(image_path, model)

    # 步骤2 :找到最相近的编码
    ## 初始化min_dist变量为足够大的数字,这里设置为100
    min_dist = 100

    ## 遍历数据库找到最相近的编码
    for (name, db_enc) in database.items():
        ### 计算目标编码与当前数据库编码之间的L2差距。
        dist = np.linalg.norm(encoding - db_enc)

        ### 如果差距小于min_dist,那么就更新名字与编码到identity与min_dist中。
        if dist < min_dist:
            min_dist = dist
            identity = name

    # 判断是否在数据库中
    if min_dist > 0.7:
        print("抱歉,您的信息不在数据库中。")

    else:
        print("姓名" + str(identity) + "  差距:" + str(min_dist))

    return min_dist, identity
#测试一下
who_is_it("images/camera_0.jpg", database, FRmodel)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值