基于 OpenCV+Dlib 的实时人脸分析系统:年龄性别检测 + 疲劳监测 + 表情识别

一、项目核心技术栈与实现原理

1.1 核心技术框架

本项目融合了OpenCV、Dlib、NumPy和Scikit-learn四大核心工具:

  • OpenCV:负责摄像头调用、图像预处理、DNN 模型加载、人脸检测、图形绘制(轮廓 / 文字 / 矩形框)

  • Dlib:实现高精度的 68 个人脸特征点检测,为眨眼监测、表情识别提供核心坐标数据

  • NumPy:处理特征点坐标的数组运算,适配各类距离计算和矩阵操作

  • Scikit-learn:提供欧氏距离计算接口,简化特征点之间的距离求解,避免手动编写距离公式。

1.2 核心实现原理

整个系统的核心逻辑围绕人脸检测 - 特征提取 - 指标计算 - 结果判定 - 可视化展示展开:

  1. 先通过 OpenCV 的 DNN 模块实现快速人脸检测,得到人脸包围框

  2. 再通过 Dlib 的 68 点特征点预测器,提取人脸关键区域(眼睛、嘴巴、面部轮廓)的坐标

  3. 计算眼睛纵横比(EAR)、嘴巴纵横比(MAR)、嘴脸比例(MAJ)等核心指标

  4. 根据指标阈值判定疲劳状态、表情类型,同时通过预训练的 DNN 模型预测年龄和性别

  5. 最后将所有分析结果实时绘制在画面上,并解决 OpenCV 原生不支持中文显示的问题。

1.3 关键预训练模型与文件

  1. OpenCV 人脸检测模型:opencv_face_detector.pbtxt(配置文件)、opencv_face_detector_uint8.pb(权重文件)

  2. 年龄预测模型:deploy_age.prototxt(配置文件)、age_net.caffemodel(权重文件)

  3. 性别预测模型:deploy_gender.prototxt(配置文件)、gender_net.caffemodel(权重文件)

  4. Dlib68 点特征点预测器:shape_predictor_68_face_landmarks.dat

  5. 中文显示字体:simhei.ttf(黑体,解决 OpenCV 中文乱码问题)

二、项目核心模块

2.1中文显示与基础配置

OpenCV 的cv2.putText方法原生不支持中文字符绘制,会出现乱码,因此需要结合 Pillow 库实现中文显示功能,这是所有可视化结果的基础。同时定义项目通用的常量(年龄 / 性别列表、像素均值)。

python 复制代码
import cv2
import dlib
import numpy as np
from sklearn.metrics.pairwise import euclidean_distances
from PIL import Image, ImageDraw, ImageFont

# 年龄/性别分类列表
ageList = ['0-2岁','4-6岁','8-12岁','15-20岁','25-32岁','38-43岁','48-53岁','60-100岁']
genderList = ['男性','女性']
# 年龄/性别模型输入标准化均值
mean = (78.4263377603,87.7689143744,114.895847746)

# 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))  # BGR转RGB,适配Pillow
    draw = ImageDraw.Draw(img)
    # 加载黑体字体,需保证simhei.ttf文件存在
    fontStyle = ImageFont.truetype("simhei.ttf", textSize, encoding="utf-8")
    draw.text(position, text, textColor, font=fontStyle)
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)  # RGB转回BGR,适配OpenCV

2.2 人脸检测模块

采用 OpenCV 的 DNN 模块加载预训练的人脸检测模型,实现高效的人脸包围框检测,支持多个人脸同时检测,并绘制人脸矩形框,置信度阈值设为 0.7 以过滤无效检测结果。

cv2.dnn.blobFromImage是 DNN 模型的标准输入处理方法,将原始图像转换为模型可识别的 blob 格式,其中[104,117,123]是人脸检测模型的标准化均值。

python 复制代码
# 加载预训练模型
faceProto = "model/opencv_face_detector.pbtxt"
faceModel = "model/opencv_face_detector_uint8.pb"
ageProto = "model/deploy_age.prototxt"
ageModel = "model/age_net.caffemodel"
genderProto = "model/deploy_gender.prototxt"
genderModel = "model/gender_net.caffemodel"

ageNet = cv2.dnn.readNet(ageModel,ageProto)    # 年龄预测网络
genderNet = cv2.dnn.readNet(genderModel,genderProto)  # 性别预测网络
faceNet = cv2.dnn.readNet(faceModel,faceProto)  # 人脸检测网络

