一、核心功能实现原理

采用多种计算机视觉技术,通过摄像头实时采集视频流,对画面中人脸进行多维度分析。系统核心技术包括深度学习模型推理、人脸关键点检测、几何特征计算和实时图像处理。

二、模型加载与初始化模块

2.1 模型文件配置

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"             # 性别识别模型权重文件

参数说明:

  • pbtxt文件:TensorFlow模型的文本配置文件,描述网络结构
  • pb文件:TensorFlow模型的二进制权重文件
  • prototxt文件:Caffe模型的网络结构定义文件
  • caffemodel文件:Caffe模型的训练权重文件

2.2 模型加载方法

# 加载神经网络模型
ageNet = cv2.dnn.readNet(ageModel, ageProto)      # 加载年龄识别模型
genderNet = cv2.dnn.readNet(genderModel, genderProto)  # 加载性别识别模型
faceNet = cv2.dnn.readNet(faceModel, faceProto)   # 加载人脸检测模型

方法解析:

  • cv2.dnn.readNet()是OpenCV的深度学习模块核心函数
  • 参数1:模型权重文件路径
  • 参数2:模型配置文件路径
  • 函数返回一个网络对象,可用于前向传播计算

2.3 数据预处理参数

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)  # 图像均值减去的RGB值

参数作用:

  • ageList:8个年龄段的分类标签,对应模型输出层的8个神经元
  • genderList:2个性别分类标签,对应模型输出层的2个神经元
  • mean:训练模型时使用的图像均值,用于输入数据标准化

三、人脸检测模块

3.1 人脸检测函数

def getBoxes(net, frame):
    """
    使用深度学习模型检测图像中的人脸位置
    
    设计思路:
    1. 将输入图像转换为模型需要的blob格式
    2. 执行前向传播获得检测结果
    3. 过滤低置信度的检测框
    4. 将归一化坐标转换为像素坐标
    5. 在图像上绘制检测框
    
    参数详解:
        net: cv2.dnn.readNet()加载的人脸检测模型对象
        frame: 输入图像,格式为numpy数组(H, W, C)
        
    返回值:
        frame: 绘制了人脸检测框的图像
        faceBoxes: 人脸边界框列表,每个元素为[x1, y1, x2, y2]
    """
    frameHeight, frameWidth = frame.shape[:2]
    
    # 关键步骤1:图像预处理
    blob = cv2.dnn.blobFromImage(
        frame,                    # 输入图像
        1.0,                      # 缩放因子
        (300, 300),               # 模型输入尺寸
        [104, 117, 123],          # 减去的BGR均值
        True,                     # 交换RB通道(OpenCV默认BGR,模型需要RGB)
        False                     # 不裁剪图像中心
    )
    
    # 关键步骤2:模型推理
    net.setInput(blob)           # 设置模型输入
    detections = net.forward()   # 执行前向传播
    
    faceBoxes = []
    
    # 关键步骤3:结果解析
    for i in range(detections.shape[2]):  # 遍历所有检测结果
        confidence = detections[0, 0, i, 2]  # 获取置信度
        
        if confidence > 0.7:  # 置信度阈值过滤
            # 关键步骤4:坐标转换(归一化→像素)
            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])  # 保存人脸框
            
            # 关键步骤5:可视化
            cv2.rectangle(
                frame,              # 目标图像
                (x1, y1),           # 矩形左上角
                (x2, y2),           # 矩形右下角
                (0, 255, 0),        # 颜色(绿色)
                int(round(frameHeight / 150)),  # 线宽(自适应)
                6                   # 线条类型
            )
    return frame, faceBoxes

核心参数详解:

cv2.dnn.blobFromImage()参数:

  • frame:输入图像
  • 1.0:像素值缩放因子,保持原始强度
  • (300, 300):模型要求的输入尺寸
  • [104, 117, 123]:训练时使用的BGR均值,用于数据标准化
  • True:交换R和B通道,因为模型在RGB图像上训练
  • False:不进行中心裁剪

检测结果数据结构:

  • detections.shape为(1, 1, N, 7),其中N为检测到的对象数
  • 每个检测包含7个值:[batch_id, class_id, confidence, x1, y1, x2, y2]

四、性别与年龄识别模块

4.1 中文文本绘制函数

def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):
    """
    解决OpenCV无法直接显示中文的问题
    设计思路:使用PIL库绘制中文,再转换回OpenCV格式
    
    参数详解:
        img: 输入图像,可以是numpy数组或PIL Image
        text: 要绘制的中文文本
        position: 文本起始位置(x, y)
        textColor: 文本颜色,默认为绿色(0, 255, 0)
        textSize: 字体大小,默认为30
        
    返回值:
        添加了中文文本的图像(numpy数组,BGR格式)
    """
    if (isinstance(img, np.ndarray)):
        # 关键步骤1:格式转换
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    
    draw = ImageDraw.Draw(img)  # 创建绘图对象
    
    # 关键步骤2:字体加载
    fontStyle = ImageFont.truetype(
        "simsun.ttc",      # 字体文件路径(宋体)
        textSize,          # 字体大小
        encoding="utf-8"   # 编码格式
    )
    
    # 关键步骤3:文本绘制
    draw.text(position, text, textColor, font=fontStyle)
    
    # 关键步骤4:格式转换回OpenCV
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

