最近在啃OpenCV的人脸识别模块,整理了一套从入门到进阶的可运行代码,从最基础的单张图片人脸检测,到LBPH、EigenFace、FisherFace三种经典识别算法,最后还有摄像头实时识别的综合案例。第一个例子会拆得细一点,方便新手理解,后面的例子重点讲核心逻辑和差异,重复的基础操作就不多啰嗦了。
一、基础入门:单张图片人脸检测(Haar级联分类器)
1.1 原理
Haar级联是OpenCV自带的传统人脸检测方案,靠提取图像的Haar-like特征,用多层级联分类器一步步滑动窗口筛选,优点是速度快、资源占用低,非常适合入门练手。 需要用到官方预训练的配置文件 haarcascade_frontalface_default.xml,OpenCV安装目录里能找到,网上也能随便搜到,记得和代码放同一个目录,或者写绝对路径。
1.2 环境准备
先装依赖库:
python
pip install opencv-python numpy
1.3 代码分步拆解
步骤1:导入库 + 读取图片 + 灰度转换
python
import cv2
import numpy as np
# 读取待检测图片
image = cv2.imread('img.png')
# 转灰度图------人脸检测不需要彩色信息,灰度能大幅减少计算量
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
这里提一句,OpenCV默认读图片是BGR通道,不是RGB,转灰度用COLOR_BGR2GRAY就好。
步骤2:加载人脸分类器
python
# ----------------加载分类器----------------
faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
踩过的坑 :路径错了不会报错,但检测结果永远是空。如果一直检测不到人脸,先打印一下faceCascade.empty()看看是不是True,True就是加载失败了。
步骤3:核心函数:detectMultiScale 人脸检测
这是最关键的一步,参数调得好不好直接影响检测效果。原代码里的注释很全,再补点大白话解释:
python
# ----------------分类器检测实现人脸识别 ----------------
# objects = cv2.CascadeClassifier.detectMultiScale( image[, scaleFactor
# [,minNeighbors[, flags[, minSize[, maxSize]]]] )
# 其中,各个参数及返回值的含义如下。
# ·image:待检测图像,通常为灰度图像。
# ·scaleFactor:表示在前后两次相继扫描中搜索窗口的缩放比例。识别,扫描,按照不同比例来进行扫描
# ·minNeighbors:表示构成检测目标的相邻矩形的最小个数。在默认情况下,该参数的值为3,
# 表示有3个以上的检测标记存在时才认为存在人脸。如果希望提高检测的准确率可以将该参数的值设置得更大,
# 但这样做可能会让一些人脸无法被检测到。
# ·flags: 该参数通常被省略。在使用低版本 OpenCV(OpenCV 1.X 版本)时,该参数可能会被设置为
# # CV_HAAR_DO_CANNY_PRUNING,表示使用 Canny 边缘检测器拒绝一些区域。
# ·minSize: 目标的最小尺寸,小于这个尺寸的目标将被忽略。
# ·maxSize: 目标的最大尺寸,大于这个尺寸的目标将被忽略。通常情况下,将该可选参数省略即可。
# 若maxSize 和 minSize 大小一致,则表示仅在一个尺度上查找目标。
# ·objects: 返回值,目标对象的矩形框向量组。该值是一组矩形信息,
# 包含每个检测到的人脸对应的矩形框的信息(x轴方向位置、y轴方向位置、宽度、高度)。
faces = faceCascade.detectMultiScale(gray, scaleFactor=1.05,minNeighbors=25, minSize=(8, 8))
个人调参:
scaleFactor:常用范围1.05~1.2。数值越小,检测越精细,但速度越慢;1.2速度快,但容易漏掉小脸。minNeighbors:控制误检率,数值越高越不容易出现"把背景当人脸"的情况,但也容易漏检。单人场景可以调到15~25,多人场景建议10左右。minSize:根据图片里人脸的实际大小设,比如远距离监控就设小一点,避免漏检。
步骤4:画框 + 显示结果
python
print("发现{0}张人脸!".format(len(faces)))
print("其位置分别是: ", faces)
# ----------------标注人脸及显示 ----------------
for (x, y, w, h) in faces:
# 画矩形框:绿色,线宽2像素
cv2.rectangle(image, pt1=(x, y), pt2=(x + w, y + h), color=(0, 255, 0), thickness=2)
# 显示窗口
cv2.imshow("result", image)
cv2.waitKey(0) # 按任意键关闭
cv2.destroyAllWindows()
# 预生成随机颜色数组,后面多目标检测扩展用
color=np.random.randint(0,255,(100,3))
1.4 完整可运行代码
把上面拼起来就是完整版本,替换成自己的图片路径就能跑:
python
import cv2
import numpy as np
image = cv2.imread('img.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# ----------------加载分类器----------------
faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
# ----------------分类器检测实现人脸识别 ----------------
# objects = cv2.CascadeClassifier.detectMultiScale( image[, scaleFactor
# [,minNeighbors[, flags[, minSize[, maxSize]]]] )
# 其中,各个参数及返回值的含义如下。
# ·image:待检测图像,通常为灰度图像。
# ·scaleFactor:表示在前后两次相继扫描中搜索窗口的缩放比例。识别,扫描,按照不同比例来进行扫描
# ·minNeighbors:表示构成检测目标的相邻矩形的最小个数。在默认情况下,该参数的值为3,
# 表示有3个以上的检测标记存在时才认为存在人脸。如果希望提高检测的准确率可以将该参数的值设置得更大,
# 但这样做可能会让一些人脸无法被检测到。
# ·flags: 该参数通常被省略。在使用低版本 OpenCV(OpenCV 1.X 版本)时,该参数可能会被设置为
# # CV_HAAR_DO_CANNY_PRUNING,表示使用 Canny 边缘检测器拒绝一些区域。
# ·minSize: 目标的最小尺寸,小于这个尺寸的目标将被忽略。
# ·maxSize: 目标的最大尺寸,大于这个尺寸的目标将被忽略。通常情况下,将该可选参数省略即可。
# 若maxSize 和 minSize 大小一致,则表示仅在一个尺度上查找目标。
# ·objects: 返回值,目标对象的矩形框向量组。该值是一组矩形信息,
# 包含每个检测到的人脸对应的矩形框的信息(x轴方向位置、y轴方向位置、宽度、高度)。
faces = faceCascade.detectMultiScale(gray, scaleFactor=1.05,minNeighbors=25, minSize=(8, 8))
print("发现{0}张人脸!".format(len(faces)))
print("其位置分别是: ", faces)
# ----------------标注人脸及显示 ----------------
for (x, y, w, h) in faces:
cv2.rectangle(image, pt1=(x, y), pt2=(x + w, y + h), color=(0, 255, 0), thickness=2)
cv2.imshow("result", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
color=np.random.randint(0,255,(100,3))
二、进阶:摄像头实时人脸+微笑检测
在人脸检测基础上,再加一个微笑检测分类器,调用摄像头实时跑。核心优化思路是:先检测人脸,再只在人脸区域内检测微笑,既提升速度,又减少背景误检。
完整代码
python
# 人脸微笑检测(摄像头)
import cv2
faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
smile = cv2.CascadeClassifier("haarcascade_smile.xml")
cap = cv2.VideoCapture(0) # 初始化摄像头,0代表默认第一个摄像头
while True: # 循环处理每一帧画面
ret, image = cap.read() # 读取一帧
image = cv2.flip(image, flipCode=1) # 水平翻转,镜像效果,符合自拍习惯
# 读取失败直接退出
if ret is None:
break
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, pt1=(x, y), pt2=(x + w, y + h), color=(0, 255, 0), thickness=2)
# 提取人脸ROI区域,只在这块区域检测微笑
roi_gray_face = gray[y:y + h, x:x + w]
cv2.imshow('lian',roi_gray_face)
# 微笑检测
smiles = smile.detectMultiScale(roi_gray_face,scaleFactor=1.5,minNeighbors=10,minSize=(50, 50))
for (sx, sy, sw, sh) in smiles:
# ROI坐标转回原图坐标
a = x + sx
b = y + sy
cv2.rectangle(image, pt1=(a, b),
pt2=(a + sw, b + sh), color=(255, 0, 0), thickness=2)
# 标注文字
cv2.putText(image,"smile",(x, y), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1,(0, 255, 255), 2)
# 显示结果
cv2.imshow("dect", image)
key = cv2.waitKey(1)
if key == 27: # 按ESC键退出
break
cap.release()
cv2.destroyAllWindows()
核心知识点
- ROI感兴趣区域:抠出人脸区域再做二次检测,是计算机视觉里非常常用的优化手段。
- 坐标映射:ROI里检测出的坐标是相对左上角的,要加回原图的x、y偏移量才能正确画框。
- 视频逻辑 :
while True+waitKey(1)是OpenCV视频处理的标准写法,延时1毫秒保证画面流畅。
三、人脸识别算法1:LBPH 局部二值模式
前面都是"检测有没有人脸",从这里开始是"识别是谁的脸"。 LBPH全称Local Binary Patterns Histograms,通过提取人脸局部纹理特征生成直方图,对比直方图距离判断身份。最大的好处是对光照不敏感,也不用统一图片尺寸,对新手友好。
完整代码
python
import cv2
import numpy as np
# 训练集:每个人准备2张不同角度的人脸灰度图
images=[]
images.append(cv2.imread('mjq.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('mjq2.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('syx.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('syx2.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('zzy.png', cv2.IMREAD_GRAYSCALE))
images.append(cv2.imread('zzy2.png', cv2.IMREAD_GRAYSCALE))
# 标签:和图片一一对应,同一个人标签相同
labels = [0, 0, 1, 1, 2, 2]
dic = {0:"mjq",1:"syx",2:"zzy",-1:"-1"}
# 待识别图片
predict_image=cv2.imread('mjq.png',cv2.IMREAD_GRAYSCALE)
# cv2.face.LBPHFaceRecognizer_create(radius=None, neighbors=None, grid_x=None, grid_y=None, threshold=None)
# 功能:创建一个LBPH的人脸特征识别器
# radius:圆形局部二进制模式的半径,默认即可
# neighbors:采样点数目,默认即可
# grid_x/grid_y:特征图划分的单元格数,默认8×8
# threshold:识别阈值,超过阈值返回-1(无法识别)
recognizer=cv2.face.LBPHFaceRecognizer_create(threshold=80)
# 训练模型
recognizer.train(images,np.array(labels))
# 预测
# label:识别结果标签
# confidence:置信度,数值越小匹配度越高,超过80认为差距过大
label,confidence=recognizer.predict(predict_image)
print('这人是:',dic[label])
print('置信度:',confidence)
四、人脸识别算法2:EigenFace 特征脸
基于PCA主成分分析的经典算法,把高维人脸图像降维到"特征脸"空间,通过计算投影距离匹配身份。
完整代码
python
import cv2
import numpy as np
images=[] # 注意:EigenFace要求所有图片尺寸必须一致!
a=cv2.imread(filename='mjq.png', flags=0)
a=cv2.resize(a, dsize=(120,180))
b=cv2.imread(filename='mjq2.png', flags=0)
b=cv2.resize(b, dsize=(120,180))
c=cv2.imread(filename='syx.png', flags=0)
c=cv2.resize(c, dsize=(120,180))
d=cv2.imread(filename='syx2.png', flags=0)
d=cv2.resize(d, dsize=(120,180))
e=cv2.imread(filename='zzy.png', flags=0)
e=cv2.resize(e, dsize=(120,180))
f=cv2.imread(filename='zzy.png', flags=0)
f=cv2.resize(f, dsize=(120,180))
images.append(a)
images.append(b)
images.append(c)
images.append(d)
images.append(e)
images.append(f)
labels=[0,0,1,1,2,2]
pre_image=cv2.imread( 'mjq.png', flags=0)
pre_image=cv2.resize(pre_image, dsize=(120,180))
# 创建识别器
# num_components:PCA保留的主成分数量,一般80够用
# threshold:识别阈值
recognizer = cv2.face.EigenFaceRecognizer_create(threshold=5000)
# 训练 + 预测
recognizer.train(images, np.array(labels))
label,confidence = recognizer.predict(pre_image)
dic={0:'mjq',1:'syx',2:'zzy',-1:'-1'}
print('这人是:',dic[label])
print('置信度为:',confidence)
# 在图上标注结果
aa=cv2.putText(cv2.imread('mjq.png').copy(),dic[label],(10,30),cv2.FONT_HERSHEY_SIMPLEX,
fontScale=0.9, color=(0,0,255), thickness=2)
cv2.imshow('xx',aa)
cv2.waitKey(0)
注意点
- 强制统一尺寸:训练集和测试集必须resize到同样宽高,不然直接报错。
- 置信度范围0~20000,越小越相似,一般5000以内算匹配成功。
- 对光照、姿态变化比较敏感,适合正面、光照均匀的场景。
五、人脸识别算法3:FisherFace
基于LDA线性判别分析,核心思想是"类间差异最大化,类内差异最小化",纯分类效果通常比EigenFace好一点。
完整代码
顺便加了OpenCV显示中文的通用方法(原生putText不支持中文,用PIL中转):
python
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
# OpenCV图片添加中文工具函数
def cv2AddChineseText(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, flags=0)
a=cv2.resize(a, dsize=(120,180))
images.append(a)
images=[]
image_re('mjq.png')
image_re('mjq2.png')
image_re('syx.png')
image_re('syx2.png')
image_re('zzy.png')
image_re('zzy2.png')
labels=[0,0,1,1,2,2]
pre_image=cv2.imread('mjq.png',0)
pre_image=cv2.resize(pre_image, dsize=(120,180))
recognizer=cv2.face.FisherFaceRecognizer_create(threshold=5000)
recognizer.train(images,np.array(labels))
label,confidence=recognizer.predict(pre_image)
dic={0:'马',1:'宋',2:'张',-1:'无法识别'}
print('这人是:',dic[label])
print('置信度为:',confidence)
# 显示中文结果
image = cv2AddChineseText(cv2.imread('mjq.png').copy(), dic[label], position=(30,10),textColor=(255,0,0))
cv2.imshow('xx',image)
cv2.waitKey(0)
六、综合实战:摄像头实时人脸识别
最后这个是课程作业,把前面所有技术串起来:提前训练模型,摄像头实时检测人脸→裁剪对齐→识别身份→中文姓名显示在人脸框上方。
完整代码
python
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
"""
作业:
使用所学的人脸识别任意算法实现对摄像头画面进行人脸识别,识别结果实时显示在人脸矩形框的正上方。
提示:多收集一些人脸图片,先进行人脸检测,然后再进行人脸识别。
"""
def cv2AddChineseText(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, flags=0)
a=cv2.resize(a, dsize=(120,180))
images.append(a)
# 1. 加载训练数据
images=[]
image_re('mjq.png')
image_re('mjq2.png')
image_re('syx.png')
image_re('syx2.png')
image_re('zzy.png')
image_re('zzy2.png')
image_re('my.png')
image_re('my2.jpg')
labels=[0,0,1,1,2,2,3,3]
dic = {0: "马", 1: "宋", 2: "张", 3: "自己", -1: "无法识别"}
# 2. 训练模型
recognizer = cv2.face.FisherFaceRecognizer_create(threshold=5000)
recognizer.train(images, np.array(labels))
# 3. 人脸检测器
faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
# 4. 实时识别
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
frame = cv2.flip(frame, flipCode=1)
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = faceCascade.detectMultiScale(gray,scaleFactor=1.1,minNeighbors=15,minSize=(30,30))
for (x, y, w, h) in faces:
cv2.rectangle(frame, pt1=(x, y), pt2=(x + w, y + h), color=(0, 255, 0), thickness=2)
roi_gray_face = gray[y:y + h, x:x + w]
cv2.imshow('lian',roi_gray_face)
pre_image = cv2.resize(roi_gray_face, dsize=(120, 180))
label, confidence = recognizer.predict(pre_image)
frame = cv2AddChineseText(frame, dic[label], position=(x, y - 35), textColor=(0, 0, 255), textSize=24)
cv2.imshow('ziji', frame)
if cv2.waitKey(1)==27:
break
cap.release()
cv2.destroyAllWindows()
七、踩坑&注意事项总结
- 分类器文件路径:优先放同级目录,加载失败不会报错,只会检测不到结果。
- 图片尺寸:EigenFace和FisherFace必须统一尺寸,LBPH无要求。
- 灰度输入:检测和识别基本都要求灰度图,不要直接喂彩色图。
- 阈值调整:误识别多就把阈值调小,漏识别多就调大,根据自己的数据集慢慢试。
- 中文显示:OpenCV原生不支持中文,必须用PIL或者freetype扩展。
- 训练集质量:每个人2~5张不同角度、光照的照片最合适,太少容易过拟合,太多训练慢。
传统算法适合入门理解原理,实际复杂场景还是深度学习方案效果好,但这些基础逻辑是进阶的必经之路。