当前位置: 首页 > news >正文

计算机视觉(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 = 0best_model = Noneprint("\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 = accuracybest_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, scalerdef 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("无法打开摄像头")returnprint("开始实时表情识别(按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'):breakcap.release()cv2.destroyAllWindows()if __name__ == "__main__":# 训练模型(如果还没有训练)if not os.path.isfile('best_expression_model.pkl'):train_expression_model()# 进行实时表情识别realtime_expression_recognition()

http://www.dtcms.com/a/490282.html

相关文章:

  • C++设计模式_行为型模式_观察者模式Observer(发布-订阅(Publish-Subscribe))
  • 怎么解决ModuleNotFoundError: No module named ‘Cython‘
  • 电子商务网站规划流程手机主题制作网站
  • 微信小程序 Button 组件 open-type 完全指南:从用户信息获取到客服分享的实战应用
  • 阿里云白皮书-架构
  • Rokid 开发空间小程序 实战
  • RAG系统搭建指南:5种主流框架的易用性和效果对比
  • 网站后台怎么添加代码哪里有免费的域名注册建网站
  • Jetson AGX Orin+GMSL+AI视觉开发套件,支持自动驾驶,机器人,工业视觉等应用
  • 【JETSON+FPGA+GMSL+AI】自动驾驶与移动机器人的摄像头如何实现高精度时间同步?
  • Java 设计模式——建造者模式:从原理到实战的极简指南
  • 怎么找做企业网站的雁塔区住房和城乡建设局网站
  • 哈尔滨电商企业服务器托管方案
  • 机器学习基础入门(第五篇):半监督学习与强化学习
  • 建网站解决方案代运营网店公司
  • 网站建设网页制作软件wordpress 移动到回收站发生错误
  • 5G安全深入解析:EAP-AKA、EAP-AKA‘与5G-AKA详解
  • YOLO 系列演进:从 V1 到 V2 的目标检测革命
  • 云栖实录|MaxCompute全新升级:AI时代的原生数据仓库
  • 基于Multi-Agent开发的SmartCare系统自动化运维管家
  • 终结浏览器隐患:自动化安全审计与报表
  • 笔记本卡顿,拖慢的不止是CAD看图评审速度
  • 电脑被捆绑软件缠上?3 步根治:紧急断网 + 深度清理 + 长效防护
  • 网站专业优化商城购物网站开发背景
  • 哪家做外贸网站好沈阳高端网页
  • GPU服务器深度解析:H100/H200的“机头”与“模组”架构详解
  • javaWeb-html、css-常见标签样式
  • 开发组件漏洞
  • 每日学习内容简单汇总记录
  • vue3 封装图片上传预览组件支持docx、excel、pdf、图片、txt格式