深度学习面部表情识别项目实战:CNN/ResNet与Fer2013数据链路解析
简介面向计算机相关专业毕业设计的高分面部表情识别系统完整项目包涵盖Python源码、毕业论文、数据集与答辩PPT配套齐全可直接用于课设与毕设展示。基于深度学习框架实现CNN、VGG、ResNet等多种主流模型及对比实验脚本并提供数据预处理、表情映射、实时视频识别演示等辅助工具覆盖从数据划分到模型部署的完整流程。压缩包共36个文件以py源码、ipynb交互式分析笔记、docx论文文档、zip数据集、pptx答辩材料为主附带模型权重pkl和演示视频整体大小446MB目录结构清晰便于按需查阅。目前已有276人学习使用。整套资料评审分98分并经导师指导认可既适合毕业设计参考也可用于课程设计、期末大作业及深度学习实战练习帮助学习者快速搭建完整的人脸表情识别系统。1. 这个面部表情识别项目先看数据还是先跑模型拿到一套基于深度学习的面部表情识别系统源码我的习惯是先别急着敲命令。这个项目打包了 CNN、VGG、ResNet 三种模型用 Fer2013 数据集训练还配了论文、答辩 PPT 和演示视频典型的高分毕业设计结构。第一次跑通它你就能看到一条完整的“人脸检测 - 人脸裁剪 - 表情分类”链路。它特别适合两类人正在做计算机视觉方向毕设的学生以及想在深度学习项目里练手的人。数据、模型、Web 模板都是现成的改起来比从零搭要省力得多。2. 架构与数据链路从原始 CSV 到训练集的预处理管线很多同学拿到这套源码第一反应是直接跑 model_CNN.py结果大概率会在加载数据时报错。原因是这个项目的输入不是一堆图而是一个 CSV 文件。所以理解它的数据链路比理解模型更重要。2.1 目录结构与文件角色先看一眼根目录下的文件我把它们按职责分成了四类类别文件作用模型model_CNN.py / model_VGG.py / model_ResNet.py定义网络结构并直接作为训练入口测试对比model_CNN_test.py / model_VGG_test.py / model_ResNet_test.py / model_All_Compare.py加载模型做准确率评估和横向对比数据处理data_process.py / data_separation.py / data_view.py负责解析 CSV、划分数据集、可视化样本业务层haarcascade_frontalface_default.xml / templates / image_emotion_mapping.py人脸检测、Web 页面、情绪标签映射另外还有dataset/存放数据face_images/放测试用的人脸图手册.docx是作者写的运行说明论文和 PPT 放在根目录。这套结构的好处是训练、测试、部署三块互相独立你替换数据集或换模型时不用动整个工程。templates目录值得多说一句。文件列表里没有 app.py 这类 Flask 入口但存在 HTML 模板常见做法是作者用简易 Web 应用包装了识别接口。我一般会补一个 main.py 把模型封装成可调用的函数再挂到 Flask 路由上这样就能从浏览器上传图片看结果。2.2 数据管线从 CSV 像素串到 PyTorch DatasetFer2013 是 Kaggle 上经典的表情数据集有 35887 张 48x48 灰度图标签对应 7 类表情anger、disgust、fear、happy、sad、surprise、neutral。原始文件fer2013.csv的每一行大约长这样emotion,pixels,Usage 0,70 80 82 72 58 58 ...,Trainingpixels是 2304 个 0-255 的灰度值以空格分隔。Usage字段把样本分成 Training、PublicTest、PrivateTest 三部分。这个项目里的data_process.py干的就是解析这部分字符串并重构出图像矩阵的事。import numpy as np import pandas as pd def load_fer2013(csv_path): df pd.read_csv(csv_path) pixels df[pixels].tolist() emotions df[emotion].tolist() images [] for px in pixels: arr np.array(px.split(), dtypenp.float32) arr arr.reshape(48, 48) / 255.0 # 归一化到 [0,1] images.append(arr) return np.expand_dims(images, axis-1), np.array(emotions)这段代码把每一行的像素字符串按空格拆开转成 float32 数组再缩放到 48x48 的单通道图。np.expand_dims(..., axis-1)是为了给 PyTorch 的卷积层补上通道维得到(N, 48, 48, 1)的形状。这里有两个常见坑一是 CSV 里的像素值可能有缺失或多余空格用split()不指定分隔符可以自动跳过连续空格二是归一化要在 reshape 之前还是之后都行但一定要用 float32否则后续torch.tensor会隐式转换导致精度丢失。data_separation.py负责把数据按Usage列拆成 train / valid / test 三个集合而不是随机划分。这样你能直接拿来和论文里的准确率做对比因为验证集是官方固定的。data_view.py则把每个类别的样本分布画成条形图也能随机展示 9 张图用来确认预处理后的图像没有扭曲或标签错位。3. 核心模型拆解CNN、VGG 与 ResNet 的代码级实现这个项目最大的价值是同时提供了三种主流的深度模型实现。新手可以从最小的 CNN 学起做实验对比的人可以直接复用 VGG 和 ResNet。三者的训练脚本是同一套模板区别只在网络定义。3.1 基线 CNN一个 3 层卷积的轻量分类器model_CNN.py开头是标准的 PyTorch 结构定义。因为 Fer2013 图像只有 48x48 灰度图所以第一层的输入通道数是 1。这套网络设计得不算深但足够在表情识别上作为基线。import torch.nn as nn class EmotionCNN(nn.Module): def __init__(self, num_classes7, dropout0.5): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 6 * 6, 256), nn.ReLU(inplaceTrue), nn.Dropout(pdropout), nn.Linear(256, num_classes), )参数说明kernel_size3配合padding1保证卷积不改变特征图尺寸stride2的池化把 48x48 依次降到 24x24、12x12、6x6所以最后的展平维度是128 * 6 * 6。如果你自己改输入尺寸这里必须同步调。nn.Dropout(p0.5)是在全连接前随机丢弃一半神经元防止小数据集过拟合。逻辑上它就是标准的卷积提取特征 - 全连接分类。这个模型在 Fer2013 上能跑到大约 60%-63% 的准确率不要嫌低Fer2013 本身标签噪声很大人脸对齐也不严格60% 已经是不错的基线。3.2 VGG 与 ResNet迁移学习的两种写法model_VGG.py和model_ResNet.py没有从零实现 VGG 或 ResNet而是直接调用了torchvision.models里预训练好的模型。因为表情识别数据集远小于 ImageNet常见做法是复用 ImageNet 上预训练的参数只替换最后一层全连接。import torchvision.models as models import torch.nn as nn def build_resnet18(num_classes7): model models.resnet18(pretrainedTrue) in_features model.fc.in_features # 默认是 512 model.conv1 nn.Conv2d(1, 64, kernel_size7, stride2, padding3, biasFalse) model.fc nn.Linear(in_features, num_classes) return model这里有个关键改动预训练 ResNet 默认接受三通道 RGB 输入而 Fer2013 是单通道灰度图。直接喂会报形状错误所以需要把conv1的输入通道从 3 改成 1。需要注意的是这种改动会让第一层卷积的预训练权重失效因为通道数对不上。我一般会保留前三通道的平均值初始化为单通道权重或者干脆接受第一层随机初始化只让后面的层用预训练权重。VGG 的改法类似VGG16 的classifier[6]是最后一层 Linear把它替换成num_classes即可。由于 VGG 参数量大在 CPU 上训练会非常慢作者给出的代码里通常会在 ImageFolder 或 Dataset 加载时把图片convert(L)转成灰度再在预处理里归一化。3.3 训练、测试与模型对比三个模型的训练脚本共用一套训练循环核心就是交叉熵损失加 Adam 优化器。别看后面论文写了一大堆训练代码其实很标准import torch import torch.nn as nn from torch.utils.data import DataLoader def train_one_epoch(model, dataloader, optimizer, device): model.train() criterion nn.CrossEntropyLoss() total_loss, correct, total 0, 0, 0 for images, labels in dataloader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() preds outputs.argmax(dim1) correct (preds labels).sum().item() total labels.size(0) return total_loss / len(dataloader), correct / total这段代码没什么玄学值得注意的有两点一是outputs.argmax(dim1)取每个样本得分最高的类别作为预测dim1表示在类别维度上操作二是每个 epoch 结束后应该保存 state_dict否则训练中断就得重来。model_All_Compare.py会加载三个模型在 PrivateTest 集上跑一遍把准确率画成柱状图这也是答辩 PPT 里最常用的素材。如果你自己改模型记得把model_CNN_test.py里的加载路径从.pkl改成你实际保存的文件名。这个项目里模型权重存在model_resnet.pkl但 PyTorch 官方更推荐.pth避免和 pickle 序列化混淆。4. 运行与避坑让这套源码在自己的机器上稳定跑起来我见过太多人在这一步翻车明明代码没问题就是跑不起来。这项目涉及 CSV 解析、模型权重、人脸检测三个环节任何一个出错都会让你觉得是玄学。下面按实际操作顺序写。4.1 环境配置与训练启动建议用 Python 3.8 或 3.10装好以下依赖pip install torch torchvision opencv-python numpy pandas matplotlib flask如果你想用 GPU 训练需要根据你的 CUDA 版本去 PyTorch 官网装对应版本否则torch.device(cuda)会直接报错。训练入口很简单python data_separation.py # 先把 CSV 拆分成 train.csv / val.csv / test.csv python model_CNN.py # 开始训练 CNNdata_separation.py会生成新的 CSV 或样本文件后面的 Dataset 读取的就是这些结果不是原始fer2013.csv。这也是很多人跳过预处理直接跑模型报“找不到图片”的原因。如果项目里已经给出了拆好的数据可以跳过这一步但最好还是自己跑一遍确认数据路径正确。model_CNN.py在训练结束后会把模型保存到model目录。如果看到FileNotFoundError: model那就先手动mkdir model。这是最基础但最常见的坑。4.2 图片与视频识别流程训练好模型后最终要落到“给一张人脸照片返回表情”。这里用的是 OpenCV 的 Haar 级联检测器把检测到的人脸区域缩放成 48x48 输入模型。import cv2 def predict_emotion(image_path, model, emotion_dict): img cv2.imread(image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) face_cascade cv2.CascadeClassifier(haarcascade_frontalface_default.xml) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(48, 48)) for (x, y, w, h) in faces: face gray[y:yh, x:xw] face cv2.resize(face, (48, 48)) face face.reshape(1, 1, 48, 48).astype(float32) / 255.0 # model 输出 tensor取 argmax return emotionsscaleFactor1.1表示每次搜索窗口扩大 10%minNeighbors5控制误检率——值越大人脸框越少但也越准。这里把 BGR 转成灰度是必须的因为模型在灰度图上训练。如果你想直接跑视频视频流里的每一帧都执行同样的检测demo 文件里那个example_dsh.mp4就是自带测试素材。4.3 常见问题排查避坑下面这五条是我实际踩过或看别人踩过的坑每个都按现象、“原因”、“解决”的顺序写建议收藏。现象一训练时loss不降准确率一直在 20% 以下。原因标签和 Channels 顺序错了比如灰度图被当成三通道读了或者数据没有归一化。解决打印一个 batch 的images.shape和labels.unique()确认是(B, 1, 48, 48)、标签范围 0-6。现象二加载验证集时报IndexError: index out of range。原因你的data_separation.py划分后训练集标签类别数少于 7比如某个类别在划分后没样本。解决检查pd.value_counts(df[emotion])确保每个类别都有样本或用分层采样。现象三torch.load报ModuleNotFoundError: model_CNN。原因你直接加载.pkl时pickle 需要用同一类结构。解决在加载脚本前先把model_CNN.py里的类 import 进来或用torch.load(weights_path, map_locationcpu)并先构建好 model 再load_state_dict。现象四CPU 训练 VGG 慢到怀疑人生。原因VGG16 参数量巨大在 CPU 上跑 30 个 epoch 要几个小时。解决先用 CNN 或 ResNet18 调试通流程最后再跑 VGG 出对比数据训练时降低图片分辨率或改用batch_size32。现象五OpenCV 检测不到侧面人脸或戴口罩人脸。原因Haar 级联的本质是弱分类器堆叠对姿态和遮挡非常敏感。解决把minNeighbors调低到 2-3 提高召回率或者在识别前用dlib的 68 点人脸检测替代但这需要额外装dlib并处理模型文件。5. 二次开发思路从毕设 Demo 到可用的表情识别服务这套项目跑通只是第一步如果你想把它写进简历或者让导师觉得工作量超纲下面这几个方向都很容易加。5.1 换更现代的数据集Fer2013 虽然经典但类别不平衡且噪声大。你可以把data_process.py替换成 RAF-DB 或 AffectNet 的数据接口。核心改动只有 Dataset 的__getitem__——原来是从 CSV 读像素串现在是从图片文件夹读文件并返回标签。模型代码完全不用动这正好验证了项目分层是否合理。5.2 加入摄像头实时识别把 4.2 的预测函数包一层用 OpenCV 打开摄像头cap cv2.VideoCapture(0) while cap.isOpened(): ret, frame cap.read() emotion predict_emotion_frame(frame, model, face_cascade) cv2.putText(frame, emotion, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2) cv2.imshow(emotion, frame)在视频流里你需要把emotion_dict映射成可读字符串例如{0:angry, 1:disgust, ...}。如果想提高流畅度每 5 帧做一次人脸检测中间几帧直接复用上一次的脸部区域省掉多余计算。5.3 我的个人习惯事实上我拿到这套代码后第一件做的事不是去增加新模型而是删掉Dropout跑 10 个 batch 看模型能不能过拟合到 100%。如果连几个样本都记不住说明网络结构或数据加载有问题后面调参全是白费。还有每次训练前我都会在同一个文件里写好torch.manual_seed(0)否则两个模型的对比结果会因为随机初始化不公平答辩时经不起问。从那以后我每次打开一个开源的深度学习项目都会先做一次“最小过拟合测试”再决定要不要扩大模型规模。久病成医这样能筛掉一大半环境问题。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取