OpenCV 之人脸识别:EigenFace 特征脸算法(PCA)详解
摘要 :本文详解 OpenCV 中第二种经典人脸识别算法------EigenFace(特征脸)。它基于 PCA 主成分分析,将整张人脸图像视为高维向量,通过降维提取最能区分人脸的主方向,在低维空间完成识别。文章从「图像即高维向量」讲起,逐步拆解 PCA 计算步骤与特征脸含义,给出完整可运行的 Python 代码,并详解
EigenFaceRecognizer_create、train、predict等 API 及置信度解读,最后对比 EigenFace 与 LBPH 的优劣,并总结提高准确率的实用方法。
前言
上一篇我们学习了 LBPH 人脸识别,它通过提取局部纹理特征来描述人脸。今天我们来认识第二种经典算法------EigenFace(特征脸)。
EigenFace 是 1987 年由 Sirovich 和 Kirby 提出、1991 年由 Turk 和 Pentland 完善的人脸识别方法,是最早将 PCA(主成分分析)应用到人脸识别的算法之一,也是人脸识别历史上里程碑式的工作。
和 LBPH 的「局部纹理」思路不同,EigenFace 走的是「全局降维」路线------把整张人脸图像看成一个高维向量,用 PCA 找到最能区分人脸的主要方向,在低维空间中进行识别。
这个思路听起来抽象,但理解之后你会发现它非常优雅。让我们一步步来拆解。
一、从高维图像到低维特征
1.1 图像就是高维向量
假设我们有一张 100×100 像素的灰度人脸图。如果把它的每一行像素首尾相连,就能拉成一个长度为 10000 的向量:
图像(100×100) → 向量(10000维)
这意味着什么?意味着每张人脸都是 10000 维空间中的一个点。
但问题是:10000 维太高了。直接在这么高的维度上计算距离、做分类,不仅计算量大,而且容易出现「维度灾难」------高维空间中所有点的距离都差不多,区分度很低。

1.2 降维的思路
那能不能用更少的维度来表示一张人脸?
答案是肯定的。因为所有人脸图像都有一些共同的结构:
- 眼睛在上面,鼻子在中间,嘴巴在下面
- 都是左右大致对称的
- 都有类似的轮廓
这说明,人脸图像虽然表面上是 10000 维,但真正重要的「变化维度」可能只有几十个甚至更少。
PCA(主成分分析) 就是用来找到这些最重要维度的算法。它的目标是:
找到一组正交的基向量(主成分),使得数据在这些基向量上的投影方差最大。换句话说,用最少的维度保留最多的信息。
这些基向量,在人脸识别中就叫做 EigenFace(特征脸)。
二、特征脸是什么样的
你可能很好奇,「特征脸」到底长什么样?