# 获取人脸包围框并绘制
def getBoxes(net, frame):
    frameHeight,frameWidth = frame.shape[:2]
    # 构建DNN输入Blob:缩放、归一化、尺寸调整
    blob = cv2.dnn.blobFromImage(frame,1.0,(300,300),[104,117,123],True,False)
    net.setInput(blob)
    detections = net.forward()  # 执行检测,返回四维检测结果
    faceBoxes = []
    # 遍历所有检测结果
    for i in range(detections.shape[2]):
        confidence = detections[0,0,i,2]  # 获取检测置信度
        if confidence>0.7:  # 过滤低置信度结果
            # 将归一化坐标转换为实际像素坐标
            x1 = int(detections[0,0,i,3] * frameWidth)
            y1 = int(detections[0,0,i,4] * frameHeight)
            x2 = int(detections[0,0,i,5] * frameWidth)
            y2 = int(detections[0,0,i,6] * frameHeight)
            faceBoxes.append([x1,y1,x2,y2])
            # 绘制人脸矩形框:绿色、线宽自适应画面高度
            cv2.rectangle(frame,(x1,y1),(x2,y2),(0,255,0),int(round(frameHeight /150)),6)
    return frame, faceBoxes

2.3 年龄性别检测模块

对检测到的每个人脸区域进行裁剪,转换为年龄 / 性别模型要求的 227×227 尺寸,通过预训练的 Caffe 模型实现快速属性预测,取预测概率最大值作为最终结果,并通过中文显示模块绘制在画面上。

python 复制代码
frame, faceBoxes = getBoxes(faceNet, frame)
    if not faceBoxes:#没有人脸时检测下一顿,后续循环操作不再继续。
        print("当前镜头中没有人")
        continue
    #遍历每一个人脸包国框
    for faceBox in faceBoxes:
        #处理frame,将其处理为符合DNN输入的格式
        x1,y1,x2,y2=faceBox
        face=frame[y1:y2,x1:x2]
        blob = cv2.dnn.blobFromImage(face,1.0,(227,227),mean)#模型输入为227*277,参考论文
        #调用模型,预测性别
        genderNet.setInput(blob)
        genderOuts = genderNet.forward()
        gender =genderList[genderOuts[0].argmax()]
        # 调用模型,预测年龄
        ageNet.setInput(blob)
        ageOuts = ageNet.forward()
        age = ageList[ageOuts[0].argmax()]
        result="{},{}".format(gender,age)# 格式化文本(年龄、性别)
        frame=cv2AddChineseText(frame,result,(x1,y1-30))#输出中文性别和年龄

2.4 疲劳监测模块

通过 Dlib 提取 68 个特征点中左右眼睛的坐标(左眼:42-48,右眼:36-42),计算 EAR 值并绘制眼睛凸包轮廓,实现疲劳监测

EAR眼睛的纵横比是判定疲劳的核心指标,通过计算眼睛上下眼睑的垂直距离与左右眼角的水平距离的比值实现:

  • 正常睁眼时,EAR 值稳定在 0.3 以上

  • 眨眼时,眼睑闭合,垂直距离骤减,EAR 值会快速低于阈值(本项目设为 0.30)

  • 设置计数阈值(50 帧),连续低于阈值时判定为闭眼 / 犯困

函数封装与基础信息:

python 复制代码
# 计算眼睛纵横比(EAR)
def eye_aspect_ratio(eye):
    # 计算眼睛上下眼睑的两个垂直距离
    A = euclidean_distances(np.array(eye[1]),np.array(eye[5]))
    B = euclidean_distances(np.array(eye[2]),np.array(eye[4]))
    # 计算眼睛左右眼角的水平距离
    C = euclidean_distances(np.array(eye[0]),np.array(eye[3]))
    ear = ((A+ B)/2.0)/C  # 计算EAR值
    return ear

# 绘制眼睛凸包轮廓
def drawEye(eye):
    eyeHuLl = cv2.convexHull(eye)  # 生成凸包,拟合眼睛轮廓
    cv2.drawContours(frame,[eyeHuLl],-1,(0,255,0),1)

# 初始化Dlib检测器和特征点预测器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
COUNTER = 0  # 眨眼计数

主程序:

