在计算机视觉领域,人脸识别是一项基础且应用广泛的技术。尽管深度学习模型如FaceNet、ArcFace等已占据主流,但以OpenCV内置的LBPH、EigenFace和FisherFace为代表的经典算法,因其原理清晰、计算高效、无需海量数据等优点,在资源受限场景、教学演示及特定工业应用中依然不可或缺。本文将从原理、OpenCV实现、代码详解到实战对比,为你系统梳理这三大经典人脸识别算法的核心知识。

一、LBPH:基于局部纹理的稳健识别器

LBPH(局部二值模式直方图)算法的核心在于利用图像的局部纹理特征。它首先通过LBP算子将图像纹理编码,再通过空间直方图统计形成最终的特征描述符,对光照变化具有较好的鲁棒性。

1.1 原理核心:从像素到直方图

LBP算子的基本思想非常简单:对于图像中的每个像素,以其灰度值为阈值,与周围邻域像素(例如3x3的8个邻居)进行比较。若邻域像素值大于等于中心像素,则标记为1,否则为0。这一圈二进制数就构成了该中心点的LBP编码值。公式化表示为:

$LBP(x_c, y_c) = \sum_{p=0}^{P-1} s(g_p - g_c) \times 2^p$, 其中 $s(x) = \begin{cases} 1 & \text{if } x \geq 0 \\ 0 & \text{else} \end{cases}$

得到整幅图像的LBP编码图后,LBPH算法将其划分为多个网格(如7x7),并计算每个网格内LBP值的直方图。最后,将所有网格的直方图连接起来,就构成了该人脸图像的高维特征向量。识别时,通过比较测试图像与训练库中图像特征向量的直方图距离(如卡方距离)来进行判断。

1.2 OpenCV实现与关键函数

OpenCV通过 cv2.face.LBPHFaceRecognizer_create() 提供了LBPH模型的完整实现。其完整的人脸识别流程代码如下:

原始图像 → 提取LBP特征 → 分块直方图 → 连接直方图 → 形成特征向量 → 分类识别

下面详细解析代码中使用的几个核心函数:

  • 创建识别器:recognizer = cv2.face.LBPHFaceRecognizer_create() 是构建模型的起点。其参数配置灵活: cv2.face.LBPHFaceRecognizer_create() 其中,radius控制采样半径,neighbors是采样点数量(常用8),grid_xgrid_y决定划分网格数,threshold是判断“未知人脸”的置信度阈值。参数详解如下:
    recognizer = cv2.face.LBPHFaceRecognizer_create(
        radius=1,          # LBP半径,默认1
        neighbors=8,       # 采样点数量,默认8
        grid_x=8,          # 水平方向网格数
        grid_y=8,          # 垂直方向网格数
        threshold=80       # 识别阈值
    )
  • 训练模型: 调用 recognizer.train(images, labels) 完成训练。 recognizer.train(images, labels) 此函数接收灰度图像列表和对应的标签数组。其内部会为每张图像提取LBP特征,划分网格并计算局部直方图,最终拼接成特征向量并存储映射关系。过程说明:
    recognizer.train(images, np.array(labels))
  • 预测识别: 使用 label, confidence = recognizer.predict(face_roi) 进行预测。 recognizer.predict(src) 该函数返回预测的标签和置信度。需要注意的是,LBPH的置信度是距离值,越低表示匹配度越高。通常经验范围是:
    label, confidence = recognizer.predict(predict_image)

1.3 算法特点与实战应用

LBPH算法因其独特的纹理分析方式,拥有鲜明的优缺点:

✅ 优点:

  • 光照不变性: 对均匀光照变化不敏感,适合光照条件不稳定的环境。
  • 计算高效: 特征提取简单,实时性好,非常适合嵌入式设备(如树莓派)或移动端应用。
  • 内存友好: 特征向量维度相对较低,模型体积小。

⚠️ 缺点:

  • 对姿态敏感: 人脸旋转或侧脸角度过大会导致识别率显著下降。
  • 需要预处理: 强烈依赖人脸检测和对齐,需先裁剪出正面、大小归一化的人脸区域。
  • 抗遮挡能力弱: 眼镜、口罩等遮挡物会严重影响局部纹理特征。

一个完整的LBPH人脸识别实战应用代码示例如下,涵盖了从数据准备到识别输出的全过程:

import cv2
import numpy as np
# 提前训练的人脸照片
images = []
images.append(cv2.imread('qingzi/qz1.jpg', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('qingzi/qz2.jpg', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('qingzi/qz3.jpg', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('qingzi/qz4.jpg', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('susu/ss1.jpg', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('susu/ss2.jpg', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('susu/ss3.jpg', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('susu/ss4.jpg', cv2.IMREAD_GRAYSCALE))
labels = [0, 0, 0, 0, 1, 1, 1, 1]
dic = {0: 'qz', 1: 'ss', -1: '无法识别'}
predict_image = cv2.imread('qingzi/qz5.jpg', cv2.IMREAD_GRAYSCALE)  # 待识别人脸
# predict_image = cv2.imread('susu/ss5.jpg', cv2.IMREAD_GRAYSCALE)  # 待识别人脸
# 创建一个LBPH的人脸特征识别器
# 函数说明:
# radius: 可选参数,圆度与半径的平方根,建议使用默认值
# neighbors: 可选参数,圆度与半径的平方根,建议使用默认值
# grid_x: 可选参数,水平方向上的像素数,默认值为8,即将LBP特征图在水平方向上划分为8个单元
# grid_y: 可选参数,垂直方向上的像素数,默认值为8,建议使用默认值
# threshold: 可选参数,人脸识别时使用的阈值,建议使用默认值
recognizer = cv2.face.LBPHFaceRecognizer_create(threshold=80)  # 修正:facce应为face
# 函数train用给定的数据和相关标签训练生成的实例模型
# 语法格式:None = 识别对象.train(src, labels)
# 参数说明:
# src: 训练图像,用来学习的人脸图像
# labels: 标签,人脸图像对应的标签
recognizer.train(images, np.array(labels))
# 函数predict()对一个待识别的人脸图像进行判断,寻找与当前图像距离最近的人脸图像
# 参数与返回值的含义如下:
# src: 需要识别的原始图像
# label: 返回的识别结果标签,返回-1表示无法识别当前人脸
# confidence: 返回的置信度评分,用来衡量识别结果与原有模型之间的差异
# 评价标准是不同距离,但若是高于80,则认为识别结果与原有模型差距大
label, confidence = recognizer.predict(predict_image)
print('这人是:', dic[label])  # 修正:应为dic[label]而不是字符串
print('置信度:', confidence)  # 修正:应为confidence变量而不是字符串

运行效果可参考下图,展示了LBPH特征提取的可视化结果:

[AFFILIATE_SLOT_1]

二、EigenFace:基于PCA降维的“特征脸”方法

EigenFace(特征脸)是历史上第一个有效的人脸识别算法,其核心思想是主成分分析(PCA)。它将所有人脸图像视为一个高维空间中的点,通过PCA找到最能表征人脸差异的“主方向”(即特征向量),这些主方向构成的子空间就是“特征脸”空间。

2.1 数学原理与“平均脸”

EigenFace的训练过程本质是求解协方差矩阵的特征值和特征向量。首先,将所有m张训练人脸图像(假设为N维列向量)组合成一个大矩阵。然后计算所有图像的平均脸,并将每张图像减去这个平均脸,得到零均值的人脸数据。接着计算其协方差矩阵,并求解该矩阵的特征值和特征向量。特征值最大的前k个特征向量(即“特征脸”)就张成了我们用于识别的低维子空间。数学过程概括如下:

1. 数据准备:将所有训练人脸图像拉直为列向量
   X = [x₁, x₂, ..., xₙ] ∈ ℝ^(m×n)

2. 计算平均脸:
   ψ = (1/n) Σ xᵢ

3. 计算差值矩阵:
   A = [x₁-ψ, x₂-ψ, ..., xₙ-ψ]

4. 计算协方差矩阵:
   C = A·Aᵀ ∈ ℝ^(m×m)  # 维度极大,直接计算困难

5. 使用SVD求解特征向量(特征脸):
   Aᵀ·A·vᵢ = λᵢ·vᵢ  # 先计算小矩阵的特征向量
   uᵢ = A·vᵢ  # 再得到特征脸

6. 选取前k个最大特征值对应的特征脸构成投影矩阵

我们可以将计算得到的“特征脸”可视化,它们看起来像是一些模糊的、具有不同轮廓和明暗模式的人脸,代表了原始人脸数据中最主要的变化模式:

import matplotlib.pyplot as plt
# 获取特征脸
mean_face = recognizer.getMean()  # 平均脸
eigenfaces = recognizer.getEigenVectors()  # 特征脸
# 显示平均脸
plt.subplot(1, 5, 1)
plt.title("Mean Face")
plt.imshow(mean_face.reshape(140, 120), cmap='gray')
# 显示前4个特征脸
for i in range(4):
    plt.subplot(1, 5, i+2)
    plt.title(f"Eigenface {i+1}")
    plt.imshow(eigenfaces[:, i].reshape(140, 120), cmap='gray')
plt.show()

2.2 OpenCV中的EigenFaceRecognizer

OpenCV中对应的类是 cv2.face.EigenFaceRecognizer_create()

  • 创建与参数:cv2.face.EigenFaceRecognizer_create() 关键参数是num_components,即保留的主成分数量。通常建议保留总方差的85%-95%对应的特征向量,数量在80-150之间。参数详细说明:
    recognizer = cv2.face.EigenFaceRecognizer_create(
        num_components=0,    # PCA保留的主成分数量
        threshold=7000       # 识别阈值
    )
  • 训练过程: 调用 recognizer.train(images, labels)recognizer.train(images, labels) 其内部完成了将图像向量化、计算平均脸、PCA降维等一系列操作:
    recognizer.train(images, np.array(labels))
  • 识别预测: 使用 label, confidence = recognizer.predict(test_face)recognizer.predict(pre_image) 识别时,先将测试图像投影到特征脸子空间,然后计算与所有训练样本投影系数的欧氏距离,返回最近邻的标签和距离值。距离即为置信度,值越小越相似:
    label, confidence = recognizer.predict(pre_image)

2.3 特点分析与适用场景

✅ 优点:

  • 强大的降维能力: 能将数万维的像素数据压缩到几十维,极大提升了计算和存储效率。
  • 原理清晰: 基于经典的PCA,数学基础扎实,易于理解和教学。
  • 部分消除共性光照: 减去“平均脸”的操作可以消除数据集中共有的光照模式。

⚠️ 缺点:

  • 对光照和姿态敏感: 要求训练和测试图像在光照、角度上高度一致。
  • 需要严格对齐: 眼睛、鼻子等关键点必须对齐,否则识别率骤降。
  • 受背景和表情影响大: 非人脸区域(背景)或夸张表情会被当作主要特征,干扰识别。

尽管在复杂环境下性能有限,但EigenFace在可控环境(如门禁系统)和算法教学中仍有重要价值。其实战应用代码如下:

import cv2
import numpy as np
images = []  # 读取训练图像,注意:图片大小需要一致
# 读取并调整图像大小
a = cv2.imread('../qingzi/qz1.jpg', 0)  # flags=0 表示以灰度模式读取
a = cv2.resize(a, (120, 140))
b  = cv2.imread('../qingzi/qz2.jpg', 0)
b = cv2.resize(b, (120, 140))
c  = cv2.imread('../qingzi/qz3.jpg', 0)
c = cv2.resize(c, (120, 140))
d = cv2.imread('../susu/ss1.jpg', 0)
d = cv2.resize(d, (120, 140))
e = cv2.imread('../susu/ss2.jpg', 0)
e = cv2.resize(e, (120, 140))
f = cv2.imread('../susu/ss3.jpg', 0)
f = cv2.resize(f, (120, 140))
images.append(a)
images.append(b)
images.append(c)
images.append(d)
images.append(e)
images.append(f)
labels = [0, 0, 0, 1, 1, 1]
# 读取并调整待预测图像大小
# pre_image = cv2.imread('susu/ss4.jpg', 0)
pre_image = cv2.imread('../qingzi/qz4.jpg', 0)
pre_image = cv2.resize(pre_image, (120, 140))
# 创建Eigenfaces人脸识别器
# 作用: 创建一个EigenFace的人脸特征识别器
# num_components: 在PCA中要保留的分量个数。该参数值通常要根据输入数据来具体确定
# 一般程序中,取80即可(降维成多少个特征)
# threshold: 进行人脸识别所采用的阈值
recognizer = cv2.face.EigenFaceRecognizer_create(threshold=7000)  # threshold=5000或4000
# 使用训练数据(images和labels)来训练识别器
# 函数FaceRecognizer.train()用给定的数据和相关标签训练生成的实例模型
# 参数的含义如下:
# src: 训练图像,用来学习的人脸图像
# labels: 标签,人脸图像对应的标签
recognizer.train(images, np.array(labels))
# 对预测图像(pre_image)进行人脸识别预测
# confidence: 大小介于0到20000,只要低于5000都被认为是可靠的结果
label, confidence = recognizer.predict(pre_image)
dic = {0: 'qz', 1: 'susu', -1: '无法识别'}
print('这是:', dic[label])
print('置信度为:', confidence)
# 在原图上标注识别结果并显示
# original_img = cv2.imread('susu/ss4.jpg').copy()
original_img = cv2.imread('../qingzi/qz4.jpg').copy()
original_img=cv2.resize(original_img,dsize=None,fx=0.5,fy=0.5)
# 在图像上添加文本
result_text = dic[label]
cv2.putText(original_img, result_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow('susu', original_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

特征脸的可视化效果如下图所示,可以看到从主要轮廓到细节的层次化特征:

三、FisherFace:追求最大类间区分度的LDA方法

FisherFace可以看作是EigenFace的“有监督”升级版。它基于线性判别分析(LDA),其目标不再是像PCA那样最大化总体方差,而是最大化不同类别(不同人)之间的差异,同时最小化同一类别(同一个人不同照片)内部的差异。这使得FisherFace在理论上具有更强的判别能力。

3.1 核心思想:从描述数据到区分数据

PCA是无监督的,它只关心数据本身的分布,不管数据标签。而LDA是有监督的,它利用标签信息,寻找一个投影方向,使得投影后同类样本尽可能聚集,异类样本尽可能远离。其优化目标是最大化Fisher判别准则:$J(w) = \frac{w^T S_b w}{w^T S_w w}$,其中 $S_b$ 是类间散度矩阵,$S_w$ 是类内散度矩阵。求解该广义特征值问题,即可得到最优的投影向量(即FisherFace)。数学原理概述如下:

目标:找到最佳投影方向w,使得投影后的数据:
1. 类间散度(between-class scatter)最大
2. 类内散度(within-class scatter)最小

定义:
- 类内散度矩阵:Sw = Σ Σ (x - μᵢ)(x - μᵢ)ᵀ
- 类间散度矩阵:Sb = Σ nᵢ(μᵢ - μ)(μᵢ - μ)ᵀ
- 总体散度矩阵:St = Sw + Sb

优化目标:J(w) = (wᵀSb w) / (wᵀSw w) → 最大化

求解:通过广义特征值分解 Sb·w = λ·Sw·w
取前(C-1)个最大特征值对应的特征向量(C为类别数)

FisherFace与EigenFace的核心区别对比如下:

特征EigenFace (PCA)FisherFace (LDA)
监督性无监督有监督
目标最大方差方向最大类间区分度
特征脸数量可任意选择最多C-1个(C为类别数)
适用场景数据降维、可视化分类任务
小样本问题相对较好容易过拟合

3.2 OpenCV实现详解

OpenCV中对应的类是 cv2.face.FisherFaceRecognizer_create()

  • 创建与参数:cv2.face.FisherFaceRecognizer_create() 关键参数num_components有一个重要限制:由于LDA的性质,最多只能得到 C-1 个有效特征向量(C为类别数,即人数)。参数说明:
    recognizer = cv2.face.FisherFaceRecognizer_create(
        num_components=0,    # LDA保留的特征数量
        threshold=2600       # 识别阈值
    )
  • 训练过程: 调用 recognizer.train(images, labels)recognizer.train(images, labels) 其内部会计算每个类的均值、总体均值,并构建类内和类间散度矩阵,最后求解投影矩阵:
    recognizer.train(images, np.array(labels))
  • 识别预测: 使用 label, confidence = recognizer.predict(test_face)recognizer.predict(pre_image) 识别过程与EigenFace类似,但距离计算是在更具判别性的Fisher子空间中进行的:
    label, confidence = recognizer.predict(pre_image)

3.3 优势、局限与实战

FisherFace在类别区分上通常优于EigenFace,但它也有自己的“阿喀琉斯之踵”——小样本问题(Small Sample Size Problem, SSS)。当训练样本总数小于图像维度时,类内散度矩阵 $S_w$ 是奇异的,无法直接求逆。常见的解决方案是采用“PCA+LDA”的两步法:先用PCA将维度降至样本数以下,再用LDA进行判别分析。

其实战应用代码如下:

import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
# 1个用法
def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):
    """向图片中添加中文"""
    if isinstance(img, np.ndarray):  # 判断是否OpenCV图片类型
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))  # 实现array到image的转换
    draw = ImageDraw.Draw(img)  # 在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)  # 转换回OpenCV格式
# 4个用法
def image_re(image):
    a = cv2.imread(image, 0)
    a = cv2.resize(a, (120, 180))
    images.append(a)
images = []
image_re('../qingzi/qz1.jpg')
image_re('../qingzi/qz2.jpg')
image_re('../qingzi/qz3.jpg')
image_re('../susu/ss1.jpg')
image_re('../susu/ss2.jpg')
image_re('../susu/ss3.jpg')
Labels = [0, 0, 0, 1, 1, 1]
pre_image = cv2.imread('../susu/ss4.jpg', 0)  # 读取待识别图像
# pre_image = cv2.imread('qingzi/qz5.jpg', 0)  # 读取待识别图像
pre_image = cv2.resize(pre_image, (120, 180))
# 创建一个FisherFace的人脸特征识别器
recognizer = cv2.face.FisherFaceRecognizer_create(threshold=2600)
# 训练模型
recognizer.train(images, np.array(Labels))
# 预测
label, confidence = recognizer.predict(pre_image)
dic = {0: '晴子', 1: '苏苏', -1: '无法识别'}
print('这人是:', dic[label])
print('置信度为:', confidence)
# 在图片上添加中文标签
image = cv2AddChineseText(cv2.imread('../susu/ss4.jpg').copy(), dic[label], (30, 10), textColor=(255, 0, 0))
# image = cv2AddChineseText(cv2.imread('qingzi/qz5.jpg').copy(), dic[label], (30, 10), textColor=(255, 0, 0))
image=cv2.resize(image,dsize=None,fx=0.6,fy=0.6)
cv2.imshow('susu', image)
# cv2.imshow('qingzi', image)
cv2.waitKey(0)

FisherFace的可视化效果如下图,其特征脸更侧重于区分不同个体的判别性区域:

[AFFILIATE_SLOT_2]

四、综合对比与选型指南

了解三种算法的原理和实现后,如何在项目中做出正确选择?关键在于明确你的应用场景、数据条件和性能要求。

4.1 横向对比一览

下表从多个维度对LBPH、EigenFace和FisherFace进行了全面对比:

算法原理特征数监督性优点缺点
EigenFacePCA任意无监督计算简单,降维效果好判别性不强
FisherFaceLDA≤C-1有监督判别性强,分类效果好小样本问题
LBPH局部二值模式固定有监督光照鲁棒性好对姿态敏感

4.2 选型策略与最佳实践

基于以上对比,我们可以得出清晰的选型建议:

  • 选择LBPH,如果: 你的应用场景光照不稳定(如户外)、计算资源有限(嵌入式设备)、或者追求简单的实现和快速的推理速度。它就像一位适应力强的“野战兵”。
  • 选择EigenFace,如果: 你的数据量不大,需要一种原理简单、易于实现的基线方法;或者人脸姿态、光照条件高度可控。它是经典的“学院派”代表。
  • 选择FisherFace,如果: 你的目标是获得最强的判别能力,且每个类别(每个人)都有相对充足(>10张)且质量较好的样本。它是在规则内追求极致的“竞赛选手”。

高级技巧与注意事项:

  • 预处理至关重要: 无论哪种算法,高质量的人脸对齐、灰度化、直方图均衡化都能极大提升识别率。可以考虑使用Dlib或MTCNN进行更精准的人脸关键点检测和对齐。
  • 解决FisherFace的小样本问题: 当每人样本很少时,可手动实现“PCA+LDA” pipeline,或直接使用OpenCV的 cv2.face 模块,它内部已做优化。代码示例如下:
    # 两步法:先PCA降维,再LDA
    # 1. 使用PCA降维到适当维度
    # 2. 再使用FisherFace进行分类
  • 处理类别不平衡: 如果某些人的图片远多于其他人,会影响LDA的效果。可以考虑对每个类别的样本进行重采样或加权处理。思路如下:
    # 对少数类样本进行数据增强
    # 或使用加权LDA
  • 集成学习提升鲁棒性: 在实际复杂应用中,可以同时训练多个模型(如LBPH+EigenFace),通过投票或加权平均的方式做出最终决策,往往能获得比单一模型更稳定、更准确的结果。

总结

LBPH、EigenFace和FisherFace作为OpenCV中内置的三大经典人脸识别算法,代表了深度学习时代之前的主流技术路径。LBPH以其对光照的鲁棒性和计算效率见长;EigenFace通过PCA降维抓住了人脸的主要变化模式;FisherFace则利用LDA实现了更强的类别判别能力。尽管它们在某些复杂场景下可能不敌现代的深度神经网络,但其轻量、高效、无需大规模训练数据、原理透明的特点,使得它们在嵌入式开发、教育科研、特定工业检测以及作为复杂系统的预处理或后备模块中,依然拥有不可替代的价值。理解这些经典算法,不仅是掌握计算机视觉的基础,更能为我们设计和优化现代AI系统提供宝贵的思路和工具。