计算机视觉(opencv)——基于 MediaPipe 的实时面部表情识别

基于 MediaPipe 的实时面部表情识别:数据采集、训练与实时推理

面部表情是人类情感表达的重要通道。通过计算机视觉与机器学习,我们可以实现自动化的表情识别,应用于人机交互、智能会议、心理健康评估、情绪分析等场景。本文以两段完整代码为核心,讲解如何使用 MediaPipe 提取面部网格关键点、采集表情数据、训练分类模型(比较常见模型并保存最优),以及如何用训练好的模型进行实时识别与显示。文章面向有一定 Python 与机器学习基础的读者,包含要点说明、实现细节、改进建议与注意事项。

系统概览

整个系统由三部分组成:

  1. 数据采集:使用摄像头与 MediaPipe Face Mesh 提取 468 个关键点(x,y,z),并保存为 CSV,附带标签(例如 neutral/happy/sad/angry/surprised)。

  2. 模型训练:读取 CSV 数据,做标准化(StandardScaler),尝试多个分类器(随机森林、SVM、KNN),比较准确率并保存最佳模型与 scaler。

  3. 实时识别:加载保存的模型和 scaler,实时从摄像头获取关键点,进行预处理并预测表情,同时在视频帧上绘制网格与识别结果。

关键实现细节与说明

1. 面部关键点提取

使用 MediaPipe Face Mesh 可以获得面部 468 个关键点,每个点包含 x,y,z 相对坐标(归一化到 0-1)。代码在采集阶段将这些坐标按顺序写入 CSV:顺序为所有 x,再所有 y,再所有 z(代码中 header 构造为 x0..x467,y0..y467,z0..z467,label),这能让每帧数据形成固定长度向量(3×468=1404 个数值)。

注意:MediaPipe 的 landmark 是相对于图像尺寸的比例值,z 值为深度相对估计,范围和含义与 x,y 不完全相同。若要用于绝对位置建模,可以乘以图像宽高或做归一化处理。

2. 数据采集设计

采集脚本对每种表情停留一段固定时间(默认 10 秒),自动将每一帧的关键点写入 CSV。为了提高数据质量建议:

  • 在稳定光照与固定背景下采集;

  • 让多名被试参与,增加样本多样性(年龄、性别、表情强度);

  • 对每种表情采集多次,包含轻微/中等/夸张三种强度;

  • 记录采集元数据(被试 ID、采集时间、摄像头分辨率)以便后续分析。

3. 特征工程与标准化

原始关键点直接作为特征通常可行,但存在尺度与冗余问题。文章示例使用 StandardScaler 对特征进行标准化,这是常见做法。此外可以考虑:

  • 以鼻尖或两眼中心为参考点做平移与尺度归一(消除头部平移与缩放影响);

  • 计算关键点间的角度、距离或几何不变特征,降低对摄像头位置敏感;

  • 使用 PCA 降维或保留部分主成分加速训练与减少噪声。

4. 模型选择与比较

示例中对比了随机森林(RandomForest)、SVM(RBF kernel)与 KNN。实践中:

  • 随机森林对噪声与特征尺度不敏感,训练与推理速度较快,可直接给出概率估计(predict_proba)。

  • SVM 在中高维数据上表现稳定,但对参数(C、gamma)敏感,训练耗时随样本数增长显著。

  • KNN 简单直观,但在高维空间性能退化且预测慢(需要计算空间距离)。

建议使用交叉验证(例如 5-fold CV)来选择超参数与模型,此外可尝试更先进的模型如 XGBoost、LightGBM,或深度学习方法(多层感知机、1D-CNN、基于 Transformer 的序列模型)在大量数据下通常更有优势。

5. 实时识别与工程化

实时模块加载模型与 scaler,循环获取摄像头帧、提取关键点、标准化、预测并在画面上显示表情与置信度。工程化注意点:

  • 将模型与 scaler 在离线环境下保存并在部署端加载(示例使用 joblib)。

  • 对摄像头读取失败或丢帧做好容错(重试逻辑、降采样)。

  • 若模型支持 predict_proba,可展示 Top-3 表情与概率以便调试与用户体验优化。

  • 若需要跨平台部署(移动端、嵌入式),可使用轻量化模型或转换为 ONNX/TF-Lite。

常见问题与改进建议

  1. 数据不平衡:部分表情采集帧数少会导致偏差。需在采集或训练时做类重采样(过采样/欠采样)或采用类别权重。

  2. 光照/姿态鲁棒性:增加采集条件的多样性,或采用数据增强(亮度、旋转、噪声)提高泛化。

  3. 多人场景 :示例限定 max_num_faces=1,若需多人识别需扩展到多目标跟踪并为每个检测到的人维护独立缓存与预测。

  4. 对抗表情/遮挡:当面部遮挡(口罩、眼镜、手挡)时性能下降,可研究局部特征或多模态(声音、身体姿态)融合。

  5. 实时性能:在高帧率或低算力设备上,可通过降频(例如每隔 N 帧预测一次)、使用较轻的模型或优化模型推理来保证实时体验。