python 复制代码
faces = detector(frame, 1)
    for face in faces:
        shape = predictor(frame, face)
        shape = np.matrix([[p.x, p.y] for p in shape.parts()])
        rightEye = shape[36:42]
        leftEye = shape[42:48]
        rightEAR = eye_aspect_ratio(rightEye)

        leftEAR = eye_aspect_ratio(leftEye)
        ear = (leftEAR + rightEAR) / 2.0

        if ear < 0.30:
            COUNTER += 1
            if COUNTER >= 50:
                frame = cv2AddChineseText(frame, "!!!别睡了!!!", (250, 250))
        else:
            COUNTER = 0

        drawEye(leftEye)
        drawEye(rightEye)

        info = "EAR:{:.2f}".format(ear[0][0])
        frame = cv2AddChineseText(frame, info, (0, 30))

2.5 表情识别模块

通过 Dlib 提取嘴巴区域的 68 特征点(48-61),计算两个核心指标实现表情分类,分别是嘴巴纵横比(MAR)和嘴脸比例(MAJ),通过阈值划分正常、微笑、大笑三种表情,并绘制嘴巴凸包轮廓。

函数封装:

python 复制代码
# 计算嘴巴纵横比(MAR):嘴巴上下距离/嘴巴左右距离
def MAR(shape):
    A = euclidean_distances(shape[50].reshape(1,2),shape[58].reshape(1,2))
    B = euclidean_distances(shape[51].reshape(1,2),shape[57].reshape(1,2))
    C = euclidean_distances(shape[52].reshape(1,2),shape[56].reshape(1,2))
    D = euclidean_distances(shape[48].reshape(1,2),shape[54].reshape(1,2))
    return ((A+B+C)/3)/D

# 计算嘴脸比例(MAJ):嘴巴左右距离/面部左右宽度
def MAJ(shape):
    M = euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2))
    J = euclidean_distances(shape[3].reshape(1, 2), shape[13].reshape(1, 2))
    return M/J

主流程:

python 复制代码
shape = predictor(frame, face)
        shape = np.array([[p.x, p.y] for p in shape.parts()])
        mar = MAR(shape)
        mjr = MAJ(shape)
        result = "正常"
        # print('mar', mar, '\tmjr', mjr)
        if mar > 0.5:
            result = "大笑"
        elif mjr > 0.45:
            result = "微笑"
        mouthHull = cv2.convexHull(shape[48:61])
        frame = cv2AddChineseText(frame, result, mouthHull[0, 0])
        cv2.drawContours(frame, [mouthHull], -1, (0, 255, 0), 1)

三、结果显示

程序运行后,会弹出实时显示窗口,实现以下功能:

  1. 绿色矩形框标记检测到的人脸

  2. 人脸上方显示预测的性别 + 年龄

  3. 画面左上角显示实时EAR 值,眼睛周围绘制绿色轮廓

  4. 嘴巴周围绘制绿色轮廓,嘴巴上方显示正常 / 微笑 / 大笑表情

  5. 当连续闭眼(EAR<0.30)50 帧时,画面中间显示红色警告文字 !!!别睡了!!!

相关推荐
互联网江湖2 小时前
携程当学胖东来
人工智能
陌殇殇2 小时前
001 Spring AI Alibaba框架整合百炼大模型平台 — 快速入门
人工智能·spring boot·ai
Proxy_ZZ02 小时前
用Matlab绘制BER曲线对比SPA与Min-Sum性能
人工智能·算法·机器学习
黎阳之光2 小时前
黎阳之光:以视频孪生领跑全球,赋能数字孪生水利智能监测新征程
大数据·人工智能·算法·安全·数字孪生
宇擎智脑科技2 小时前
基于 SAM3 + FastAPI 搭建智能图像标注工具实战
人工智能·计算机视觉
F_U_N_2 小时前
效率提升80%:AI全流程研发真实项目落地复盘
人工智能·ai编程
月诸清酒2 小时前
24-260409 AI 科技日报 (Gemma 4发布一周下载破千万,开源模型生态加速演进)
人工智能·开源
2501_933329552 小时前
技术架构深度解析:Infoseek舆情监测系统的全链路设计与GEO时代的技术实践
开发语言·人工智能·分布式·架构
X journey3 小时前
机器学习进阶(16):如何防止过拟合
人工智能·机器学习
AI_Claude_code3 小时前
ZLibrary访问困境方案四:利用Cloudflare Workers等边缘计算实现访问
javascript·人工智能·爬虫·python·网络爬虫·边缘计算·爬山算法