一、引言:让计算机看懂人脸
在之前的博客中,我们学习了OpenCV的图像处理、特征检测、目标提取等技术。今天,我们将进入一个更加贴近生活的应用领域------人脸检测与识别。
人脸检测与识别是计算机视觉中最经典、应用最广泛的技术之一。它已经深入到我们日常生活的方方面面:
手机解锁:Face ID让你看一眼屏幕就能解锁手机
门禁系统:刷脸进出办公楼和小区
相机美颜:自动检测人脸并应用美颜滤镜
安防监控:在人群中自动识别特定人员
表情分析:检测微笑、愤怒等情绪状态
这些看似神奇的功能,其实都离不开两大核心技术:人脸检测 (找到人脸在哪里)和人脸识别(确认这是谁的脸)。
本篇博客将结合6个完整的代码示例,从静态图片检测到视频实时检测,从LBPH到EigenFaces再到FisherFace,带领大家系统掌握OpenCV中的人脸检测与识别技术。
二、人脸检测------Haar级联分类器
2.1 什么是Haar级联分类器
Haar级联分类器是一种基于机器学习的对象检测方法,由Paul Viola和Michael Jones在2001年提出。它通过使用Haar-like特征和级联分类器来实现快速的人脸检测。
Haar-like特征是一种简单的矩形特征,通过计算图像中相邻矩形区域的像素和之差来描述图像的局部特征。例如,眼睛区域比脸颊区域暗,鼻梁区域比眼睛区域亮,这些明暗对比关系可以通过Haar特征来捕捉。
级联分类器的核心思想是"层层筛选":将多个弱分类器串联起来,前面的分类器快速排除大量非人脸区域,后面的分类器对疑似人脸区域进行更精细的判断。这种方式可以大幅提高检测速度。
OpenCV已经预训练好了多种Haar级联分类器,存放在cv2/data/目录下:
| 分类器文件 | 检测目标 |
|---|---|
haarcascade_frontalface_default.xml |
正面人脸 |
haarcascade_frontalface_alt.xml |
正面人脸(替代版) |
haarcascade_smile.xml |
微笑 |
haarcascade_eye.xml |
眼睛 |
haarcascade_fullbody.xml |
全身 |
2.2 静态图像人脸检测
让我们先从一个简单的例子开始------检测一张静态图片中的人脸:
python
import cv2
# 读取图像并转为灰度图
image = cv2.imread('people.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 加载人脸分类器
faceCascade = cv2.CascadeClassifier(
"haarcascade_frontalface_default.xml"
)
# 检测人脸
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.05,
minNeighbors=9,
minSize=(8, 8)
)
print("发现{}张人脸!".format(len(faces)))
print("其位置分别是:", faces)
# 标注人脸
for (x, y, w, h) in faces:
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow("result", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
detectMultiScale()参数详解:
| 参数 | 含义 | 说明 |
|---|---|---|
image |
待检测图像 | 通常为灰度图像 |
scaleFactor |
缩放比例 | 前后两次扫描中搜索窗口的缩放比例,默认1.1 |
minNeighbors |
最小相邻矩形数 | 构成检测目标所需的最小相邻矩形个数,默认3 |
flags |
标志位 | 通常省略 |
minSize |
最小尺寸 | 小于该尺寸的目标将被忽略 |
maxSize |
最大尺寸 | 大于该尺寸的目标将被忽略 |
返回值 :faces是一个矩形框向量组,包含每个检测到的人脸的(x, y, w, h)信息。
参数调优建议:
-
scaleFactor越小,检测越细致,但速度越慢 -
minNeighbors越大,检测越严格,误检越少但可能漏检 -
根据图像大小调整
minSize和maxSize可以显著提升检测效率
2.3 视频实时人脸检测与微笑检测
将人脸检测应用到视频流中,就可以实现实时检测。我们还可以在人脸区域内进一步检测微笑:
python
import cv2
# 加载分类器
faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
smile = cv2.CascadeClassifier("haarcascade_smile.xml")
# 打开摄像头或视频文件
cap = cv2.VideoCapture('xiao.mp4')
while True:
ret, image = cap.read()
if ret is None:
break
image = cv2.resize(image, (500, 500))
image = cv2.flip(image, flipCode=1) # 水平翻转(镜像)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 人脸检测
faces = faceCascade.detectMultiScale(
gray, scaleFactor=1.1, minNeighbors=15, minSize=(5, 5)
)
for (x, y, w, h) in faces:
# 绘制人脸矩形框
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
# 提取人脸区域
roi_gray_face = gray[y:y + h, x:x + w]
# 在人脸区域内检测微笑
smiles = smile.detectMultiScale(
roi_gray_face, scaleFactor=1.5, minNeighbors=10, minSize=(50, 50)
)
for (sx, sy, sw, sh) in smiles:
# 绘制微笑区域
a = x + sx
b = y + sy
cv2.rectangle(image, (a, b), (a + sw, b + sh), (255, 0, 0), 2)
# 显示文字"smile"
cv2.putText(image, "smile", (x, y),
cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 255, 255), 2)
cv2.imshow("detect", image)
if cv2.waitKey(30) == 27: # ESC键退出
break
cap.release()
cv2.destroyAllWindows()
代码要点:
-
cv2.flip(image, flipCode=1):水平翻转实现镜像效果,让操作更自然 -
微笑检测仅在人脸区域内进行,这样可以大幅减少误检和计算量
-
微笑分类器的
scaleFactor和minNeighbors需要比人脸检测更严格,因为微笑特征更局部
三、人脸识别------三种经典算法
人脸检测解决了"人脸在哪里"的问题,而人脸识别要解决的是"这是谁的脸"。
OpenCV提供了三种经典的人脸识别算法:
| 算法 | 核心思想 | 特点 |
|---|---|---|
| LBPH | 局部二值模式直方图 | 对光照变化鲁棒,适合小样本 |
| EigenFaces | 主成分分析(PCA) | 降维提取主要特征,速度快 |
| FisherFace | 线性判别分析(LDA) | 最大化类间差异,识别率高 |
3.1 LBPH算法------局部二值模式直方图
LBPH(Local Binary Patterns Histograms) 是一种基于局部纹理特征的识别方法。
核心原理:
-
对图像中每个像素,将其与周围邻居比较,大于等于中心像素标记为1,小于标记为0
-
将二进制序列转换为十进制数,得到该像素的LBP值
-
将图像划分为多个网格(如8×8),统计每个网格的LBP直方图
-
将所有网格的直方图拼接成特征向量,用于识别
LBPH的优势:
-
对光照变化不敏感
-
计算简单,适合实时应用
-
可以处理小样本训练
下面的代码展示了LBPH的完整使用流程:
python
import cv2
import numpy as np
# 准备训练数据
images = []
images.append(cv2.imread('hg1.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('hg2.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('yz1.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('yz2.png', cv2.IMREAD_GRAYSCALE))
labels = [0, 0, 1, 1]
dic = {0: 'hg', 1: 'yz', -1: '无法识别'}
# 待识别人脸
predict_image = cv2.imread('yz3.png', cv2.IMREAD_GRAYSCALE)
# 创建LBPH识别器
recognizer = cv2.face.LBPHFaceRecognizer.create(threshold=80)
# 训练模型
recognizer.train(images, np.array(labels))
# 预测
label, confidence = recognizer.predict(predict_image)
print('这是:', dic[label])
print('置信度:', confidence)
参数说明:
-
radius:圆形局部二进制模式的半径,建议默认 -
neighbors:采样点数,建议默认 -
grid_x, grid_y:水平/垂直方向的单元格数,默认8×8 -
threshold:识别阈值,低于该值认为匹配可靠
返回值:
-
label:识别结果标签,-1表示无法识别 -
confidence:置信度,越小表示匹配度越高
3.2 EigenFaces算法------特征脸
EigenFaces 基于主成分分析(PCA) ,将人脸图像从高维空间投影到低维特征空间,提取最具代表性的"特征脸"。
核心思想:
-
将所有训练人脸图像拉成向量,计算平均脸
-
每张人脸减去平均脸,得到差异图像
-
通过PCA找到差异图像的主要成分(特征脸)
-
将每张人脸表示为特征脸的线性组合
-
识别时,将待识别人脸投影到特征脸空间,与训练样本比较距离
下面展示了EigenFaces的使用:
python
import cv2
import numpy as np
# 准备训练数据(注意:图片大小必须一致)
images = []
a = cv2.imread('hg1.png', 0)
a = cv2.resize(a, (200, 200))
b = cv2.imread('hg2.png', 0)
b = cv2.resize(b, (200, 200))
d = cv2.imread('yz1.png', 0)
d = cv2.resize(d, (200, 200))
e = cv2.imread('yz2.png', 0)
e = cv2.resize(e, (200, 200))
images = [a, b, d, e]
labels = [0, 0, 1, 1]
# 待识别图像
image = cv2.imread('yz3.png')
pre_image = cv2.resize(image, (200, 200))
pre_image = cv2.cvtColor(pre_image, cv2.COLOR_BGR2GRAY)
# 创建EigenFaces识别器
recognizer = cv2.face.EigenFaceRecognizer.create(threshold=5000)
# 训练
recognizer.train(images, np.array(labels))
# 预测
label, confidence = recognizer.predict(pre_image)
dic = {0: 'yz', 1: 'hg', -1: '-1'}
print('这人是:', dic[label])
print('置信度为:', confidence)
注意事项:
-
所有训练图片和待识别图片必须调整为相同尺寸(如200×200)
-
threshold需要根据实际情况调整,太大容易误识别,太小容易拒识 -
EigenFaces对光照和角度变化较为敏感
3.3 FisherFace算法------线性判别分析
FisherFace 基于线性判别分析(LDA) ,与EigenFaces类似,但它不仅考虑数据的主要变化方向,还最大化类间差异、最小化类内差异。
核心思想:
-
EigenFaces只关注"哪些方向变化最大"
-
FisherFace关注"哪些方向最能区分类别"
因此,FisherFace通常比EigenFaces有更高的识别率,尤其适合类别区分明显的场景。
下面展示了FisherFace的使用,并实现了中文标注:
python
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
def cv2add_chinese_text(img, text, position, textcolor=(0, 255, 0), textsize=30):
"""向图片中添加中文"""
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):
a = cv2.imread(image, 0)
a = cv2.resize(a, (200, 200))
images.append(a)
# 准备训练数据
images = []
image_re('hg1.png')
image_re('hg2.png')
image_re('yz1.png')
image_re('yz2.png')
labels = [0, 0, 1, 1]
# 待识别图像
image = cv2.imread('hg3.png')
grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
pre_image = cv2.resize(grey, (200, 200))
# 创建FisherFace识别器
recognizer = cv2.face.FisherFaceRecognizer.create(threshold=5000)
# 训练
recognizer.train(images, np.array(labels))
# 预测
label, confidence = recognizer.predict(pre_image)
dic = {0: '胡歌', 1: '彦祖', -1: '无法识别'}
print('这是:', dic[label])
print('置信度为:', confidence)
# 输出中文
image = cv2add_chinese_text(image.copy(), dic[label], (30, 10), (255, 0, 0))
cv2.imshow('xx', image)
cv2.waitKey(0)
中文标注的技巧 :OpenCV自带的cv2.putText()不支持中文,我们借助PIL库的ImageFont和ImageDraw实现中文绘制。核心步骤是:
-
将OpenCV图像转为PIL图像
-
使用PIL绘制中文文本
-
再转回OpenCV格式
四、三种算法对比与选择建议
| 对比维度 | LBPH | EigenFaces | FisherFace |
|---|---|---|---|
| 核心原理 | 局部二值模式 | 主成分分析(PCA) | 线性判别分析(LDA) |
| 光照鲁棒性 | 强 | 弱 | 中 |
| 训练速度 | 快 | 中 | 中 |
| 识别速度 | 快 | 快 | 快 |
| 小样本表现 | 好 | 一般 | 好 |
| 是否需要统一尺寸 | 否 | 是 | 是 |
| 适用场景 | 实时检测、光照变化大 | 简单场景 | 类别区分明显的场景 |
选择建议:
-
LBPH:最推荐,对光照不敏感,使用最灵活
-
EigenFaces:适合光照稳定的简单场景
-
FisherFace:适合需要高识别率的场景
五、综合实战:完整的人脸识别系统
将人脸检测和人脸识别结合,可以构建一个完整的实时人脸识别系统:
python
import cv2
import numpy as np
# 加载人脸检测器
faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
# 准备训练数据
images = []
labels = []
dic = {0: 'Person A', 1: 'Person B'}
# 假设已经准备好了训练图像
# images.append(...)
# labels.append(...)
# 创建LBPH识别器
recognizer = cv2.face.LBPHFaceRecognizer.create(threshold=80)
# recognizer.train(images, np.array(labels))
# 打开摄像头
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 人脸检测
faces = faceCascade.detectMultiScale(gray, 1.1, 5)
for (x, y, w, h) in faces:
# 绘制人脸框
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
# 提取人脸区域并调整大小
roi = gray[y:y + h, x:x + w]
roi = cv2.resize(roi, (200, 200))
# 人脸识别
# label, confidence = recognizer.predict(roi)
# name = dic.get(label, '未知')
# cv2.putText(frame, name, (x, y - 10),
# cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
cv2.imshow('Face Recognition', frame)
if cv2.waitKey(1) == 27:
break
cap.release()
cv2.destroyAllWindows()
六、总结
本篇博客系统学习了OpenCV中人脸检测与识别的核心技术:
| 技术 | 核心方法 | 关键函数 |
|---|---|---|
| 人脸检测 | Haar级联分类器 | detectMultiScale() |
| 微笑检测 | Haar级联分类器(smile) | detectMultiScale() |
| LBPH识别 | 局部二值模式直方图 | LBPHFaceRecognizer.create() |
| EigenFaces识别 | 主成分分析 | EigenFaceRecognizer.create() |
| FisherFace识别 | 线性判别分析 | FisherFaceRecognizer.create() |
| 中文标注 | PIL文字绘制 | ImageFont.truetype() |