65.9K
CodeProject 正在变化。 阅读更多。
Home

人脸识别简介

2021年7月28日

CPOL

6分钟阅读

viewsIcon

13304

downloadIcon

3338

在本文中,我们将重点开发和测试人脸识别算法以及人脸检测模块。

引言

人脸识别是人工智能(AI)的一个领域,在过去十年中,深度学习(DL)的现代方法在该领域取得了巨大成功。最好的人脸识别系统能够以与人类相同甚至更高的精度识别图像和视频中的人物。

我们关于这个系列文章分为两部分

  • 人脸检测,客户端应用程序在图像或视频源中检测人脸,对检测到的人脸图像进行对齐,并将它们提交给服务器。
  • 人脸识别(本部分),服务器端应用程序执行人脸识别。

在本系列文章的这一部分,我们将讨论人脸识别问题,并将之前开发的人脸检测器与人脸识别器结合起来。然后,我们将在Docker容器中实现人脸识别,并添加一个Web API来将检测到的人脸传输到运行识别服务的服务器。此外,我们将考虑在Kubernetes中运行人脸识别的某些方面。最后,我们将讨论如何从头开始构建人脸识别系统。

我们假设您熟悉 DNN、Python、Keras 和 TensorFlow。欢迎下载此项目代码以进行学习。

人脸识别

人脸识别可以描述为在人脸数据库中查找与正在识别的那个最相似的人。在上篇文章(本系列前半部分最后一篇)中,我们创建了一个包含15个人的数据库。在本系列后半部分,我们将更详细地研究人脸识别任务,并使用预训练的DNN模型开发一个用于在视频流中识别人的算法。

我们有一个包含人物样本图像的人脸数据库,每张照片对应一个人。当在图像或视频中检测到人脸时,检测器会生成每张检测到的人脸的图像。我们必须确定这张图片中的人脸是否属于我们数据库中的某个人。有两种可能的情况:

  • 检测到的人脸属于数据库中的某个人,在这种情况下,我们必须指定该人的ID(例如,他们的姓名)。
  • 检测到的人脸属于一个未知的人,在这种情况下,我们必须说明这一事实。

人脸识别模型

所有现代最先进的人脸识别方法都使用DNN模型进行人脸识别。这些模型可以有不同的架构,并且可以在不同的人脸数据库上进行训练。然而,它们使用类似的方法来实现目标。首先,DNN模型被用作特征提取器,以获取人脸图像的嵌入。然后,使用这些嵌入来确定人脸的相似程度,这被称为两个面部图像之间的“距离”——距离越小,人脸越相似。通过评估检测到的人脸与数据库中所有人的距离,我们可以找到最相似的人。

开发和训练人脸识别的DNN模型并非易事。幸运的是,有许多免费的预训练模型和库实现了最先进的用于人脸识别的DNN架构。例如:

以上每个模型都有其优点和缺点。最佳选择取决于具体情况。在本系列中,我们将出于两个原因使用FaceNet预训练模型:

  • 该模型经过训练,可以直接优化人脸嵌入,无需中间层。
  • 由于该模型是用Keras实现的,因此我们可以用几行Python代码运行识别算法。

人脸识别器代码

现在是时候编写人脸识别器的代码了。基于DNN的人脸识别器必须至少实现两个函数:一个用于从人脸图像提取嵌入,另一个用于评估两张人脸图像嵌入之间的距离。以下是我们基于FaceNet DNN模型编写识别器的方法:

class FaceNetRec:    
    def __init__(self, model, min_distance):
        self.model = load_model(model)
        self.min_distance = min_distance
    
    def get_model(self):
        return self.model
    
    def embeddings(self, f_img):
        r_img = cv2.resize(f_img, (160, 160), cv2.INTER_AREA)
        arr =  r_img.astype('float32')
        arr = (arr-127.5)/127.5
        samples = np.expand_dims(arr, axis=0)
        embds = self.model.predict(samples)
        return embds[0]
    
    def eval_distance(self, embds1, embds2):
        dist = distance.cosine(embds1, embds2)
        return dist 
    
    def img_distance(self, f_img1, f_img2):
        embds1 = self.embeddings(f_img1)
        embds2 = self.embeddings(f_img2)
        dist = self.eval_distance(embds1, embds2)
        return dist 
    
    def match(self, embds1, embds2):
        dist = self.eval_distance(embds1, embds2)
        return dist <= self.min_distance
    
    def img_match(self, f_img1, f_img2):
        embds1 = self.embeddings(f_img1)
        embds2 = self.embeddings(f_img2)
        return self.match(embds1, embds2)
    
    def recognize(self, embds, f_db):
        minfd = 2.0
        indx = -1
        f_data = f_db.get_data();
        for (i, data) in enumerate(f_data):
            (name, embds_i, p_img) = data
            dist = self.eval_distance(embds, embds_i)
            if (dist<minfd) and (dist<self.min_distance):
                indx = i
                minfd = dist
        if indx>=0:
            (name, embds_i, p_img) = f_data[indx]
            return (name, minfd, p_img)
        
        return None
    
    def img_recognize(self, f_img, f_db):
        embds = self.embeddings(f_img)
        
        return self.recognize(embds, f_db)

该类的构造函数从 `model` 参数指定的文件加载Keras模型。 `min_distance` 参数指定将一张人脸图像识别为属于特定人员所需的最小距离值。 `embeddings` 和 `eval_distance` 方法实现了上述功能。我们使用所谓的余弦距离来评估嵌入的相似性。

