一、人脸识别概述

实现人脸识别的主要流程如下图所示:

在这里插入图片描述

在 OpenCV 中,提供了三种经典的人脸识别特征提取算法:

  • LBPH(局部二值模式直方图)
  • EigenFaces(特征脸)
  • FisherFaces(费舍尔脸)

下面将逐一详细介绍这三种方法的原理与实现。

二、LBPH 算法

LBPH(Local Binary Patterns Histogram,局部二值模式直方图)算法基于 LBP(Local Binary Pattern,局部二值模式)算子。LBP 最早作为一种有效的纹理描述算子被提出,因其在表述图像局部纹理特征方面效果出众而得到广泛应用。

LBPH 实现步骤

  1. LBP 编码:以每个像素为中心,将其与周围像素的灰度值进行比较,根据大小关系生成二进制编码,得到整幅图像的 LBP 编码图。
  2. 分块直方图统计:将 LBP 编码图划分为多个区域,统计每个区域的 LBP 直方图,拼接得到整幅图像的完整直方图特征。

通过比较不同人脸图像的 LBP 直方图,即可实现人脸识别。该方法的优点是对光照、缩放、旋转和平移具有较好的鲁棒性。

步骤1:LBP 编码

圆形邻域

为了获得不同尺度下的纹理结构,可以使用圆形邻域,将计算扩展到任意大小的邻域范围。圆形邻域用 (P, R) 表示,其中:

  • P 表示邻域内参与运算的像素点个数
  • R 表示圆形邻域的半径

在这里插入图片描述

旋转不变性

为了使 LBP 特征具有旋转不变性,通常采用以下策略:

  1. 以中心像素为圆心,将周围像素点按照顺时针方向依次移动一个位置
  2. 重新计算所有旋转后的 LBP 值,取其中的最小值作为该点的最终 LBP 值

在这里插入图片描述

步骤2:分块直方图

将 LBP 编码图划分为多个区域(如 8×8 网格),统计每个区域的直方图,然后拼接成完整的特征向量。LBP 特征与 Haar 特征类似,都是描述图像的灰度变化特征。

在这里插入图片描述

代码实现

import cv2
import numpy as np

