OpenCV三大传统人脸识别算法:EigenFace、FisherFace与LBPH实战

系列 :OpenCV 机器视觉实战系列
关键词:EigenFace、FisherFace、LBPH、PCA、LDA、人脸识别、OpenCV face 模块


一、为什么要学传统人脸识别?

深度学习大行其道的今天,OpenCV 内置的三种传统人脸识别算法依然有其不可替代的价值:

  • 零依赖 :仅需 opencv-contrib-python,无需 GPU、无需大模型
  • 极少样本 :每人 2~3 张图 即可训练,适合小场景快速部署
  • 实时识别:训练 + 识别延迟均在毫秒级
  • 原理透明:PCA / LDA / LBP 均有清晰数学基础,便于调参

本文将结合实战代码,深度解析三种算法,并与前序文章中的 Haar 级联分类器dlib HOG 检测器 进行全维度对比。


二、算法流程总览

三种算法共享相同的 API 框架:创建识别器 → 训练 → 预测,但内部特征提取逻辑截然不同。

算法 核心思路 是否需统一尺寸 置信度可靠区
EigenFace PCA 全局降维 (120×180) < 5000
FisherFace PCA + LDA 判别分析 (120×128) < 5000
LBPH 局部二值模式直方图 < 80

三、EigenFace:特征脸算法

3.1 核心原理

EigenFace 将人脸图像视为高维向量,通过 PCA(主成分分析) 找到数据方差最大的方向------即"特征脸"。识别时将待测图像投影到低维特征空间,再做最近邻匹配。

数学核心

复制代码
协方差矩阵 C = (1/N) * 求和[(xi - 均值)(xi - 均值)^T]
提取前 k 个特征向量 → 特征脸基底
投影系数 w = V^T * (x - 均值)
识别:argmin ||w_test - w_i||^2

3.2 关键代码解析

Step 1:准备训练数据(统一尺寸不可省略)

python 复制代码
import cv2
import numpy as np

images = []
# EigenFace 要求所有训练图像尺寸一致
a = cv2.imread('handou1.jpg', 0)
a = cv2.resize(a, (120, 180))   # 宽×高
b = cv2.imread('handou2.jpg', 0)
b = cv2.resize(b, (120, 180))
c = cv2.imread('lxl1.jpg', 0)
c = cv2.resize(c, (120, 180))
d = cv2.imread('lxl2.jpg', 0)
d = cv2.resize(d, (120, 180))

images = [a, b, c, d]
labels = [0, 0, 1, 1]  # 0=handou, 1=lixiaolong

注意cv2.resize(img, (width, height)),参数顺序是宽×高,与 NumPy shape 的行×列相反。

Step 2:创建识别器并训练

python 复制代码
# num_components: PCA 保留的主成分数,默认根据数据自动决定
# threshold: 置信度超过此值则判为"不认识",返回 label=-1
recognizer = cv2.face.EigenFaceRecognizer_create(threshold=7600)
recognizer.train(images, np.array(labels))

Step 3:预测并标注结果

python 复制代码
pre_image = cv2.imread('hd.jpg', 0)
pre_image = cv2.resize(pre_image, (120, 180))  # 测试图同样需要统一尺寸

label, confidence = recognizer.predict(pre_image)
dic = {0: 'handou', 1: 'lixiaolong', -1: 'Unknown'}
print(f'识别结果:{dic[label]},置信度:{confidence:.1f}')
# 置信度说明:值越小越匹配,< 5000 为可靠,> threshold 则返回 -1

# 在图像上标注
result = cv2.putText(cv2.imread('hd.jpg').copy(),
                     dic[label], (10, 30),
                     cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow('EigenFace 识别结果', result)
cv2.waitKey(0)

3.3 适用场景与局限

  • 适用:光照均匀、正脸、背景简单的受控环境
  • 局限:对光照变化、姿态变化极为敏感;训练图像必须统一尺寸

四、FisherFace:Fisher 判别脸

4.1 核心原理

FisherFace 在 EigenFace 的 PCA 降维基础上,额外引入 LDA(线性判别分析),最大化类间散度、最小化类内散度,使不同人的投影距离更大、同一人的更小。

复制代码
LDA 目标:最大化 J(W) = |W^T * S_b * W| / |W^T * S_w * W|
S_b = 类间散度矩阵(Between-class Scatter)
S_w = 类内散度矩阵(Within-class Scatter)

与 EigenFace 的本质区别 :EigenFace 保留数据整体方差最大的方向(可能混入类内变化);FisherFace 专门找区分不同人最有效的方向,对光照变化更鲁棒。

4.2 关键代码解析

封装图像读取(函数化)

python 复制代码
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont

def image_re(image_path):
    """读取灰度图并统一尺寸"""
    img = cv2.imread(image_path, 0)
    img = cv2.resize(img, (120, 128))  # FisherFace 使用 120×128
    images.append(img)

images = []
for path in ['handou1.jpg', 'handou2.jpg', 'lxl1.jpg', 'lxl2.jpg']:
    image_re(path)
labels = [0, 0, 1, 1]

创建识别器(FisherFace 至少需要 2 个类别)

python 复制代码
# 注意:FisherFace 的训练样本必须包含 >= 2 个不同标签
recognizer = cv2.face.FisherFaceRecognizer_create(threshold=5000)
recognizer.train(images, np.array(labels))

预测 + 中文标注

python 复制代码
pre_image = cv2.imread('hd.jpg', 0)
pre_image = cv2.resize(pre_image, (120, 128))
label, confidence = recognizer.predict(pre_image)

dic = {0: '憨豆', 1: '李小龙', -1: '无法识别'}
print(f'识别结果:{dic[label]},置信度:{confidence:.1f}')

def cv2_add_chinese_text(img, text, position, color=(0, 255, 0), size=30):
    """使用 PIL 在 OpenCV 图像上叠加中文"""
    if isinstance(img, np.ndarray):
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    draw = ImageDraw.Draw(img)
    font = ImageFont.truetype("simsun.ttc", size, encoding="utf-8")
    draw.text(position, text, color, font=font)
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGRA)