4.2 性别年龄识别流程

# 核心识别逻辑
if faceBoxes:
    for faceBox in faceBoxes:
        x1, y1, x2, y2 = faceBox
        
        # 步骤1:提取人脸区域
        face = frame[y1:y2, x1:x2]  # 图像切片操作
        
        # 步骤2:准备模型输入
        blob = cv2.dnn.blobFromImage(
            face,          # 人脸区域图像
            1.0,           # 缩放因子
            (227, 227),    # 性别年龄模型输入尺寸
            mean,          # 标准化用的均值
            swapRB=False,  # 不交换通道(已在训练时处理)
            crop=False     # 不裁剪
        )
        
        # 步骤3:性别识别
        genderNet.setInput(blob)              # 设置模型输入
        genderOuts = genderNet.forward()      # 前向传播
        gender = genderList[genderOuts[0].argmax()]  # 取最大概率类别
        
        # 步骤4:年龄识别
        ageNet.setInput(blob)                 # 设置模型输入
        ageOuts = ageNet.forward()            # 前向传播
        age = ageList[ageOuts[0].argmax()]    # 取最大概率类别
        
        # 步骤5:结果显示
        result = "{} {}".format(gender, age)  # 拼接结果
        frame = cv2AddChineseText(frame, result, (x1, y1 - 30))  # 显示在面部上方

在这里插入图片描述

算法特点:

  • 使用两个独立的CNN模型分别进行性别和年龄识别
  • 模型输出为概率分布,取最大值作为预测结果
  • 年龄识别为分类问题而非回归问题,使用年龄段而非具体年龄

五、面部表情识别模块

5.1 dlib人脸关键点检测器

# 关键点检测器初始化
detector = dlib.get_frontal_face_detector()  # 人脸检测器
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")  # 关键点预测器

# 关键点检测流程
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # 转换为灰度图(dlib要求)
faces = detector(gray, 0)  # 检测人脸,第二个参数表示上采样次数

for face in faces:
    shape = predictor(frame, face)  # 获取68个关键点
    shape = np.array([[p.x, p.y] for p in shape.parts()])  # 转换为numpy数组

参数说明:

  • dlib.get_frontal_face_detector():返回一个基于HOG+SVM的人脸检测器
  • shape_predictor_68_face_landmarks.dat:预训练的68点人脸关键点模型
  • detector(gray, 0):0表示不进行图像上采样,适用于标准分辨率图像

5.2 嘴部特征计算函数

def MAR(shape):
    """
    计算嘴部纵横比(Mouth Aspect Ratio)
    
    设计思路:
    1. 计算嘴部上下三对对应点的垂直距离
    2. 计算平均值作为嘴部垂直张开度
    3. 计算嘴部水平宽度
    4. 比值反映嘴部张开程度
    
    关键点索引说明:
    48-54: 外嘴唇轮廓
    60-64: 内嘴唇轮廓
    50,58: 上嘴唇中点与下嘴唇中点
    
    参数:
        shape: 68x2的关键点坐标数组
        
    返回值:
        MAR值,越大表示嘴张得越开
    """
    # 垂直距离计算(三对对应点)
    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


def MJR(shape):
    """
    计算嘴部宽度与脸宽比例(Mouth Jaw Ratio)
    
    设计思路:
    1. 嘴部宽度反映微笑时嘴角拉伸程度
    2. 脸宽作为参考基准
    3. 比值越大表示微笑越明显
    
    参数:
        shape: 68x2的关键点坐标数组
        
    返回值:
        MJR值,越大表示微笑越明显
    """
    # 嘴部宽度(嘴角到嘴角)
    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

关键点索引解释:

  • 索引0-16:下巴轮廓
  • 索引17-21:右眉毛
  • 索引22-26:左眉毛
  • 索引27-35:鼻子
  • 索引36-41:右眼
  • 索引42-47:左眼
  • 索引48-60:外嘴唇
  • 索引61-67:内嘴唇

5.3 表情分类逻辑

# 恢复被注释的显示代码
frame = cv2AddChineseText(frame, result, (50, 100))  # 固定位置显示
cv2.putText(frame, result, (50, 150), 
           cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)  # OpenCV英文显示

# 表情判断逻辑
mar = MAR(shape)
mjr = MJR(shape)
result = "正常"

if mar > 0.5:        # 嘴部张开度阈值
    result = "大笑"
elif mjr > 0.45:     # 微笑拉伸阈值
    result = "微笑"

# 嘴部轮廓可视化
mouthHull = cv2.convexHull(shape[48:61])  # 生成嘴部凸包
frame = cv2AddChineseText(frame, result, 
                         (int(mouthHull[0, 0, 0]), int(mouthHull[0, 0, 1])))  # 嘴部位置显示