简单来说,每一个特征脸就是一张图像,它看起来像一张模糊的、平均化的人脸。前几个特征脸捕捉的是最主要的人脸变化:
- 第 1 个特征脸:通常对应光照方向的整体变化
- 第 2 个特征脸:可能对应人脸的左右明暗差异
- 第 3、4 个特征脸:可能对应五官的形状变化
- ......
越往后的特征脸,捕捉的细节越细微,也越接近噪声。
当我们取前 K 个特征脸时,就相当于用 K 个「基础人脸模板」的线性组合来表示任意一张人脸:
人脸 ≈ 平均脸 + a1×特征脸1 + a2×特征脸2 + ... + aK×特征脸K
其中的系数 (a1, a2, ..., aK) 就是这张人脸在低维空间中的特征向量,也叫「投影坐标」。
识别的时候,我们不用比较整张 10000 维的图像,只需要比较这 K 维的特征向量就够了。
三、PCA 计算步骤
下面简要回顾一下 PCA 的计算过程(了解即可,OpenCV 会帮我们完成):
- 组成数据矩阵:把 N 张训练人脸图像都拉成行向量,组成一个 N×D 的矩阵(D 是像素数)
- 减去均值:计算所有图像的平均脸,每张图像减去平均脸,得到去中心化的数据
- 计算协方差矩阵:衡量各维度之间的相关性
- 求协方差矩阵的特征值和特征向量:特征值越大,说明对应方向的信息量越大
- 取前 K 个最大特征值对应的特征向量:这些就是特征脸(EigenFace)
K 的选择很关键:
- K 太小:丢失太多信息,识别准确率下降
- K 太大:保留了噪声,计算量大,也可能过拟合
实际应用中,K 通常取 20~100,取前 80 个主成分是比较常见的选择。
四、EigenFace 识别流程
训练阶段
- 收集训练人脸图像,统一尺寸
- 计算平均脸
- 对训练集做 PCA,得到特征脸(前 K 个主成分)
- 将每张训练人脸投影到 K 维空间,得到各自的特征向量
- 存储特征向量和对应的身份标签
识别阶段
- 将待识别的人脸图像统一到相同尺寸
- 减去平均脸,投影到 K 维空间,得到特征向量
- 和训练库中所有特征向量计算距离(通常是欧氏距离)
- 找到最近邻,判断是否属于同一个人
- 如果最近距离超过阈值,返回「无法识别」
五、完整代码实现
python
import cv2
import numpy as np
# ========== 1. 准备训练数据 ==========
# 注意:EigenFace 要求所有图像尺寸必须一致!
images = []
# 读取图像并统一缩放为 120×180
a = cv2.imread('wyz1.png', 0) # 0 表示以灰度模式读取
a = cv2.resize(a, (120, 180))
b = cv2.imread('wyz2.png', 0)
b = cv2.resize(b, (120, 180))
c = cv2.imread('yy1.png', 0)
c = cv2.resize(c, (120, 180))
d = cv2.imread('yy2.png', 0)
d = cv2.resize(d, (120, 180))
images.append(a)
images.append(b)
images.append(c)
images.append(d)
# 标签:0=wyz, 1=yy
labels = [0, 0, 1, 1]
# ========== 2. 准备待识别图像 ==========
pre_image = cv2.imread('wyz.png', 0)
pre_image = cv2.resize(pre_image, (120, 180)) # 必须和训练图尺寸一致!
# ========== 3. 创建 EigenFace 识别器 ==========
# cv2.face.EigenFaceRecognizer_create(num_components, threshold)
# 参数说明:
# num_components - 保留的主成分个数(即特征脸数量),默认取全部
# threshold - 识别阈值,距离超过阈值返回 -1
recognizer = cv2.face.EigenFaceRecognizer_create(threshold=5000)
# ========== 4. 训练识别器 ==========
recognizer.train(images, np.array(labels))
# ========== 5. 预测识别 ==========
# label - 预测标签,-1 表示无法识别
# confidence - 置信度(距离),EigenFace 中范围通常是 0~20000
# 低于 5000 认为是比较可靠的匹配
label, confidence = recognizer.predict(pre_image)
dic = {0: 'wyz', 1: 'yy', -1: '-1'}
print('这人是:', dic[label])
print('置信度:', confidence)
# ========== 6. 在图像上显示结果 ==========
result_img = cv2.imread('wyz.png').copy()
aa = cv2.putText(
result_img, dic[label], (10, 30),
cv2.FONT_HERSHEY_COMPLEX,
1, (0, 255, 0), 2
)
cv2.imshow('xx', aa)
cv2.waitKey(0)
cv2.destroyAllWindows()
六、API 详解
6.1 创建 EigenFace 识别器
python
recognizer = cv2.face.EigenFaceRecognizer_create(
num_components=None,
threshold=None
)
| 参数 | 默认值 | 含义 |
|---|---|---|
num_components |
0(全部保留) | PCA 保留的主成分数量,即使用多少个特征脸 |
threshold |
DBL_MAX | 识别阈值,距离超过此值则返回 -1 |
num_components 怎么选?
- 训练样本少时(每人 1~2 张),取默认全部即可
- 训练样本多时,取 50~100 通常足够
- 这个值越大,保留的信息越多,但计算也越慢
- 可以通过「累计方差贡献率」来选择:比如保留 95% 的方差对应的 K 值
6.2 训练与预测
和 LBPH 一样,也是 train + predict 两步:
python
recognizer.train(src, labels) # 训练
label, confidence = recognizer.predict(src) # 预测
重要区别 :EigenFace 要求所有训练图像和测试图像尺寸必须完全一致 。如果尺寸不同,必须先用 cv2.resize 统一。这是因为 PCA 需要对每个像素位置做统计,位置必须一一对应。
6.3 置信度解读
EigenFace 的置信度(confidence)和 LBPH 有很大不同:
| 算法 | 置信度含义 | 典型范围 | 可靠阈值 |
|---|---|---|---|
| LBPH | 直方图距离 | 0~200 左右 | < 80 |
| EigenFace | 重构误差 / 欧氏距离 | 0~20000 左右 | < 5000 |
EigenFace 的置信度数值通常比较大,因为它是在高维空间中计算的距离。经验上,5000 以下可以认为是比较可靠的匹配。
当然,这个阈值需要根据你的数据集和应用场景来调整。
七、EigenFace 的优缺点
优点
- 理论优美:基于严格的 PCA 数学推导,有坚实的理论基础
- 特征维度低:将几万维降到几十维,识别速度快
- 去噪效果好:PCA 相当于一种信号压缩,能过滤掉一部分噪声
- 可解释性强:每个特征脸都有直观的视觉含义
缺点
- 对光照敏感:PCA 的第一主成分通常对应光照变化,光照差异可能盖过身份差异
- 对姿态敏感:侧脸、仰头低头都会影响识别效果
- 需要统一尺寸:所有图像必须 resize 到相同大小
- 需要较多训练样本:样本太少时 PCA 估计不准
- 全局特征为主:对局部细节(如眼睛形状)的捕捉不如 LBPH 精细
八、提高 EigenFace 准确率的方法
1. 人脸对齐与裁剪
先用 Haar 级联分类器检测人脸,裁剪出人脸区域后再做识别。排除背景干扰,让 PCA 真正学到人脸的特征。
2. 直方图均衡化
光照是 EigenFace 的大敌。对所有图像做直方图均衡化,可以在一定程度上缓解光照差异:
python
gray = cv2.equalizeHist(gray)
3. 增加训练样本
每人训练样本越多,PCA 学得越准,识别效果越好。建议每人至少 5~10 张。
4. 调整主成分数量
num_components 不是越大越好。太小会丢失信息,太大会引入噪声。可以尝试不同的值,找到最佳平衡点。
5. 数据预处理
- 确保所有图像的人脸位置大致对齐(眼睛在同一水平线)
- 统一图像尺寸和比例
- 尽量保证光照条件一致
九、运行结果
运行代码后,控制台输出:
这人是: wyz
置信度: 9518.875311635313
识别结果图:

