基于CNN的人脸表情识别系统:从期末大作业到摄像头实时推理的完整实现

发布时间:2026/10/12 21:34:06
基于CNN的人脸表情识别系统:从期末大作业到摄像头实时推理的完整实现 简介这份资源是面向计算机相关专业学生的Python期末大作业完整源码包主题为基于CNN的人脸表情识别系统适合正在准备课程设计、需要项目实战练习的学习者参考使用。项目经导师指导并通过评审难度适中源码均经过本地编译与严格调试可正常运行。压缩包共23个文件约19.17MB以10个Python脚本为核心涵盖数据读取与写入、模型定义、训练与识别等模块另含Keras训练脚本、tfrecord数据文件、界面渲染页面及说明文档结构清晰便于按模块学习。目前已有144人学习下载。读者可从中获得一套完整的人脸表情识别实现方案理解CNN模型构建、数据预处理、训练流程与摄像头及图片识别等关键环节并借助README快速上手适合作为大作业参考或深度学习入门实战案例。1. 从一份期末大作业说起CNN 人脸表情识别到底能跑出什么效果每年期末季总有人拿着「Python期末大作业基于CNN的人脸表情识别系统源码.zip」这个标题来找我问的无非两件事这东西能不能直接跑起来以及答辩的时候老师会问什么。先说结论——能跑而且只要环境装对从解压到看到摄像头里自己咧嘴笑被识别成「happy」快的话一个下午就够。但「能跑」和「跑得对」之间隔着一条不小的沟这条沟里埋着数据集格式、OpenCV 读帧、模型输入尺寸、标签映射这几颗雷踩中任何一颗你看到的都是全程「neutral」或者干脆报错退出。人脸表情识别Facial Expression RecognitionFER本质是一个图像分类任务给一张人脸图判断它属于哪一类情绪。主流数据集一般分 7 类——angry、disgust、fear、happy、sad、surprise、neutral。CNN 在这里干的事就是从像素里一层层抽出边缘、纹理、五官形变这些特征最后映射到情绪标签上。它不像人脸识别那样认「你是谁」而是认「你现在什么状态」所以对光照、角度、遮挡都更敏感这也是为什么同一份源码在不同机器上表现能差出一大截。这份大作业适合谁如果你正在做课程设计、想找一个能讲清楚原理又能现场演示的题目它很合适如果你已经会写 PyTorch 训练循环想拿它当练手项目补一补数据增强和推理部署的细节也够用。但如果你指望它直接商用、或者拿它去跑真实监控场景那得先做好心理准备——实验室数据集和真实场景之间的差距比想象中大得多。下面我按「先立住原理、再动手复现、最后讲坑」的顺序把这条路走一遍。2. 拆开这份源码CNN 表情识别的数据流与模型选型2.1 一份典型源码里到底有哪几块拿到压缩包解压后目录结构通常长这样一个data或dataset文件夹放图片一个train.py负责训练一个model.py定义网络一个predict.py或camera.py做推理外加requirements.txt和几个.pth权重文件。别急着python train.py先花五分钟把每个文件扫一遍重点看三处数据是怎么读的、模型输入尺寸是多少、标签顺序是什么。这三处对不上后面全是白忙。数据读取这块常见做法是用torchvision.datasets.ImageFolder它要求目录按类别分好比如data/train/happy/xxx.jpg。如果你的数据是 CSV 里存路径和标签那就得自己写Dataset子类。模型输入尺寸一般在model.py或者train.py的transforms里写死常见是 48×48 灰度图FER2013 的风格或者 224×224 彩色图迁移学习的风格。标签顺序更隐蔽有的源码按字母序有的按自定义列表推理时如果顺序错了happy 会被认成 angry这种 bug 最难查因为程序不报错只是结果全乱。2.2 为什么是 CNN而不是全连接或者传统特征有人会问表情识别用 HOG 加 SVM 不也能做吗为什么非要上 CNN。答案是传统方法依赖人工设计特征比如眼睛睁多大、嘴角上扬多少度这些特征在正面清晰图上还行一旦头部有偏转、光照变化鲁棒性就崩了。CNN 的卷积核是学出来的浅层学边缘和纹理深层学五官组合和整体形变对空间平移有一定不变性这正是表情识别需要的。具体到网络结构这份大作业级别的源码一般不会上 ResNet 那么重常见的是一个 4 到 6 层的自定义 CNN两三层卷积加池化后面接两三个全连接层最后 softmax 输出 7 类。这种结构参数量小在 FER2013 这种 3 万多张图的数据集上CPU 也能在几十分钟内跑完一轮。如果你想提精度可以换成预训练的 ResNet18 或 MobileNetV2把最后一层改成 7 输出冻结前面几层先训分类头再解冻微调这是常见做法精度通常能比自定义 CNN 高几个百分点。2.3 数据增强小数据集能不能训好的关键FER2013 这类数据集有个特点类别不平衡happy 和 neutral 的样本远多于 disgust 和 fear。直接训模型会偏向多数类disgust 几乎认不出来。解决办法有两个一是加WeightedRandomSampler给少数类加权二是在transforms里做增强比如随机水平翻转、随机旋转 ±10 度、颜色抖动。注意水平翻转对表情识别是安全的因为左右脸对称但垂直翻转绝对不能用倒过来的脸不是脸。from torchvision import transforms train_transform transforms.Compose([ transforms.Grayscale(num_output_channels1), # FER2013 是灰度图统一通道 transforms.Resize((48, 48)), # 和模型输入对齐别改错 transforms.RandomHorizontalFlip(p0.5), # 水平翻转表情识别安全 transforms.RandomRotation(10), # 小角度旋转模拟头部微偏 transforms.ColorJitter(brightness0.2, contrast0.2), # 光照鲁棒性 transforms.ToTensor(), transforms.Normalize(mean[0.5], std[0.5]) # 灰度图单通道归一化 ]) val_transform transforms.Compose([ transforms.Grayscale(num_output_channels1), transforms.Resize((48, 48)), transforms.ToTensor(), transforms.Normalize(mean[0.5], std[0.5]) ])这段代码的关键在Resize和Normalize两处。Resize的尺寸必须和模型第一层卷积期望的输入一致如果模型是按 48×48 写的你喂 224×224 进去全连接层的维度就对不上报错信息通常是mat1 and mat2 shapes cannot be multiplied。Normalize的 mean 和 std 要和训练时一致推理时如果忘了归一化或者用了不同的值预测结果会整体偏移。灰度图用单通道如果你用的是彩色数据集把Grayscale去掉Normalize改成三通道的均值方差。3. 从零跑通训练环境、数据、训练循环三步走3.1 环境安装与依赖版本对齐第一步永远是环境。Python 版本建议 3.8 到 3.10太新的版本有些老源码里的库装不上。核心依赖就四个torch、torchvision、opencv-python、numpy。安装命令如下pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install opencv-python numpy matplotlib如果你有 NVIDIA 显卡把第一行的 cpu 换成对应的 cuda 版本比如cu118。这里有个血泪经验torch 和 torchvision 版本必须匹配torch 2.0 配 torchvision 0.15torch 1.13 配 torchvision 0.14装错了会在import torchvision时直接报错。装完跑一句python -c import torch; print(torch.__version__, torch.cuda.is_available())确认输出False说明用的是 CPU训练会慢但能跑。3.2 数据准备ImageFolder 还是自定义 Dataset如果你的数据已经是按类别分文件夹的直接用ImageFolder最省事from torchvision.datasets import ImageFolder from torch.utils.data import DataLoader train_dataset ImageFolder(rootdata/train, transformtrain_transform) val_dataset ImageFolder(rootdata/val, transformval_transform) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue, num_workers2) val_loader DataLoader(val_dataset, batch_size64, shuffleFalse, num_workers2) print(train_dataset.classes) # 打印标签顺序务必记下来ImageFolder会按文件夹名的字母序生成classes列表这个顺序就是模型输出的索引顺序。比如输出[angry, disgust, fear, happy, neutral, sad, surprise]那模型输出第 3 个值最大就代表 happy。推理时如果自己另写了一个标签列表且顺序不同结果就全错。batch_size根据显存调CPU 训练建议 32 或 64太大内存扛不住。num_workers在 Windows 上有时会出问题设成 0 最稳。3.3 训练循环与关键超参训练循环是整份源码的核心看懂这一段答辩时老师问什么你都能接住import torch import torch.nn as nn import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model SimpleCNN(num_classes7).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5) for epoch in range(30): model.train() running_loss 0.0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() scheduler.step() print(fEpoch {epoch1}, Loss: {running_loss/len(train_loader):.4f}) torch.save(model.state_dict(), best.pth)几个参数值得说清楚。lr1e-3是 Adam 的常用起点如果 loss 震荡厉害就降到 1e-4。weight_decay1e-4是 L2 正则防止过拟合小数据集上很有用。StepLR每 10 轮把学习率乘 0.5让后期收敛更稳。CrossEntropyLoss内部已经带了 softmax所以模型最后一层不要再加 softmax加了反而会导致梯度问题。保存权重用state_dict()别直接torch.save(model)后者依赖类定义换环境加载容易出问题。训练过程中要盯两个信号训练 loss 持续下降但验证 loss 开始上升说明过拟合了该加 dropout 或者早停两个 loss 都不降可能是学习率太大或者数据标签有问题。验证集准确率能到 60% 以上就算及格FER2013 上人类标注一致率也就 65% 左右别指望冲到 90%。4. 推理与摄像头实时识别把模型用起来4.1 单张图片推理的完整流程训练完拿到.pth文件下一步是推理。单张图片推理的流程是读图、灰度化、缩放、归一化、转 tensor、加 batch 维度、送模型、取 argmax、映射标签。import cv2 import torch import numpy as np from torchvision import transforms labels [angry, disgust, fear, happy, neutral, sad, surprise] model.load_state_dict(torch.load(best.pth, map_locationdevice)) model.eval() img cv2.imread(test.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) face cv2.resize(gray, (48, 48)) face face.astype(np.float32) / 255.0 face (face - 0.5) / 0.5 tensor torch.from_numpy(face).unsqueeze(0).unsqueeze(0).to(device) with torch.no_grad(): output model(tensor) pred torch.argmax(output, dim1).item() print(labels[pred])注意unsqueeze(0)用了两次第一次是把 (48,48) 变成 (1,48,48) 补通道维第二次是变成 (1,1,48,48) 补 batch 维。少任何一个模型都会报维度错误。model.eval()必须调用否则 dropout 和 batchnorm 还在训练模式结果不稳定。torch.no_grad()关掉梯度计算省显存也更快。4.2 摄像头实时识别OpenCV 加 Haar 人脸检测实时识别比单张推理多一步人脸检测常见做法是用 OpenCV 自带的 Haar 级联分类器先框出人脸再把人脸区域送模型。这样能避免背景干扰也能提高准确率。face_cascade cv2.CascadeClassifier(cv2.data.haarcascades haarcascade_frontalface_default.xml) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(48, 48)) for (x, y, w, h) in faces: roi gray[y:yh, x:xw] roi cv2.resize(roi, (48, 48)).astype(np.float32) / 255.0 roi (roi - 0.5) / 0.5 tensor torch.from_numpy(roi).unsqueeze(0).unsqueeze(0).to(device) with torch.no_grad(): pred torch.argmax(model(tensor), dim1).item() cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, labels[pred], (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) cv2.imshow(FER, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()detectMultiScale的三个参数直接影响体验scaleFactor1.1是每次缩放比例越小检测越细但越慢minNeighbors5控制误检调大能减少误框但可能漏检minSize(48,48)过滤太小的区域。摄像头场景下光照变化大Haar 检测容易漏脸如果效果差可以换 DNN 人脸检测器OpenCV 自带cv2.dnn模块精度更高但配置稍麻烦。4.3 推理速度与准确率的平衡实时场景下每帧都跑一次模型可能掉帧。常见优化是隔几帧检测一次人脸中间帧沿用上一次的框和标签或者把模型换成 MobileNet 这类轻量网络。另外输入尺寸从 48×48 提到 96×96 通常能提一点精度但推理时间翻倍要自己权衡。如果只是答辩演示48×48 加 Haar 足够别过度优化。5. 避坑与排查那些让结果全错的细节5.1 预测结果永远是同一类现象不管输入什么图输出都是 neutral 或者 happy。原因通常是标签顺序错位或者归一化参数和训练时不一致。先打印train_dataset.classes确认训练时的标签顺序再检查推理时的labels列表是否一致。如果顺序对检查归一化训练用了mean0.5, std0.5推理也必须一样用mean0, std1会让输入分布偏移模型输出全乱。5.2 摄像头读不到帧或黑屏现象cap.read()返回False或者窗口一片黑。原因一般是摄像头索引不对或者被其他程序占用。cv2.VideoCapture(0)是默认摄像头如果有多个摄像头可以试 1 或 2。Windows 上有时需要加cv2.CAP_DSHOW参数cv2.VideoCapture(0, cv2.CAP_DSHOW)。另外确认没有其他程序比如视频会议软件占着摄像头。5.3 训练 loss 不下降现象跑了几轮 loss 一直在 1.9 左右不动。原因可能是学习率太大导致震荡或者数据标签全是乱的。先把学习率降到 1e-4 试试如果还不行手动打印几个 batch 的图片和标签确认图片不是全黑、标签不是全 0。还有一种可能是模型最后一层加了 softmax和CrossEntropyLoss冲突去掉 softmax 即可。5.4 显存不足或内存溢出现象训练到一半报CUDA out of memory或者程序被系统杀掉。解决办法是减小batch_size从 64 降到 32 甚至 16。如果用的是 CPU检查num_workers是不是设太大Windows 上设 0 最稳。另外图片尺寸别设太大48×48 的 batch 64 在 4G 显存上完全够用224×224 就悬了。5.5 换数据集后精度暴跌现象在 FER2013 上能到 60%换成自己拍的图只有 20%。原因是域偏移——训练数据和真实数据的分布不一样。解决办法是用自己的数据微调几轮或者做更激进的数据增强随机裁剪、亮度对比度大幅抖动。如果只是答辩演示建议直接用和训练集风格接近的测试图别拿手机自拍去测容易翻车。6. 进阶技巧把这份大作业做出区分度如果你想让这份作业在答辩时脱颖而出有几个方向可以加。第一是加注意力机制在卷积层后面接一个 SE 模块或者 CBAM让模型聚焦到眼睛和嘴巴区域代码量不大但效果提升明显答辩时也有东西可讲。第二是做 Grad-CAM 可视化把模型关注的区域画出来直观展示它是不是在看脸这个用pytorch-grad-cam库几行就能搞定。第三是加一个简单的 Web 界面用 Flask 或 Gradio 包一层上传图片返回表情演示效果比命令行好得多。import gradio as gr def predict_image(img): gray cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) gray cv2.resize(gray, (48, 48)).astype(np.float32) / 255.0 gray (gray - 0.5) / 0.5 tensor torch.from_numpy(gray).unsqueeze(0).unsqueeze(0).to(device) with torch.no_grad(): pred torch.argmax(model(tensor), dim1).item() return labels[pred] gr.Interface(fnpredict_image, inputsimage, outputstext).launch()这段 Gradio 代码十行不到就能起一个网页 demo答辩时老师可以直接上传图片试互动感强。注意输入图片是 RGB要先转灰度再处理别直接送模型。最后说个我自己的习惯每次跑通一个模型我都会把标签顺序、归一化参数、输入尺寸这三个值写在一个config.py里训练和推理都从这里读绝不硬编码在两处。这个习惯帮我省了无数次「结果全错但找不到原因」的时间。表情识别这个方向模型结构其实不是最难的难的是数据管线和前后处理的一致性把这块做扎实剩下的就是调参的事。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询