# 加载训练图像(灰度图)
images = []
images.append(cv2.imread('zms1.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('zms2.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('zms3.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('nm1.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('nm2.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('nm3.png', cv2.IMREAD_GRAYSCALE))

labels = [0, 0, 0, 1, 1, 1]
label_map = {0: '詹姆斯', 1: '浓眉', -1: '无法识别'}

# 待识别图像
predict_image = cv2.imread('zms4.png', cv2.IMREAD_GRAYSCALE)

# 创建 LBPH 识别器(阈值 80)
recognizer = cv2.face.LBPHFaceRecognizer_create(threshold=80)

# 训练
recognizer.train(images, np.array(labels))

# 预测
label, confidence = recognizer.predict(predict_image)
print('识别结果:', label_map[label])
print('置信度:', confidence)

三、EigenFaces 算法

EigenFaces 是基于 PCA(主成分分析,Principal Component Analysis) 算法实现的人脸识别方法。

PCA 主成分分析
PCA 是一种矩阵降维压缩算法,在减少矩阵维数的同时尽可能保留原始矩阵的主要信息。简单来说,就是将 n×m 的矩阵转换为 n×k 的矩阵(k < m),仅保留最主要特征,从而节省存储空间和计算量。

在这里插入图片描述

代码实现

基础版本

import cv2
import numpy as np

# 加载训练图像(统一尺寸 120×180)
images = []
a = cv2.imread('zms4.png', 0)
a = cv2.resize(a, (120, 180))
b = cv2.imread('zms3.png', 0)
b = cv2.resize(b, (120, 180))
c = cv2.imread('nm1.png', 0)
c = cv2.resize(c, (120, 180))
d = cv2.imread('nm2.png', 0)
d = cv2.resize(d, (120, 180))
images.extend([a, b, c, d])

labels = [0, 0, 1, 1]

# 待识别图像
pre_image = cv2.imread('zms1.png', 0)
pre_image = cv2.resize(pre_image, (120, 180))

# 创建 EigenFaces 识别器(阈值 5000)
recognizer = cv2.face.EigenFaceRecognizer_create(threshold=5000)
recognizer.train(images, np.array(labels))

label, confidence = recognizer.predict(pre_image)
label_map = {0: '詹姆斯', 1: '浓眉'}
print('识别结果:', label_map[label])
print('置信度:', confidence)

# 显示结果(英文)
result_img = cv2.imread('zms1.png').copy()
cv2.putText(result_img, label_map[label], (10, 30),
            cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow('识别结果', result_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

支持中文显示的增强版本
OpenCV 的 putText 不支持中文,可通过 PIL 库实现中文绘制:

import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont

# 加载训练图像(统一尺寸)
images = []
a = cv2.imread('zms4.png', 0)
a = cv2.resize(a, (120, 180))
b = cv2.imread('zms3.png', 0)
b = cv2.resize(b, (120, 180))
c = cv2.imread('nm1.png', 0)
c = cv2.resize(c, (120, 180))
d = cv2.imread('nm2.png', 0)
d = cv2.resize(d, (120, 180))
images.extend([a, b, c, d])

labels = [0, 0, 1, 1]

pre_image = cv2.imread('zms1.png', 0)
pre_image = cv2.resize(pre_image, (120, 180))

recognizer = cv2.face.EigenFaceRecognizer_create(threshold=5000)
recognizer.train(images, np.array(labels))

label, confidence = recognizer.predict(pre_image)
label_map = {0: '詹姆斯', 1: '浓眉'}
print('识别结果:', label_map[label])
print('置信度:', confidence)

# 使用 PIL 绘制中文
img = cv2.imread('zms1.png')
pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
font = ImageFont.truetype("C:/Windows/Fonts/simhei.ttf", 24)  # 根据系统调整路径
draw.text((10, 30), label_map[label], font=font, fill=(255, 0, 0))

result = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
cv2.imshow('识别结果', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

四、FisherFaces 算法

PCA 方法是 EigenFaces 的核心,但其存在明显缺点:降维过程中可能会损失对分类至关重要的判别信息,导致预测错误。

FisherFaces 采用 LDA(Linear Discriminant Analysis,线性判别分析) 进行人脸识别,其核心思想是:

同类样本投影后尽可能聚集(类内距离小)

异类样本投影后尽可能分散(类间距离大)

在这里插入图片描述

代码实现

import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont

def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):
    """在 OpenCV 图像上添加中文"""
    if isinstance(img, np.ndarray):
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    draw = ImageDraw.Draw(img)
    fontStyle = ImageFont.truetype("simsun.ttc", textSize, encoding="utf-8")
    draw.text(position, text, textColor, font=fontStyle)
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

def image_re(image):
    img = cv2.imread(image, flags=0)
    img = cv2.resize(img, dsize=(120, 180))
    return img

# 加载训练图像
images = []
images.append(image_re('zms1.png'))
images.append(image_re('zms2.png'))
images.append(image_re('nm1.png'))
images.append(image_re('nm2.png'))

labels = [0, 0, 1, 1]

# 待识别图像
pre_image = cv2.imread('zms3.png', flags=0)
pre_image = cv2.resize(pre_image, dsize=(120, 180))

# 创建 FisherFaces 识别器(阈值 5000)
recognizer = cv2.face.FisherFaceRecognizer_create(threshold=5000)
recognizer.train(images, np.array(labels))

label, confidence = recognizer.predict(pre_image)
label_map = {0: '詹姆斯', 1: '浓眉', -1: '无法识别'}
print('识别结果:', label_map[label])
print('置信度:', confidence)

# 显示中文结果
img = cv2.imread('zms3.png')
result = cv2AddChineseText(img.copy(), label_map[label], position=(30, 10), textColor=(255, 0, 0))
cv2.imshow('识别结果', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

更多推荐