可以看到 EigenFace 正确识别了测试图片的身份。置信度 9518.88 相对较高(EigenFace 的置信度范围通常在 0~20000),这说明训练样本较少(每人仅 2 张)时,匹配距离偏大。如果增加训练样本数量,置信度会明显降低,识别也会更可靠。
十、EigenFace vs LBPH 对比
| 对比项 | LBPH | EigenFace |
|---|---|---|
| 核心思想 | 局部纹理直方图 | 全局 PCA 降维 |
| 光照鲁棒性 | 好 | 一般 |
| 姿态鲁棒性 | 一般 | 较差 |
| 训练样本要求 | 少,1 张也能训 | 较多,建议每人 3 张以上 |
| 图像尺寸要求 | 不需要统一 | 必须统一 |
| 识别速度 | 一般(和样本数成正比) | 快(低维空间计算) |
| 理解难度 | 容易 | 中等(需要 PCA 基础) |
小结
本篇详细讲解了 EigenFace 特征脸算法的原理和实现。核心要点:
- 图像即向量:每张人脸都是高维空间中的一个点
- PCA 降维:找到最能代表人脸变化的主方向,用低维特征表示高维图像
- 特征脸:PCA 的特征向量就是特征脸,是人脸的「基础模板」
- 识别过程:将人脸投影到低维空间,用最近邻分类
- 注意事项:图像尺寸必须统一;对光照和姿态比较敏感