我们想提到的另一个方法是 `recognize`。此方法以检测到的人脸的嵌入和 `f_db` 对象作为输入。此对象创建一个人脸数据库,如本文(参考“创建数据库”部分)中所述。

class FaceDB:
    def __init__(self):
        self.clear()
        
    def clear(self):
        self.f_data = []
        
    def load(self, db_path, rec):
        self.clear()
        files = FileUtils.get_files(db_path)
        for (i, fname) in enumerate(files):
            f_img = cv2.imread(fname, cv2.IMREAD_UNCHANGED)
            embds = rec.embeddings(f_img)
            f = os.path.basename(fname)
            p_name = os.path.splitext(f)[0]
            data = (p_name, embds, f_img)
            self.f_data.append(data)
            
    def get_data(self):
        return self.f_data

该类的 `load` 方法在 `db_path` 参数指定的文件夹中搜索所有文件,为找到的所有图像创建嵌入,并为数据库中的每个人构建姓名-嵌入-图像三元组。使用来自人脸数据库的数据,`recognize` 方法评估所有人员与检测到的人脸的嵌入之间的距离。最小距离表示数据库中最相似的人。请注意,如果此值大于指定的阈值,结果将为 `None`,这表示未知人脸。

将人脸识别器与人脸检测器结合

我们现在可以将开发的类与本文(参考“人脸检测”部分)中描述的MTCNN视频人脸检测器结合起来。

class VideoFR:    
    def __init__(self, detector, rec, f_db):
        self.detector = detector
        self.rec = rec
        self.f_db = f_db
    
    def process(self, video, align=False):
        detection_num = 0;
        rec_num = 0
        capture = cv2.VideoCapture(video)
        img = None

        dname = 'AI face recognition'
        cv2.namedWindow(dname, cv2.WINDOW_NORMAL)
        cv2.resizeWindow(dname, 960, 720)
        
        frame_count = 0
        dt = 0
        if align:
            fa = Face_Align_Mouth(160)
            
        # Capture all frames
        while(True):    
            (ret, frame) = capture.read()
            if frame is None:
                break
            frame_count = frame_count+1
            
            faces = self.detector.detect(frame)
            f_count = len(faces)
            detection_num += f_count
            
            names = None
            if (f_count>0) and (not (self.f_db is None)):
                t1 = time.time()
                names = [None]*f_count
                for (i, face) in enumerate(faces):
                    if align:
                        (f_cropped, f_img) = fa.align(frame, face)
                    else:
                        (f_cropped, f_img) = self.detector.extract(frame, face)
                    if (not (f_img is None)) and (not f_img.size==0):
                        embds = self.rec.embeddings(f_img)
                        data = self.rec.recognize(embds, self.f_db)
                        if not (data is None):
                            rec_num += 1
                            (name, dist, p_photo) = data
                            conf = 1.0 - dist
                            names[i] = (name, conf)
                        
                t2 = time.time()
                dt = dt + (t2-t1)
                    
            if len(faces)>0:
                Utils.draw_faces(faces, (0, 0, 255), frame, True, True, names)
            
            # Display the resulting frame
            cv2.imshow(dname,frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            
        capture.release()
        cv2.destroyAllWindows()    
        
        if dt>0:
            fps = detection_num/dt
        else:
            fps = 0
        
        return (detection_num, rec_num, fps)

在初始化时,`VideoFR` 类接收MTCNN人脸检测器、人脸识别器和人脸数据库。然后将这些组件组合成一个人脸识别管道。

测试识别器

现在我们可以使用以下代码在视频文件上运行识别:

m_file = r"C:\PI_FR\net\facenet_keras.h5"
rec = FaceNetRec(m_file, 0.5)
print("Recognizer loaded.")
print(rec.get_model().inputs)
print(rec.get_model().outputs)

db_path = r"C:\PI_FR\db"
f_db = FaceDB()
f_db.load(db_path, rec)
d = MTCNN_Detector(50, 0.95)
vr = VideoFR(d, rec, f_db)

v_file = r"C:\PI_FR\video\5_3.mp4"

(f_count, rec_count, fps) = vr.process(v_file, True)

print("Face detections: "+str(f_count))
print("Face recognitions: "+str(rec_count))
print("FPS: "+str(fps))

这是我们运行的测试结果视频。

请注意,当识别出某人时,我们在输出中绘制他们的姓名以及识别的置信度(相似度分数)。如果一个人未被识别,我们则绘制人脸检测的置信度。正如您从上述测试结果中看到的,我们的人脸识别算法在视频文件上运行良好。在大多数帧中,它都能正确识别数据库中的两个人(Lena和Marat),并且在整个视频中都没有识别出未知人物。

让我们在另外两个视频片段上进行测试。

在第一个视频中,我们获得了100%的正确结果。在第二个视频中,算法未能识别出其中一个人:它错误地命名了一个不在数据库中的人。

这展示了人脸识别系统的一个常见问题。当一个人是已知的(他的脸在数据库中)时,系统会通过分配最大的相似度值来正确地识别他们。然而,当被检测到的人是未知的时候,算法仍然可以在数据库中找到一个看起来有点像被检测到的人。

我们可以通过在初始化识别器时更改 `min_distance` 参数来解决上述问题。请注意,对那位被错误识别为已知人员的女性的置信度从未大于0.65。因此,如果我们设置最小相似度(距离)值为0.35,则不会再次发生错误的识别。

下一步

现在我们拥有了一个良好的人脸识别系统。在下一篇文章中,我们将为该系统创建一个Docker容器。敬请关注!

© . All rights reserved.