2026/10/19 11:13:37

基于深度学习的七类面部表情识别项目:CNN/VGG/ResNet全实现

基于深度学习的七类面部表情识别项目:CNN/VGG/ResNet全实现 简介一份面向计算机相关专业学生与初学者的深度学习面部表情识别系统毕业设计资源整合完整源码、学术论文、数据集和答辩PPT适用于大作业、课程设计或毕业设计参考。压缩包共36个文件以Python脚本14个py、Jupyter Notebook5个ipynb、Word/PDF文档docx/doc/pdf、答辩演示pptx及实操视频mp4为主并包含CNN、VGG、ResNet三种模型实现代码、训练好的pkl模型与haar级联分类器整体约446MB目录结构清晰便于按文档、代码、数据分模块查阅。项目来源于经导师指导并获评审98分的高分毕设覆盖数据预处理、模型训练、多模型效果对比、实时界面演示直至论文撰写与答辩展示的完整链路。代码附详细注释和部署说明配套使用手册可辅助理解调参与优化思路数据集、标注工具、数据划分与情绪映射脚本均可直接用于实验复现和二次开发。已有276人学习下载适合需要完整实战项目、快速搭建表情识别系统或借鉴高质量毕设范式的学习者尤其适合答辩前补充演示素材与项目亮点。1. 深度学习面部表情识别项目为什么说它是毕设模板级代码做毕业设计最怕的不是模型跑不出来而是论文写完了、PPT 做完了代码却经不起老师打开看一眼。这套基于深度学习的面部表情识别系统最大的价值不在“识别率多高”而在它把 CNN、VGG、ResNet 三条技术路线全部实现了一遍并且每个模型都配了训练、测试、对比脚本。你不需要自己攒代码拼图直接把项目跑通再挑一条路线往深里讲论文和答辩的素材就都有了。项目以 FER2013 这类公开表情数据集为基准把面部表情分成生气、厌恶、恐惧、开心、悲伤、惊讶、中性七类完整覆盖数据切分、模型训练、精度评估、图片与视频推理、实时摄像头识别整条链路。适合正在做计算机类大作业、课程设计或毕业设计的学生也适合想快速上手图像分类项目的学习者。数据、论文、答辩 PPT、部署说明一应俱全是一份能直接对照复现的高完成度工程而不是只有一个 ipynb 的玩具 demo。2. 项目全景从文件清单看清这套毕设的分层设计拿到压缩包先别急着跑训练花十分钟把文件分类过一遍你就能发现这套项目的结构和大多数高分毕设完全一致训练代码、测试代码、工具脚本、模型权重、论文文档、答辩 PPT 分层存放互不干扰。2.1 核心代码与模型层项目的 Python 文件命名非常直白按模型类型和用途分成两组。训练入口是model_CNN.py、model_VGG.py、model_ResNet.py对应三个独立模型定义与训练流程测试入口是model_CNN_test.py、model_VGG_test.py、model_ResNet_test.py负责加载权重并对图片或视频做推理。model_CNN.py # CNN 模型定义 训练 model_VGG.py # VGG 迁移学习模型定义 训练 model_ResNet.py # ResNet 残差网络定义 训练 model_CNN_test.py # CNN 模型推理测试 model_VGG_test.py # VGG 模型推理测试 model_ResNet_test.py # ResNet 模型推理测试 model_All_Compare.py # 三个模型精度对比与可视化此外还有ResNet.py和VGG_model_template.py、ResNet_model_template.py这类模板文件以及CNN_01.ipynb、CNN_02.ipynb、CNN_03.ipynb、ResNet.ipynb、Facial-expressions.ipynb等 Jupyter Notebook 实验记录。Notebook 是探索过程py 脚本是最终成果这个区分值得你在二次开发时沿用。2.2 数据集与预处理工具层dataset目录存放原始表情图片face_images目录存放经人脸检测截取后的面部区域图。data_process.py实现图像尺寸统一、灰度转换、数据增强等预处理逻辑data_separation.py负责把数据集按比例切分为训练集、验证集和测试集data_view.py用来快速浏览某个批次的数据。dataset/ # 原始表情数据集 face_images/ # 人脸区域截取图 data_process.py # 图像预处理缩放、灰度、增强 data_separation.py # 训练/验证/测试集切分 data_view.py # 数据集可视化浏览 image_emotion_mapping.py # 表情标签映射关系其中image_emotion_mapping.py是整个数据链路的“字典”它维护表情类别索引和标签名的映射关系。常见做法是把七个类别映射为数字标签比如0 到 6分别对应 angry、disgust、fear、happy、sad、surprise、neutral模型最后输出一个 7 维概率向量取 argmax 得到预测结果。2.3 模型权重、人脸检测与附加资源model目录和【人脸面部表情识别项目】模型文件.zip中存放训练好的权重文件比如model_resnet.pkl就是 ResNet 模型的序列化权重。haarcascade_frontalface_default.xml是 OpenCV 自带的 Haar 级联人脸检测器用于视频和摄像头场景中先框出人脸再交由表情模型分类。video目录里的example_dsh.mp4是演示视频templates目录有 Web 页面模板暗示项目可以扩展成带界面的人脸表情识别服务。model/ # 训练好的模型权重 【人脸面部表情识别项目】模型文件.zip # 权重打包备份 model_resnet.pkl # ResNet 序列化权重 haarcascade_frontalface_default.xml # Haar 人脸检测器 templates/ # Web 页面模板 video/example_dsh.mp4 # 演示视频文档层包含论文 docx/pdf、答辩 PPT、使用手册手册.docx和README.md。整个项目结构呈现“数据进、模型出、文档兜底”的分层设计底层数据、中间训练、上层推理、外围论文答辩材料每一层可以独立替换这也是它作为毕设模板最值得学习的地方。3. 核心代码拆解一条数据从像素到表情标签的完整链路这一章把项目最关键的数据处理、模型训练、多模型对比三块代码抽出来逐段讲。你不用急着复制整段代码重点是理解每个文件在整条链路上的位置以及哪些地方是改一个参数就能影响整个训练效果的“开关”。3.1 表情映射与数据预处理从文件路径到张量先看image_emotion_mapping.py。表情识别本质上是一个 7 分类图像问题训练前必须把“文件夹名”和“模型输出数字”建立稳定的对应关系否则训练集和测试集标签编号错位模型精度会直接从 90% 掉到 30%。import os # 表情类别映射表目录名 - 数字标签 - 类别名 EMOTION_MAP { angry: 0, disgust: 1, fear: 2, happy: 3, sad: 4, surprise: 5, neutral: 6 } def get_emotion_label_by_path(img_path): 根据图片所在目录名推断表情标签 dir_name os.path.basename(os.path.dirname(img_path)) return EMOTION_MAP.get(dir_name, -1)这个模块的逻辑就是一次字典查询但它的重要性体现在整个项目都用它来统一标签。你在二次开发时如果引入了新的表情类别或自定义数据集只需要改EMOTION_MAP这一处字典所有训练和脚本脚本的标签行为会同步变化。再看data_process.py的预处理部分。FER2013 数据集原始图像是 48x48 的灰度图而本地dataset目录下可能会有不同尺寸的彩色图统一尺寸是训练的前提import cv2 import numpy as np IMG_SIZE 48 def load_grayscale_image(img_path): 读取图片并转为 48x48 灰度图适配 FER2013 输入尺寸 img cv2.imread(img_path) if img is None: raise FileNotFoundError(f图片读取失败: {img_path}) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, (IMG_SIZE, IMG_SIZE)) / 255.0 return gray这里把像素值归一化到 0~1 之间可以显著加快模型收敛速度。如果你想让模型更鲁棒常见做法是在这里加入随机旋转、水平翻转等数据增强操作比如cv2.flip(gray, 1)做随机水平镜像。这套项目默认不做复杂增强是为了保证公平对比三个模型的基线精度这一点在论文里交代得比较清楚。3.2 CNN 基础模型手动搭一个表情分类网络model_CNN.py里定义了一个轻量级卷积网络适合 CPU 快速跑通全流程。它由三个卷积块加全连接层组成每个卷积块包含“卷积 批归一化 ReLU 最大池化”四件套。import torch import torch.nn as nn class EmotionCNN(nn.Module): def __init__(self, num_classes7): super(EmotionCNN, self).__init__() self.conv1 nn.Conv2d(1, 32, kernel_size3, padding1) self.bn1 nn.BatchNorm2d(32) self.conv2 nn.Conv2d(32, 64, kernel_size3, padding1) self.bn2 nn.BatchNorm2d(64) self.conv3 nn.Conv2d(64, 128, kernel_size3, padding1) self.bn3 nn.BatchNorm2d(128) self.pool nn.MaxPool2d(2, 2) self.fc1 nn.Linear(128 * 6 * 6, 512) self.fc2 nn.Linear(512, num_classes) self.relu nn.ReLU() def forward(self, x): x self.pool(self.relu(self.bn1(self.conv1(x)))) x self.pool(self.relu(self.bn2(self.conv2(x)))) x self.pool(self.relu(self.bn3(self.conv3(x)))) x x.view(x.size(0), -1) x self.relu(self.fc1(x)) x self.fc2(x) return x输入是单通道 48x48 灰度图经过三次池化后特征图尺寸从 48 降到 6x6展平后喂给全连接层。self.pool每次都把尺寸减半所以最终 48 - 24 - 12 - 6。这个网络很小显存占用不到 1GB入门选手用 CPU 也能完成一个 epoch 的训练。模型末层输出 7 维 logits配合交叉熵损失函数nn.CrossEntropyLoss()完成端到端训练。3.3 VGG 与 ResNet用迁移学习把精度往上推model_VGG.py和model_ResNet.py走的是迁移学习路线。VGG 直接加载在 ImageNet 上预训练好的权重把最后的全连接分类头替换成 7 类输出冻结前面的卷积层只微调最后一层ResNet 则利用残差连接解决深层网络梯度消失的问题。import torchvision.models as models import torch.nn as nn def build_vgg_emotion_model(num_classes7): 加载预训练 VGG16替换分类头为 7 类 model models.vgg16(pretrainedTrue) # 冻结特征提取层参数只训练分类头 for param in model.features.parameters(): param.requires_grad False # VGG16 分类头原本是 1000 类输出这里替换 num_features model.classifier[6].in_features model.classifier[6] nn.Linear(num_features, num_classes) return modelVGG 的优点是在小型数据集上不容易过拟合因为大部分卷积层权重被冻结缺点是模型体积大、推理速度慢。ResNet 在项目里提供了ResNet.py和model_ResNet.py两个版本前者是网络结构复现后者是训练入口。加载model_resnet.pkl权重时要注意模型定义必须和训练时完全一致否则反序列化会报结构不匹配的错误。3.4 多模型对比毕设论文里最有说服力的那张图model_All_Compare.py是这个项目的论文助攻脚本它把 CNN、VGG、ResNet 三个模型在测试集上的准确率画成对比图并输出每一类的精确率、召回率、F1 分数。import matplotlib.pyplot as plt def compare_models(model_names, accuracies): 绘制三个模型的测试集准确率对比柱状图 plt.bar(model_names, accuracies, color[#1f77b4, #ff7f0e, #2ca02c]) for i, acc in enumerate(accuracies): plt.text(i, acc 0.01, f{acc:.4f}, hacenter) plt.ylim(0, 1.0) plt.ylabel(Test Accuracy) plt.title(CNN vs VGG vs ResNet) plt.savefig(model_compare.png, dpi300) plt.close()很多毕设论文里的模型对比柱形图就是这么来的。你只需要在表格或代码里填入三行准确率这张图就能放进论文实验章节。注意比较时要在同一数据集、同一种切分方式下进行论文里要把数据增强方式和训练轮数写清楚否则评审老师会质疑对比的公平性。3.5 数据切分与批量读取让训练脚本真正跑起来data_separation.py的作用是把dataset下每个表情类别的图片按比例切分为训练、验证、测试三份。常见比例是train:val:test 8:1:1。import random import os import shutil RATIO (0.8, 0.1, 0.1) def split_dataset(root_dir, output_dir): 按 8:1:1 切分数据集到 train/val/test 三个目录 for emotion in os.listdir(root_dir): emotion_dir os.path.join(root_dir, emotion) if not os.path.isdir(emotion_dir): continue images os.listdir(emotion_dir) random.shuffle(images) train_cnt int(len(images) * RATIO[0]) val_cnt int(len(images) * RATIO[1]) # 按序号写入不同子目录 for idx, img_name in enumerate(images): if idx train_cnt: target os.path.join(output_dir, train, emotion) elif idx train_cnt val_cnt: target os.path.join(output_dir, val, emotion) else: target os.path.join(output_dir, test, emotion) os.makedirs(target, exist_okTrue) shutil.copy(os.path.join(emotion_dir, img_name), target)切分脚本会覆盖原目录结构生成train/emotion、val/emotion、test/emotion三层目录后续训练脚本就可以用torchvision.datasets.ImageFolder直接按目录名自动生成标签。整个链路从原始图片到最终模型权重每一步的输入输出都非常清晰。4. 部署与运行从依赖安装到摄像头实时表情识别讲完核心代码接下来照着操作把项目跑起来。重点说明环境怎么配、训练命令怎么执行、测试和视频识别怎么调以及常见参数应该怎么修改。4.1 环境准备与依赖安装项目基于 PyTorch 构建核心依赖包括 PyTorch、Torchvision、OpenCV、NumPy、Matplotlib。训练脚本本身不依赖显卡纯 CPU 也能跑通小轮数但真要训练到 60 个 epoch 甚至更高强烈建议用 GPU。PyTorch 安装命令建议走官网选择对应 CUDA 版本不要用默认的 CPU 版。pip install torch torchvision opencv-python numpy matplotlib scikit-learn安装完成后建议在 Python 里验证一下 OpenCV 和 Torch 是否正常import torch, cv2 print(torch:, torch.__version__) print(opencv:, cv2.__version__) print(cuda available:, torch.cuda.is_available())如果cuda available返回 False 但你的电脑有显卡说明 PyTorch 装成了 CPU 版需要重新安装对应 CUDA 轮子。另外提醒一句OpenCV 版本和 Python 版本有对应关系Python 3.11 及以上装旧版 opencv-python 可能没有预编译轮子直接pip install opencv-python装最新版最稳妥。4.2 数据准备与训练模型把dataset目录按项目原有结构放好先跑data_separation.py切分数据然后执行训练脚本。python data_separation.py python model_CNN.py以model_CNN.py为例训练入口通常内置了参数配置区包含batch_size、learning_rate、epochs等关键超参数。一个常见配置如下BATCH_SIZE 64 LEARNING_RATE 1e-3 EPOCHS 50batch_size影响单次前向传播的数据量和显存占用过大数据集会内存溢出过小导致训练震荡学习率建议从1e-3开始如果 loss 在波动不下降降到1e-4再试epoch 数在 Fer2013 这类中规模数据集上 50 轮已经能收敛到平台期。训练结束后脚本会自动把最佳权重保存为.pkl文件到model目录。4.3 单张图片与视频推理训练完毕后用对应的_test.py脚本验证单张图片效果。入口参数一般是图片路径脚本会先做人脸检测框出面部然后把面部区域送入模型输出表情类别和置信度。python model_CNN_test.py --image test_face.jpg如果要对视频做识别项目使用haarcascade_frontalface_default.xml完成人脸框取再逐帧送入模型。视频推理脚本的伪代码如下import cv2 face_cascade cv2.CascadeClassifier(haarcascade_frontalface_default.xml) model load_model(model_resnet.pkl) cap cv2.VideoCapture(video/example_dsh.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.3, 5) for (x, y, w, h) in faces: face_roi gray[y:yh, x:xw] face_resized cv2.resize(face_roi, (48, 48)) / 255.0 pred model.predict_emotion(face_resized) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, pred, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(Emotion Recognition, frame) if cv2.waitKey(1) 0xFF ord(q): breakdetectMultiScale的三个核心参数scaleFactor1.3、minNeighbors5、minSize分别控制检测窗口缩放步长、误检阈值和最小人脸尺寸。把scaleFactor调小到 1.1 会检测得更精细但速度变慢minNeighbors调大会减少误检但也会漏检小尺寸人脸。把摄像头设备号从 0 开始传入就能从演示视频无缝切到摄像头实时识别。5. 踩坑与排查训练翻车、模型加载失败、环境冲突的血泪经验这套项目我在 CPU 和 GPU 两种环境各复现过一遍前前后后踩过不少坑。下面把最影响跑通的五条按“现象 - 原因 - 解决”的方式记录下来你照着排查能省不少时间。5.1 加载 model_resnet.pkl 报错模型结构不匹配跑测试脚本时直接报错size mismatch for fc2.weight: copying a param with shape torch.Size([7, 512]) from checkpoint, the shape in current model is torch.Size([7, 512])或者更常见的KeyError: model。原因是权重文件的模型结构与当前定义不一致。比如训练时用的ResNet.py里的 ResNet18测试时却用model_ResNet.py重新定义了一个带不同全连接层的网络两边num_classes也不统一。解决办法是确认权重由哪个脚本产生就调用哪个脚本加载。model_resnet.pkl对应model_ResNet.py里的模型定义加载前先torch.load看一眼里面的键名有些版本保存的是整个模型对象torch.save(model, path)有些只保存state_dict。这两种情况加载方式完全不同前者直接用torch.load还原模型后者需要先构造模型实例再load_state_dict。5.2 训练 loss 不下降精度始终在 10%~15% 徘徊刚启动训练时 loss 下降到1.8左右后就不再明显变化跑了 20 个 epoch 精度依然在 10% 上下。这个精度接近随机猜测水平明显有问题。最常见原因是对标签和模型输出维度理解错了模型输出的是 7 维 logitsloss 用CrossEntropyLoss没有错但如果数据加载时没有做归一化像素值范围是 0~255 而网络期望 0~1会导致梯度更新方向混乱。另一个高频原因是没打乱数据训练集和验证集分布高度一致验证集永远约等于训练集看起来精度“很高”但没有泛化意义。解决方法是先跑一个几十张图片的小数据集测试确认 loss 能从初始值明显下降数据加载器设置shuffleTrue图像预处理除以 255 归一化。对学习率过大引起的 loss 震荡把learning_rate从1e-3降到1e-4再观察一轮。5.3 OpenCV 读视频失败frame 始终为 Nonecv2.VideoCapture(video/example_dsh.mp4)循环读出全黑帧cap.isOpened()返回 True 但第一帧就是 None。原因是视频编码格式与当前 OpenCV 版本不兼容本地缺少对应的解码器。常见解决办法是用pip install opencv-python升级版本如果还不行就换用opencv-contrib-python包它带更多扩展解码能力。也可以先转码一份 MP4用ffmpeg -i example_dsh.mp4 -vcodec mpeg4 out.mp4转成更通用的编码再喂给 OpenCV。另外要注意cv2.VideoCapture的路径不能带中文OpenCV 底层是按 C 风格处理路径的中文目录名大概率打不开文件。把整个项目放在纯英文字符路径下运行能少踩一圈坑。5.4 批量读取图片时内存直接溢出batch_size设置成 128 训练到第二个 epoch 时内存占用飙升进程被杀。这是因为本地face_images目录里的图片是彩色图读入后如果不统一转灰度数据张量尺寸会是[128, 3, 48, 48]约等于同尺寸灰度图的三倍显存。解决办法是确认data_process.py里已经做了cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)灰度转换并且输入通道数in_channels1卷积层第一层也要对应改为nn.Conv2d(1, 32, kernel_size3, padding1)。如果确实需要彩色信息就把IMG_SIZE调到 64 并同步调整全连接层输入维度。通用的经验是先用小 batch 比如 16 试跑再用torch.cuda.max_memory_allocated()观测显存峰值据此选择合适的 batch_size。5.5 三个模型对比脚本报错无法画图model_All_Compare.py跑到最后一张图时报findfont: Font family SimHei not found图上中文全部显示成方块。这是 Matplotlib 在英文字体环境下缺失中文字体导致。最简单的解决方式是给 Matplotlib 指定系统已有的中文字体import matplotlib import matplotlib.pyplot as plt matplotlib.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, Arial Unicode MS] matplotlib.rcParams[axes.unicode_minus] False绘图脚本里强制指定一种你系统上确实安装了的中文字体再重新运行。另外如果你的对比图保存前没有plt.close()连续画多张图会叠加坐标轴导致图像异常这也是常见翻车点。6. 二次开发从毕设项目到可演示系统的四条改造路径如果你不满足于交一份毕设想把这套代码改造成能写在简历里的项目我建议按以下四条路径选一条深入。先说结论把这套代码从“训练跑通”升级为“系统可用”最大的增量不是模型精度而是工程化能力。第一条路径是 Web 化。项目已有templates目录说明原始作者大概率是用 Flask 或 Django 搭建过页面的。把model_ResNet_test.py的推理逻辑封装成一个函数再在 Flask 路由里接收上传图片、调用模型、返回结果 JSON一个在线表情识别服务就能演示。界面不需要复杂一个上传框、一个识别结果区域、一张置信度条形图已经足够拿得出手。第二条路径是数据扩充与重训。Fer2013 数据集存在明显的类别不均衡问题disgust 类样本远少于 happy 类。用 PyTorch 的WeightedRandomSampler给少数类更高采样概率或者用torchvision.transforms做随机亮度、对比度调整来增强数据多样性。重训后对比旧模型的混淆矩阵你会发现 difficult 类的召回率提升非常明显。第三条路径是实时摄像头人脸表情识别系统的产品化。现有脚本是循环读帧 对每张人脸逐一推理多人场景下性能会大幅下降。这里可以使用 OpenCV 的cv2.dnn模块加载一个轻量级人脸检测模型或者用一个独立的检测线程只对关键帧做人脸检测其他帧直接沿用上一帧的人脸位置可以显著降低 CPU 占用。再把推理结果做成平滑的滑动窗口平均表情不会一帧一变导致像抽风一样乱跳。第四条路径是模型轻量化部署。ResNet 在这三个模型里精度最高但放到树莓派或 Jetson 上跑视频推理帧率不理想。可以对训练好的模型做量化感知训练比如把精度从 float32 降到 float16 再导出 ONNX 格式推理速度能提升两倍以上。量化后的模型文件更小Web 部署时加载也快。我自己做这套项目的二次改造时最后只保留了 ResNet 这一条模型线其余两个模型留着做对比参考。从那以后我每次拿到一份毕设代码都会强制先跑通最小闭环——加载预训练权重 单张图片推理成功再动任何结构否则改到一半分不清是代码 bug 还是环境问题。希望帮到你。本文还有配套的精品资源点击获取