result = cv2_add_chinese_text(cv2.imread('hd.jpg').copy(),
                               dic[label], (30, 10), textColor=(0, 0, 255))
cv2.imshow('FisherFace 识别结果', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

中文标注技巧 :OpenCV 原生 putText 不支持中文,通过 PIL 绘制中文后转回 OpenCV 格式是最常见解法。


五、LBPH:局部二值模式直方图

5.1 核心原理

LBPH 放弃全局特征,转而提取局部纹理。核心思想:对每个像素,比较其 8 邻域与中心像素大小,得到一个 8 位二进制码(LBP 码),再统计整幅图中各 LBP 码出现的频次直方图作为人脸描述符。

LBP 编码步骤

复制代码
对每个中心像素 p(x,y):
  1. 读取 8 邻域像素值
  2. 与中心值比较:≥ center → 1,< center → 0
  3. 顺时针拼接 8 个 bit → 十进制 LBP 码(0~255)
将图像划分为 8×8 网格(Cell)
统计每个 Cell 的 256 维直方图
串联所有 Cell 直方图 → 最终描述符

5.2 关键代码解析

准备数据(无需统一尺寸,是 LBPH 最大优势)

python 复制代码
import cv2
import numpy as np

images = []
images.append(cv2.imread('handou1.jpg', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('handou2.jpg', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('lxl1.jpg',   cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('lxl2.jpg',   cv2.IMREAD_GRAYSCALE))
labels = [0, 0, 1, 1]
dic = {0: 'handou', 1: 'lxl', -1: '无法识别'}

创建 LBPH 识别器

python 复制代码
# radius:  LBP 采样圆半径(默认1)
# neighbors: 采样点数(默认8)
# grid_x/y: 水平/垂直 Cell 划分数(默认8)
# threshold: 置信度阈值,> 80 视为不认识
recognizer = cv2.face.LBPHFaceRecognizer_create(threshold=80)
recognizer.train(images, np.array(labels))

预测

python 复制代码
predict_image = cv2.imread('hd.jpg', cv2.IMREAD_GRAYSCALE)
label, confidence = recognizer.predict(predict_image)
print(f'识别结果:{dic[label]}')
print(f'置信度:{confidence:.2f}  (< 80 为可靠,越小越匹配)')

置信度注意 :LBPH 的置信度是直方图之间的卡方距离,量纲与 EigenFace/FisherFace 完全不同,不可相互比较。

5.3 优势总结

LBPH 在三种算法中综合表现最佳:

  • 无需统一图像尺寸,部署最灵活
  • 局部特征对光照、表情变化更鲁棒
  • 可增量训练recognizer.update(new_images, new_labels) 无需重训
  • 置信度直观:< 80 即可信,调参简单

六、五技术全维度对比(含 Haar / dlib)

前序博客介绍了 Haar 级联分类器(人脸检测 )和 dlib HOG 检测器(人脸检测 ),本文三种算法解决的是人脸识别("这是谁?")问题。两者在流程上串联使用:

复制代码
输入视频帧
    ↓
人脸检测(Haar / dlib HOG)→ 找到人脸位置
    ↓
人脸识别(EigenFace / FisherFace / LBPH)→ 判断是谁
    ↓
输出姓名 + 置信度

关键差异深度解析

检测 vs 识别

Haar 级联 dlib HOG EigenFace FisherFace LBPH
解决问题 人脸在哪? 人脸在哪? 这是谁? 这是谁? 这是谁?
输出 人脸矩形框 人脸矩形框 标签 + 置信度 标签 + 置信度 标签 + 置信度
是否需要标注数据 大量(已预训练) 大量(已预训练) 每人2张起 每人2张起 每人2张起

特征提取方式对比

复制代码
Haar:        矩形区域灰度均值差(全局,速度极快)
dlib HOG:    像素梯度方向统计(局部方向性,精度高)
EigenFace:   PCA全局线性降维(全局,对光照敏感)
FisherFace:  PCA+LDA判别降维(全局,类间分离好)
LBPH:        局部二值纹理统计(局部,光照鲁棒)

七、性能雷达图与置信度阈值

置信度三算法说明

算法 度量方式 可靠阈值 返回 -1 条件
EigenFace 欧氏距离(投影系数差) < 5000 > threshold (默认DBL_MAX)
FisherFace 欧氏距离(LDA投影差) < 5000 > threshold
LBPH 卡方距离(直方图差) < 80 > threshold

陷阱 :三种算法的置信度单位不同、量纲不同,不能直接横向比较大小。


八、实战建议与扩展方向

8.1 三算法选型一句话

复制代码
光照变化大  → FisherFace(LDA对光照最鲁棒)
实时/增量更新 → LBPH(速度快,支持 update)
理想受控环境 → EigenFace(计算简单,原理最直观)

8.2 与检测器组合的最优实践

python 复制代码
# 推荐组合:Haar粗筛(速度快)+ LBPH识别(鲁棒)
cap = cv2.VideoCapture(0)
face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
recognizer = cv2.face.LBPHFaceRecognizer_create(threshold=80)
recognizer.train(train_images, np.array(train_labels))

while True:
    ret, frame = cap.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # Step1: Haar快速定位人脸
    faces = face_detector.detectMultiScale(gray, 1.1, 5)
    for (x, y, w, h) in faces:
        face_roi = gray[y:y+h, x:x+w]
        # Step2: LBPH识别身份
        label, conf = recognizer.predict(face_roi)
        name = id_to_name.get(label, 'Unknown')
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
        cv2.putText(frame, f'{name} {conf:.0f}', (x, y-8),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
    cv2.imshow('人脸识别', frame)
    if cv2.waitKey(1) == 27:
        break

8.3 进阶扩展

方向 技术 说明
深度学习识别 ArcFace / FaceNet 大规模、高精度,需GPU
活体检测 眨眼检测(EAR) 结合 dlib 68关键点
年龄/性别估计 OpenCV DNN + Age-Net 与本文识别器串联
增量学习 LBPH.update() 动态注册新用户无需重训
人脸对齐 dlib 68关键点 提高识别精度5~15%

九、总结

EigenFace FisherFace LBPH
最大优势 原理简洁,PCA直观 类间区分最优,光照鲁棒 无需统一尺寸,支持增量
致命弱点 光照/姿态敏感 必须≥2类,训练样本要求高 整体结构信息丢失
推荐度 ★★★ ★★★★ ★★★★★

三种算法均适合小样本、无GPU、快速部署的场景。在实际工程中,推荐 Haar 检测 + LBPH 识别 作为轻量基线方案,对精度要求更高时升级为 dlib 检测 + FisherFace 识别,或直接引入深度学习方案。


参考资料

  • OpenCV 官方文档:cv2.face 模块
  • Turk & Pentland, "Eigenfaces for Recognition" (1991)
  • Belhumeur et al., "Eigenfaces vs. Fisherfaces" (1997)
  • Ahonen et al., "Face Description with Local Binary Patterns" (2006)
    |

三种算法均适合小样本、无GPU、快速部署的场景。在实际工程中,推荐 Haar 检测 + LBPH 识别 作为轻量基线方案,对精度要求更高时升级为 dlib 检测 + FisherFace 识别,或直接引入深度学习方案。


参考资料

  • OpenCV 官方文档:cv2.face 模块
  • Turk & Pentland, "Eigenfaces for Recognition" (1991)
  • Belhumeur et al., "Eigenfaces vs. Fisherfaces" (1997)
  • Ahonen et al., "Face Description with Local Binary Patterns" (2006)
相关推荐
AI先驱体验官2 小时前
BotCash:AI智能体变现从小 Demo 到商业产品的距离
大数据·人工智能·深度学习·重构·aigc
平安的平安2 小时前
MCP 协议实战:用 Python 开发你的第一个 AI 工具服务
网络·人工智能·python
akarinnnn2 小时前
【DAY16】字符函数和字符串函数
c语言·数据结构·算法
宸津-代码粉碎机2 小时前
Spring Boot 4.0 进阶实战+源码解析系列(持续更新)—— 从落地到源码,搞定面试与工作
java·人工智能·spring boot·后端·python·面试
Z.风止2 小时前
Large Model-learning(4)
人工智能·pytorch·笔记·python·深度学习·机器学习
Fleshy数模2 小时前
openCV实现实时颜色识别:从基础检测到指定颜色区域提取
人工智能·opencv·计算机视觉
海兰2 小时前
【第3篇】使用LangGraph构建工作流
人工智能·windows
_日拱一卒2 小时前
LeetCode:螺旋矩阵
算法·leetcode·矩阵
Jial-(^V^)2 小时前
使用强化学习微调大模型
人工智能·llm