[Day 16] Facial Recognition - Local Binary Patterns (LBPs)

如果说特徵脸方法是婴儿,我会说局部二值方法是会跑会跳的幼儿

局部二值方法(Local Binary Patterns),透过将
图片中的每一个像素,与其相邻的其他像素做值的大小比对与加总,得出一个图片全域相对值

lbp_1
图片左方是原始人脸,右方是计算LBP後的结果

从上图有几个重点可以看出:

  • 整块区域有类似的特徵或颜色 (如头发、脸部等),在执行LBP後得出的值相对比较小而且差异不大
  • LBP中颜色较深的,对应到原始图片大多是人脸的特徵 (如眉毛、眼睛、鼻子等),这类特徵就是经过LBP後可以用来辨识

理论上,我们是可以透过LBP计算後的特徵值,跟昨天介绍的特徵脸方法一样,来用作人脸辨识 (只要特徵一样就表示同一人);

但实际上直接使用LBP的特徵值会受特徵相对位置不一样而产生很大的误差(比如说人脸不是照片照、人脸离镜头的位置不一样等等)。

比较可行的方式,是将LBP後的特徵图片划分多个等大小的子区域,分别做直方统计图

然後真正用来辨识的依据是这些直方统计值组合而成的局部二值直方图 (Local Binary Patterns Histogram)

讲这麽多终於进入今天的重点 -- LBPH

接下来我们会使用OpenCV内建的face_LBPHFaceRecognizer来建立我们的人脸辨识。

