【深度学习】OpenCV 人脸识别实战:FisherFace + 摄像头 + 中文标注

文章目录


完整代码一览

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

# 人脸检测器
xml_file = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(xml_file)

# 中文绘制函数
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)
    try:
        fontStyle = ImageFont.truetype("simsun.ttc", textSize, encoding="utf-8")
    except:
        fontStyle = ImageFont.load_default()
    draw.text(position, text, textColor, font=fontStyle)
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

# 加载训练图片
images = []
def image_re(img_path):
    a = cv2.imread(img_path, flags=0)
    if a is None:
        print(f"警告:图片 {img_path} 缺失,跳过")
        return
    a = cv2.resize(a, dsize=(120, 180))
    images.append(a)

# 样本加载(权志龙只有1张会报错,临时复制一份凑数,后续自己补qzl2.png)
image_re('mcy1.png')
image_re('mcy2.png')
image_re('mcy3.png')
image_re('mcy4.png')

image_re('pyy1.png')
image_re('pyy2.png')

image_re('qzl1.png')
image_re('qzl2.png')  

# 标签同步增加一位,对应上面8张图
labels = [1, 1, 1, 1, 0, 0, 2, 2]
dic = {0: '彭于晏', 1: 'mcy', 2: '权志龙', -1: '无法识别'}

# Fisher人脸识别器
recognizer = cv2.face.FisherFaceRecognizer_create(threshold=5000)
recognizer.train(images, np.array(labels))

# 摄像头
cap = cv2.VideoCapture(0)

while True:
    ret, image = cap.read()
    if not ret:
        print("摄像头读取失败")
        break

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    faces = faceCascade.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=9, minSize=(8, 8))

    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]
        roi_gray_face = cv2.resize(roi_gray_face, (120, 180))

        label, confidence = recognizer.predict(roi_gray_face)
        if confidence > 5000:
            label = -1
        name = dic[label]

        image = cv2AddChineseText(image, dic[label], (x, y - 25), (0, 255, 255), 24)
        cv2.imshow('单人脸窗口', roi_gray_face)

    cv2.imshow('Video', image)
    if cv2.waitKey(60) == 27:
        break

cap.release()
cv2.destroyAllWindows()

导入库与安装说明

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

加载 Haar 人脸检测器

我们打开摄像头首先先进行人脸检测,之后在进行人脸识别,识别之后进行中文标注。

c 复制代码
xml_file = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(xml_file)

使用 OpenCV 自带的正脸检测模型,用于从摄像头画面中定位人脸位置。

中文绘制函数 cv2AddChineseText

c 复制代码
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)
    try:
        fontStyle = ImageFont.truetype("simsun.ttc", textSize, encoding="utf-8")
    except:
        fontStyle = ImageFont.load_default()
    draw.text(position, text, textColor, font=fontStyle)
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

这个函数专门解决 OpenCV 不能写中文的问题。

先判断输入是否是 OpenCV 的 NumPy 数组。如果是,则用 cv2.cvtColor 将 BGR 转为 RGB,然后用 Image.fromarray 转换为 PIL 图像对象。

if判断结束后在 PIL 图像上创建一个绘图对象 draw,加载中文字体文件 "simsun.ttc"(宋体),大小 textSize。

draw.text 在指定位置写入文字,颜色用 RGB 格式,最后将 PIL 图像转回 NumPy 数组,并转换回 BGR 格式,以便 OpenCV 显示。

准备训练数据

定义加载函数

c 复制代码
images = []
def image_re(img_path):
    a = cv2.imread(img_path, flags=0)
    if a is None:
        print(f"警告:图片 {img_path} 缺失,跳过")
        return
    a = cv2.resize(a, dsize=(120, 180))
    images.append(a)

读取灰度图(flags=0),统一缩放为 120×180 像素。

如果文件不存在,打印警告并跳过(避免崩溃)。

加载样本图片