总结

本文介绍了一个从数据采集、模型训练到实时推理的端到端面部表情识别流程,使用 MediaPipe 提取 468 个面部关键点作为特征,采用经典机器学习方法进行训练并实时展示识别结果。该方案易于实现、便于扩展,适合作为原型系统与教学示例。后续可在数据质量、特征工程、模型优化与多模态融合方向继续改进,以提升准确率与鲁棒性。


下面附上你提供的两段完整代码(未改动),可直接保存为脚本运行与调试。

代码一:数据采集脚本

import cv2

import mediapipe as mp

import numpy as np

import csv

import os

from datetime import datetime

初始化MediaPipe面部识别

mp_face_mesh = mp.solutions.face_mesh

face_mesh = mp_face_mesh.FaceMesh(static_image_mode=False, max_num_faces=1, min_detection_confidence=0.5)

mp_drawing = mp.solutions.drawing_utils

表情标签和对应的数字编码

EXPRESSIONS = {

'neutral': 0,

'happy': 1,

'sad': 2,

'angry': 3,

'surprised': 4

}

def collect_expression_data(output_file='expression_data.csv', collect_duration=10):

"""

采集面部表情数据

参数:

output_file: 数据保存的CSV文件路径

collect_duration: 每种表情的采集时长(秒)

"""

检查文件是否存在,如果不存在则创建并写入表头

file_exists = os.path.isfile(output_file)

打开摄像头

cap = cv2.VideoCapture(0)

if not cap.isOpened():

print("无法打开摄像头")

return

遍历每种表情进行采集

for expr, label in EXPRESSIONS.items():

print(f"即将开始采集 {expr} 表情数据")

print("请准备好,3秒后开始采集...")

cv2.waitKey(3000) # 等待3秒

print(f"开始采集 {expr} 表情数据,将持续 {collect_duration} 秒...")

start_time = datetime.now()

while (datetime.now() - start_time).total_seconds() < collect_duration:

ret, frame = cap.read()

if not ret:

print("无法获取视频帧")

break

转换为RGB格式,因为MediaPipe需要RGB输入

rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

results = face_mesh.process(rgb_frame)

如果检测到面部

if results.multi_face_landmarks:

for face_landmarks in results.multi_face_landmarks:

提取面部关键点坐标

landmarks = []

for lm in face_landmarks.landmark:

landmarks.append(lm.x)

landmarks.append(lm.y)

landmarks.append(lm.z)

将数据写入CSV文件

with open(output_file, 'a', newline='') as f:

writer = csv.writer(f)

if not file_exists:

写入表头

header = [f'x{i}' for i in range(468)] + [f'y{i}' for i in range(468)] + [f'z{i}' for i in range(468)] + ['label']

writer.writerow(header)

file_exists = True

写入数据行(关键点坐标 + 表情标签)

writer.writerow(landmarks + [label])

在图像上绘制面部网格

mp_drawing.draw_landmarks(

image=frame,

landmark_list=face_landmarks,

connections=mp_face_mesh.FACEMESH_TESSELATION,

landmark_drawing_spec=None,

connection_drawing_spec=mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=1, circle_radius=1)

)

显示提示信息