本文开始

  1. 在前几天的专案中,找到你的face_recognition目录,新增一个档案lbp.py
  2. 在该档案下新增以下程序码:
    import ntpath
    import sys
    
    # resolve module import error in PyCharm
    sys.path.append(ntpath.dirname(ntpath.dirname(ntpath.abspath(__file__))))
    
    # 汇入必要套件
    import argparse
    import random
    import time
    
    import cv2
    import numpy as np
    from imutils import paths
    from skimage import feature
    from sklearn.metrics import classification_report
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import LabelEncoder
    
    from dataset.load_dataset import images_to_faces
    
    
    def main():
        # 初始化arguments
        ap = argparse.ArgumentParser()
        ap.add_argument("-i", "--input", type=str, required=True, help="the input dataset path")
        args = vars(ap.parse_args())
    
        print("[INFO] loading dataset....")
        (faces, labels) = images_to_faces(args["input"])
        print(f"[INFO] {len(faces)} images in dataset")
    
        # 将名称从字串转成整数 (在做训练时时常会用到这个方法:label encoding)
        le = LabelEncoder()
        labels = le.fit_transform(labels)
    
        # 将资料拆分训练用与测试用;测试资料占总资料1/4 (方便後续我们判断这个方法的准确率)
        split = train_test_split(faces, labels, test_size=0.25, stratify=labels, random_state=9527)
        (trainX, testX, trainY, testY) = split
    
        print("[INFO] training...")
        start = time.time()
        recognizer = cv2.face_LBPHFaceRecognizer().create(radius=1, neighbors=8, grid_x=8, grid_y=8)
        recognizer.train(trainX, trainY)
        end = time.time()
        print(f"[INFO] training took: {round(end - start, 2)} seconds")
    
        # 辨识测试资料
        print("[INFO] predicting...")
        start = time.time()
        predictions = []
        confidence = []
        # loop over the test data
        for i in range(0, len(testX)):
            (prediction, conf) = recognizer.predict(testX[i])
            predictions.append(prediction)
            confidence.append(conf)
        end = time.time()
        print(f"[INFO] training took: {round(end - start, 2)} seconds")
        print(classification_report(testY, predictions, target_names=le.classes_))
    
        # 随机挑选测试资料来看结果
        idxs = np.random.choice(range(0, len(testY)), size=10, replace=False)
        for i in idxs:
            predName = le.inverse_transform([predictions[i]])[0]
            actualName = le.classes_[testY[i]]
    
            face = np.dstack([testX[i]] * 3)
            face = imutils.resize(face, width=250)
    
            cv2.putText(face, f"pred:{predName}", (5, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
            cv2.putText(face, f"actual:{actualName}", (5, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
    
            print(f"[INFO] prediction: {predName}, actual: {actualName}")
            cv2.imshow("Face", face)
            cv2.waitKey(0)
    
    if __name__ == '__main__':
        main()
    
    
  3. 在terminal下输入python face_recognition/lbp.py -i dataset/caltech_faces观看结果:
    最後辨识准确率有95% (可以通过更改程序第47行的radiusneighbors参数来提高辨识率)
                   precision    recall  f1-score   support
    
            man_1       0.83      1.00      0.91         5
           man_10       1.00      1.00      1.00         6
           man_11       0.86      1.00      0.92         6
           man_13       1.00      1.00      1.00         5
           man_14       1.00      0.86      0.92         7
           man_15       0.86      1.00      0.92         6
            man_2       1.00      1.00      1.00         5
            man_4       1.00      1.00      1.00         6
            man_5       0.86      1.00      0.92         6
            man_7       1.00      1.00      1.00         5
            man_9       1.00      1.00      1.00         6
          woman_1       1.00      1.00      1.00         5
         woman_10       1.00      1.00      1.00         6
          woman_2       1.00      1.00      1.00         5
          woman_5       1.00      0.67      0.80         6
          woman_6       1.00      0.60      0.75         5
          woman_7       1.00      1.00      1.00         6
          woman_8       0.86      1.00      0.92         6
          woman_9       1.00      1.00      1.00         5
    
         accuracy                           0.95       107
        macro avg       0.96      0.95      0.95       107
     weighted avg       0.96      0.95      0.95       107
    
    辨识完随机选择测试图片验证
    lbp_2
  4. 我们也可以随便找一张照片来看看LBP结果图像化的样子:
    # 随机选取一张照片来看LBP的结果
    image_path = random.choice(list(paths.list_images(args["input"])))
    image = cv2.imread(image_path)
    rects = detect(image)
    
    (x, y, w, h) = rects[0]["box"]
    roi = image[y:y + h, x:x + w]
    
    gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
    lbp = feature.local_binary_pattern(gray, 8, 1, method="default")
    lbp = rescale_intensity(lbp, out_range=(0, 255))
    lbp = lbp.astype("uint8")
    
    img = np.hstack([roi, np.dstack([lbp] * 3)])
    cv2.imshow("img", img)
    cv2.waitKey(0)
    
    lbp_3

结论

  1. 局部二值方法相对昨天的特徵脸方法,辨识的准确度更高 (94% -> 95%)、对原始图片的亮度或平移旋转等变化比较不会受影响
  2. 与特徵脸方法一样,是经由取出图片中人脸的特徵来比对与辨识,因此原始图片通常都需要人脸是正面的
  3. OpenCV内建的FaceRecognizer在一些比较简单的情景中就已经可以提供很高的人脸辨识准确率了

实际应用中 (比如像Day14前言提到的那种情境),人脸辨识会有各种奇怪的状况发生:

  • 人脸影像过於模糊
  • 人脸太小
  • 有遮蔽物在脸上
  • 假脸 (如使用手机中的相片)
  • ...

因此为了要让辨识模型更加的完善,我们需要让模型"学习"如何去辨识人脸。

这将是我们明天谈论的内容,See you!


<<:  [Day-29] R语言 - 分群其他演算法 ( Clustering other Algorithms )

>>:  [区块链&DAPP介绍 Day21] contract 案例3 - 比大小下注游戏

< 关於 next.js: 开始打地基| Next中的Pages,究竟有什麽用途? >

09-15-2021 本章内容 pages意想不到的用途! 每个页面都是以pages作为基准路径 动...

设定字体颜色及文字大小、行距及间距

设定基本段落样式,字体大小、行距及行距设定方式以及嵌入google font方式 设定基本字体 f...

Day26:【技术篇】Webpack5 - Webpack之运作阶段

一、前言   因为网页应用程序不断扩大、开发模式慢慢地被模组化设计取代,近期诞生了 Webpack,...

Burp Suite 已经提供给你了最便利的 C2 Server

虽然是写C2 Server, 但实际上我们并不是真的要从这个Server发送指令出去, 我们只是要让...

Day29|常见的三种工作流程 - Git flow、GitHub Flow 与 Gitlab Flow

在制作专案时,大多都是与他人共同协作,当一起开发的人越来越多时,就更需要有一套规则或模式来进行合作,...