cv2.drawContours(frame, [mouthHull], -1, (0, 255, 0), 1)  # 绘制轮廓

在这里插入图片描述

阈值选择依据:

  • MAR > 0.5:基于大量实验数据,当嘴部垂直高度达到水平宽度的一半时,通常为大笑
  • MJR > 0.45:微笑时嘴角拉伸,嘴宽通常达到脸宽的45%以上

六、疲劳状态检测模块

6.1 眼部纵横比计算

def eye_aspect_ratio(eye):
    """
    计算眼部纵横比(Eye Aspect Ratio)
    
    设计思路:
    1. 计算眼部上下两对点的垂直距离
    2. 计算平均值作为眼部垂直闭合度
    3. 计算眼部水平宽度作为基准
    4. 比值越小表示眼睛闭合程度越大
    
    关键点索引(单眼6个点):
    0: 左眼角
    1: 上眼睑中点
    2: 右眼角
    3: 下眼睑中点
    4: 下眼睑右侧
    5: 上眼睑右侧
    
    参数:
        eye: 6x2的眼部关键点坐标数组
        
    返回值:
        EAR值,正常睁眼时约为0.25-0.35,闭眼时接近0
    """
    # 垂直距离计算
    A = euclidean_distances(eye[1].reshape(1, 2), eye[5].reshape(1, 2))
    B = euclidean_distances(eye[2].reshape(1, 2), eye[4].reshape(1, 2))
    
    # 水平宽度计算
    C = euclidean_distances(eye[0].reshape(1, 2), eye[3].reshape(1, 2))
    
    ear = ((A + B) / 2.0) / C
    return ear


def drawEye(eye):
    """
    绘制眼部填充区域
    
    参数:
        eye: 眼部关键点坐标
        
    实现方法:
    1. 使用凸包算法连接眼部关键点
    2. 填充凸包区域实现可视化
    """
    eyehull = cv2.convexHull(eye)  # 生成眼部凸包
    cv2.drawContours(
        frame,       # 目标图像
        [eyehull],   # 轮廓列表
        -1,          # 绘制所有轮廓
        (0, 255, 0), # 填充颜色(绿色)
        -1           # 负值表示填充内部
    )

6.2 疲劳检测逻辑

COUNTER = 0  # 连续闭眼帧数计数器

# 眼部关键点提取
rightEye = shape[36:42]  # 右眼关键点(索引36-41)
leftEye = shape[42:48]   # 左眼关键点(索引42-47)

# EAR计算
rightEAR = eye_aspect_ratio(rightEye)
leftEAR = eye_aspect_ratio(leftEye)
ear = (leftEAR + rightEAR) / 2.0  # 双眼平均值

# 疲劳状态判断
if ear < 0.3:  # EAR阈值
    COUNTER += 1  # 增加闭眼帧数
    
    if COUNTER >= 50:  # 连续闭眼帧数阈值
        # 危险警告显示
        frame = cv2AddChinesetext(
            frame, 
            "!!!!危险!!!!", 
            (250, 250),         # 屏幕中央位置
            textColor=(255, 0, 0),  # 红色强调
            textSize=50          # 大字号
        )
else:
    COUNTER = 0  # 眼睛睁开,重置计数器

# 眼部可视化
drawEye(leftEye)
drawEye(rightEye)

# EAR值显示
info = "EAR: {:.2f}".format(ear[0][0])  # 格式化显示两位小数
frame = cv2AddChinesetext(frame, info, (0, 30))  # 左上角显示

在这里插入图片描述

阈值设定原理:

  • ear < 0.3:正常睁眼时EAR约为0.25-0.35,低于0.3表示眼睛部分闭合
  • COUNTER >= 50:以30fps计算,连续闭眼约1.67秒触发警告,避免眨眼误报

七、集成主循环

7.1 主循环结构

COUNTER = 0  # 全局疲劳计数器

while True:
    # 步骤1:视频帧读取
    ret, frame = cap.read()
    frame = cv2.flip(frame, 1)  # 水平镜像,使显示更自然
    
    # 步骤2:性别年龄识别(基于OpenCV DNN)
    frame_dnn, faceBoxes = getBoxes(faceNet, frame)
    
    # 步骤3:多特征分析(基于dlib)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # dlib需要灰度图
    faces = detector(gray, 0)
    
    # 步骤4:遍历所有人脸进行特征分析
    for face in faces:
        # 关键点检测
        shape = predictor(frame, face)
        shape = np.array([[p.x, p.y] for p in shape.parts()])
        
        # 表情识别
        mar = MAR(shape)
        mjr = MJR(shape)
        # ...表情判断逻辑...
        
        # 疲劳检测
        rightEye = shape[36:42]
        leftEye = shape[42:48]
        # ...疲劳判断逻辑...
    
    # 步骤5:显示结果
    cv2.imshow("Multi-Feature Detection", frame)
    
    # 步骤6:退出检测
    if cv2.waitKey(1) == 27:  # ESC键退出
        break

在这里插入图片描述

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