c 复制代码
image_re('mcy1.png')
image_re('mcy2.png')
image_re('mcy3.png')
image_re('mcy4.png')

image_re('pyy1.png')
image_re('pyy2.png')

image_re('qzl1.png')
image_re('qzl2.png') 

加载博主(mcy)4 张照片、彭于晏(pyy)2 张照片、权志龙(qzl)2 张(第二张是复制第一张)。

FisherFace 要求每个类别至少有 2 个样本,否则无法计算类间散度矩阵。

如果只有 1 张图,可以临时复制一份。实际项目中应准备至少 2 张不同的照片。

设置标签和映射字典

c 复制代码
labels = [1, 1, 1, 1, 0, 0, 2, 2]
dic = {0: '彭于晏', 1: 'mcy', 2: '权志龙', -1: '无法识别'}

标签顺序与 images 列表中的图片一一对应:

前 4 张(mcy1~4)→ 标签 1(mcy)

后 2 张(pyy1~2)→ 标签 0(彭于晏)

最后 2 张(qzl 1~2)→ 标签 2(权志龙)

dic 将数字标签转为人名,-1 表示未识别。

创建 FisherFace 识别器并训练

c 复制代码
recognizer = cv2.face.FisherFaceRecognizer_create(threshold=5000)
recognizer.train(images, np.array(labels))

threshold=5000:设定识别阈值。如果预测的置信度大于 5000,则认为不认识,返回 -1。这个值需要根据实际测试调整,一般范围在 3000~8000。

打开摄像头并进入主循环

c 复制代码
cap = cv2.VideoCapture(0)
while True:
    ret, image = cap.read()
    if not ret:
        print("摄像头读取失败")
        break

人脸检测与识别

c 复制代码
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    faces = faceCascade.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=9, minSize=(8, 8))

将彩色帧转为灰度图。

detectMultiScale 检测人脸,参数:

scaleFactor=1.05:缩放步长较小,检测更精细。

minNeighbors=9:较大的值减少误报。

minSize=(8,8):最小人脸尺寸 8×8。

处理每个人脸

c 复制代码
    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]
        roi_gray_face = cv2.resize(roi_gray_face, (120, 180))# 裁剪出人脸区域(灰度图),并 resize 到 120×180

        label, confidence = recognizer.predict(roi_gray_face)
        if confidence > 5000:# 调用 recognizer.predict 预测,得到标签和置信度。如果置信度超过阈值,强制设为 -1
            label = -1
        name = dic[label]

        image = cv2AddChineseText(image, dic[label], (x, y - 25), (0, 255, 255), 24) # 用 cv2AddChineseText 在人脸框上方(y - 25)绘制名字
        cv2.imshow('单人脸窗口', roi_gray_face)

显示与退出

c 复制代码
    cv2.imshow('Video', image)
    if cv2.waitKey(60) == 27:
        break

显示带标注的主画面。

释放资源

c 复制代码
cap.release()
cv2.destroyAllWindows()

释放摄像头,关闭所有窗口。

运行效果与参数调优

运行效果:

摄像头打开后,会实时检测人脸,每个检测到的人脸会被绿色框标出,上方显示识别出的中文名。

如果识别置信度超过阈值,会显示"无法识别"。

常见问题与调整:

  • FisherFace 报错"样本数不足"

原因:某个人只有 1 张训练图。解决方案:准备至少 2 张不同图片,或临时复制(不推荐)。

  • 中文显示成方框

原因:未找到 simsun.ttc 字体。可以下载一个中文字体(如 msyh.ttc)放到当前目录,修改函数中的字体路径。

  • 识别率低

增加每个人的训练样本数量(10~20 张不同角度、光照)。或者调整阈值 threshold,观察打印的置信度,设定一个合理值。

  • 检测不到人脸

降低 scaleFactor(如 1.03)和 minNeighbors(如 5),或调整 minSize。