cv2.putText(frame, f"Collecting {expr}... {int(collect_duration - (datetime.now() - start_time).total_seconds())}s",

(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

cv2.imshow('Expression Collection', frame)

按q键可以提前退出

if cv2.waitKey(1) & 0xFF == ord('q'):

cap.release()

cv2.destroyAllWindows()

return

print("数据采集完成!")

cap.release()

cv2.destroyAllWindows()

if name == "main":

collect_expression_data()

代码二:训练与实时识别脚本

复制代码
import cv2
import mediapipe as mp
import numpy as np
import pandas as pd
import joblib
import os
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt

# 初始化MediaPipe面部识别
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(static_image_mode=False, max_num_faces=1, min_detection_confidence=0.5)
mp_drawing = mp.solutions.drawing_utils

# 表情标签
EXPRESSIONS = {
    0: 'neutral',
    1: 'happy',
    2: 'sad',
    3: 'angry',
    4: 'surprised'
}

def train_expression_model(data_file='expression_data.csv', model_file='best_expression_model.pkl', scaler_file='scaler.pkl'):
    """
    训练表情识别模型并保存最优模型

    参数:
        data_file: 包含训练数据的CSV文件路径
        model_file: 保存最优模型的文件路径
        scaler_file: 保存数据标准化器的文件路径
    """
    # 检查数据文件是否存在
    if not os.path.isfile(data_file):
        print(f"数据文件 {data_file} 不存在,请先运行数据采集程序")
        return

    # 加载数据
    print("加载训练数据...")
    data = pd.read_csv(data_file)

    # 分割特征和标签
    X = data.iloc[:, :-1].values  # 所有特征(面部关键点)
    y = data.iloc[:, -1].values   # 标签(表情类别)

    # 数据分割:训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    # 数据标准化
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)

    # 保存标准化器
    joblib.dump(scaler, scaler_file)
    print(f"标准化器已保存到 {scaler_file}")

    # 定义要测试的模型
    models = {
        'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
        'SVM': SVC(kernel='rbf', gamma='scale', random_state=42),
        'KNN': KNeighborsClassifier(n_neighbors=5)
    }

    # 训练并评估每个模型
    best_accuracy = 0
    best_model = None

    print("\n开始模型训练和评估...")
    for name, model in models.items():
        print(f"\n训练 {name} 模型...")
        model.fit(X_train_scaled, y_train)

        # 预测
        y_pred = model.predict(X_test_scaled)

        # 评估
        accuracy = accuracy_score(y_test, y_pred)
        print(f"{name} 准确率: {accuracy:.4f}")
        print("分类报告:")
        print(classification_report(y_test, y_pred, target_names=EXPRESSIONS.values()))

        # 跟踪最佳模型
        if accuracy > best_accuracy:
            best_accuracy = accuracy
            best_model = model

    # 保存最佳模型
    if best_model is not None:
        joblib.dump(best_model, model_file)
        print(f"\n最佳模型已保存到 {model_file},准确率: {best_accuracy:.4f}")

    return best_model, scaler

def realtime_expression_recognition(model_file='best_expression_model.pkl', scaler_file='scaler.pkl'):
    """
    使用训练好的模型进行实时表情识别

    参数:
        model_file: 训练好的模型文件路径
        scaler_file: 数据标准化器文件路径
    """
    # 检查模型文件和标准化器文件是否存在
    if not os.path.isfile(model_file) or not os.path.isfile(scaler_file):
        print(f"模型文件 {model_file} 或标准化器文件 {scaler_file} 不存在,请先训练模型")
        return

    # 加载模型和标准化器
    model = joblib.load(model_file)
    scaler = joblib.load(scaler_file)

    # 打开摄像头
    cap = cv2.VideoCapture(0)
    if not cap.isOpened():
        print("无法打开摄像头")
        return

    print("开始实时表情识别(按q键退出)...")
    while True:
        ret, frame = cap.read()
        if not ret:
            print("无法获取视频帧")
            break

        # 转换为RGB格式
        rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        results = face_mesh.process(rgb_frame)

        # 如果检测到面部
        if results.multi_face_landmarks:
            for face_landmarks in results.multi_face_landmarks:
                # 提取面部关键点坐标
                landmarks = []
                for lm in face_landmarks.landmark:
                    landmarks.append(lm.x)
                    landmarks.append(lm.y)
                    landmarks.append(lm.z)

                # 数据预处理
                landmarks_np = np.array(landmarks).reshape(1, -1)
                landmarks_scaled = scaler.transform(landmarks_np)

                # 预测表情
                prediction = model.predict(landmarks_scaled)
                expression = EXPRESSIONS[prediction[0]]

                # 获取预测概率
                probabilities = model.predict_proba(landmarks_scaled)[0]
                confidence = max(probabilities) * 100

                # 在图像上绘制面部网格
                mp_drawing.draw_landmarks(
                    image=frame,
                    landmark_list=face_landmarks,
                    connections=mp_face_mesh.FACEMESH_TESSELATION,
                    landmark_drawing_spec=None,
                    connection_drawing_spec=mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=1, circle_radius=1)
                )

                # 显示识别结果
                cv2.putText(frame, f"Expression: {expression} ({confidence:.1f}%)",
                           (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

        # 显示图像
        cv2.imshow('Real-time Expression Recognition', frame)

        # 按q键退出
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

if __name__ == "__main__":
    # 训练模型(如果还没有训练)
    if not os.path.isfile('best_expression_model.pkl'):
        train_expression_model()

    # 进行实时表情识别
    realtime_expression_recognition()
相关推荐
忙碌54421 小时前
AI大模型时代下的全栈技术架构:从深度学习到云原生部署实战
人工智能·深度学习·架构
LZ_Keep_Running21 小时前
智能变电巡检:AI检测新突破
人工智能
InfiSight智睿视界21 小时前
AI 技术助力汽车美容行业实现精细化运营管理
大数据·人工智能
没有钱的钱仔1 天前
机器学习笔记
人工智能·笔记·机器学习
听风吹等浪起1 天前
基于改进TransUNet的港口船只图像分割系统研究
人工智能·深度学习·cnn·transformer
化作星辰1 天前
深度学习_原理和进阶_PyTorch入门(2)后续语法3
人工智能·pytorch·深度学习
boonya1 天前
ChatBox AI 中配置阿里云百炼模型实现聊天对话
人工智能·阿里云·云计算·chatboxai
8K超高清1 天前
高校巡展:中国传媒大学+河北传媒学院
大数据·运维·网络·人工智能·传媒
老夫的码又出BUG了1 天前
预测式AI与生成式AI
人工智能·科技·ai
AKAMAI1 天前
AI 边缘计算:决胜未来
人工智能·云计算·边缘计算