简介:这套基于卷积神经网络的人脸表情识别系统,面向Python开发者与计算机视觉初学者,借助Keras、OpenCV和PyQt5构建图形界面,训练与测试使用fer2013表情数据集。系统支持从本地导入图片、调用摄像头实时拍摄及视频文件处理,并可切换不同模型进行识别,覆盖图像获取、灰度归一化、头部姿态矫正、图像分割等预处理环节,为后续特征提取与分类识别打下基础。压缩包为zip格式,共49个文件,主要包含20张png图片素材、5个py源码模块、4张jpg测试图片、2个mp4演示视频、1个论文答辩PPT,以及训练好的hdf5模型权重、ui界面文件、qrc资源、log训练日志和docx说明文档,总大小约12.49MB。目前已有119人学习下载。这份资料提供从源码、数据集、模型到演示文档的完整链路,可直接运行或二次改造,尤其适合课程设计、毕业设计及希望理解表情识别系统实际搭建流程的读者。
1. 把表情识别做成桌面应用:基于 CNN 的 Emotion-Recognition 价值在哪
很多人以为做一个人脸表情识别系统,先得有一块像样的 GPU,再从零写网络结构、自己标注数据集。实际上,把 Python、卷积神经网络、OpenCV 和 PyQt5 组合起来,在普通笔记本上就能跑通一个完整的桌面应用。这份 Emotion-Recognition 源码包就是这样一套可以直接复现的项目:它基于 CNN 用 fer2013 表情库训练出模型,把训练好的模型文件、训练脚本、UI 界面、测试图片和论文答辩 PPT 都打包好了。打开就能看识别效果,也能改代码自己重新训练,属于典型的“拿来就能跑,跑完能改”的资源。
它适合的人很明确:一是正在做课程设计或毕业设计,需要一套能演示、能答辩的人脸表情识别系统;二是想搞清楚“CNN 训练到 UI 部署”全链路怎么串起来的 Python 学习者。这套资源的核心价值不是让你从零造轮子,而是让你理解它的数据流、模型组织方式和踩过的坑,然后快速改造成自己的东西。
2. 模型选型与训练链路:为什么是 mini_XCEPTION + fer2013
2.1 fer2013 数据集与模型命名里的信息
表情识别领域常用的公开数据集不少,CK+、JAFFE、fer2013 各有特点。CK+ 是实验室环境下录制的,表情比较标准但数据量小;JAFFE 是日本人脸表情库,只有 213 张图,做演示还行,做训练明显不够;fer2013 是 Kaggle 比赛用的数据集,包含 35887 张 48x48 的灰度人脸图,分 angry、disgust、fear、happy、sad、surprise、neutral 七类,官方已经划分好了训练集、验证集和测试集,这也是这套源码选择 fer2013 的直接原因。
标签序号 类别 英文标识 0 生气 angry 1 厌恶 disgust 2 恐惧 fear 3 开心 happy 4 难过 sad 5 惊讶 surprise 6 中性 neutral这个顺序不是随便排的,后面改代码、换模型时标签顺序必须和它保持一致,否则预测结果会对不上号。资源包里的模型文件命名成_mini_XCEPTION.102-0.66.hdf5,拆开看就是训练了 102 个 epoch,验证准确率约 0.66。0.66 在 fer2013 这种 7 分类任务里算中等偏上的水平,比随机猜 1/7 强得多,用作课堂演示和答辩足够了。如果你想要更高的准确率,可以加大数据增强力度,或者把网络加深,但 CPU 上跑起来就会明显变慢,这个取舍后面会细说。
mini_XCEPTION 是 XCEPTION 的轻量版本,核心是深度可分离卷积,把标准卷积拆成逐通道卷积和逐点卷积两步,参数量小很多。对课程设计这个场景来说,这是很实际的选择:普通笔记本 CPU 推理一张人脸图几十毫秒到一百多毫秒,不会卡得让人难受。如果你用 VGG16 那种大网络,识别准一点但每张图要等几秒,界面体验会差很多。
2.2 训练脚本的核心配置:从训练到生成 hdf5
资源包里的train_emotion_classifier.py就是训练入口。它不是一份摆着看的代码,而是能直接跑出模型的脚本。核心逻辑概括一下是这样的:
# train_emotion_classifier.py 的核心训练配置示意 from keras.optimizers import SGD from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau # 常见做法:SGD 加动量,学习率从 0.01 开始,配合 Nesterov 加速 sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) model.compile( optimizer=sgd, loss="categorical_crossentropy", metrics=["accuracy"], ) # 按验证准确率保存最佳权重 checkpoint = ModelCheckpoint( filepath="models/_mini_XCEPTION.{epoch:03d}-{val_accuracy:.2f}.hdf5", monitor="val_accuracy", save_best_only=True, verbose=1, ) reduce_lr = ReduceLROnPlateau( monitor="val_loss", factor=0.5, patience=5, min_lr=1e-6, )这段代码的逻辑很直白:用 SGD 加动量训练,每轮迭代后如果验证准确率提高,就把当前权重存成 hdf5 文件。这就是为什么模型文件名叫_mini_XCEPTION.102-0.66.hdf5,里面的 102 和 0.66 是{epoch:03d}-{val_accuracy:.2f}格式化出来的。decay=1e-6是学习率衰减,避免训练后期步长太大在最优解附近震荡;save_best_only=True保证磁盘上留下的是验证集上表现最好的版本,而不是最后一个 epoch 的版本。ReduceLROnPlateau是我比较偏好的配置,当验证损失连续 5 轮不降时,学习率减半,能救回一些卡在局部最优的训练过程。
需要注意,这个训练脚本默认数据路径是 fer2013 解压后的目录,如果你要自己重新训练,得先把数据集按类别分好目录,或者改脚本里的读取逻辑。实际训练时作者一般还会加数据增强,比如随机旋转 10 度、水平翻转、平移等,这些在 Keras 里用ImageDataGenerator一行就能配好,增加样本多样性,对防过拟合帮助非常明显。
2.3 cnn.py 里的自定义网络:什么时候用它替换 mini_XCEPTION
资源包里的cnn.py是一份自定义 CNN 的参考实现,用来演示“不依赖预训练模型,自己搭网络也能做表情识别”。它比 mini_XCEPTION 更直白,适合新手读,也适合答辩时讲网络结构。
# cnn.py 里的自定义 CNN 参考结构(示意) from keras.models import Sequential from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout model = Sequential() # fer2013 输入是 48x48 灰度图,通道数 1 model.add(Conv2D(32, (3, 3), activation="relu", input_shape=(48, 48, 1))) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(64, (3, 3), activation="relu")) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(128, activation="relu")) model.add(Dropout(0.5)) model.add(Dense(7, activation="softmax"))这个网络是两层卷积加两个全连接,最后用 softmax 输出 7 类概率。它没有残差结构、没有深度可分离卷积,理解起来比 mini_XCEPTION 容易很多。如果你自己训练发现 mini_XCEPTION 网络太深跑不动,可以先用这个做 baseline,再逐步加深。替换时唯一要注意的是input_shape=(48, 48, 1)必须和preprocess阶段输出的形状完全一致,不然 Keras 会报维度不匹配。
3. 把系统跑起来:环境配置、目录结构与 UI 启动全流程
3.1 依赖安装:版本匹配是第一步
这套系统基于 Keras、OpenCV、PyQt5 实现,资源包里没有 requirements.txt,所以依赖要自己装。我建议在干净环境里操作,避免和已有项目打架。
# 建议新建虚拟环境,Python 3.8 到 3.10 最稳 pip install tensorflow==2.12.0 keras==2.12.0 pip install opencv-python pyqt5 numpy pandas提示:不要装 Keras 3.x。这套源码里的训练和推理代码是按 Keras 2.x 的 API 写的,Keras 3 里部分 API 变了,跑起来会报各种兼容性错误。TensorFlow 2.12 自带 Keras 2.12,两者配套最省心。
版本问题上我踩过一次坑:一开始图省事直接pip install keras,装成了最新版,结果加载 hdf5 模型时报属性找不到,浪费了半小时查资料。后来锁到 2.12 就一切正常了。OpenCV 版本不需要特别新,4.x 就行,但要注意它自带的人脸检测器路径和 PyInstaller 打包时的数据文件路径,后面会细说。
3.2 目录结构和启动命令
解压后的资源包目录结构比较清晰,先花两分钟认清每个目录是干什么的,后面排错才能快速定位。
Emotion-Recognition-master/ ├── runMain.py # 程序入口 ├── EmotionRecongnition.py # 核心逻辑,模型加载和识别主流程 ├── cnn.py # 自定义 CNN 参考实现 ├── train_emotion_classifier.py # 训练脚本 ├── models/ │ └── _mini_XCEPTION.102-0.66.hdf5 # 训练好的模型 ├── image_test/ # UI 界面图标资源 ├── images_test/ # 测试图片 ├── Pic2py.py # 图片转 py 工具,打包时有用 ├── EmotionRecongnition_UI.ui # Qt Designer 设计的界面文件 ├── 对什么队.ppt # 论文答辩 PPT └── 测试结果和存在的问题.docx # 原作者的实测记录,建议先读启动方式很简单,在项目根目录执行:
cd Emotion-Recognition-master python runMain.py如果报缺少模块,按错误提示逐个补装;如果报模型文件找不到,多半是当前工作目录不在项目根目录,用绝对路径改EmotionRecongnition.py里的模型路径即可。首次启动建议先拿图片测试,不要直接开摄像头,因为你还不清楚这套系统的行为逻辑,先用静态图把流程跑通,成功率更高。
3.3 UI 能做的三件事:图片、视频、切换模型
runMain.py加载EmotionRecongnition_UI.ui界面后,主窗口会有几个核心功能区:打开图片、打开摄像头、视频文件分析,以及模型切换入口。图片和视频经识别后,会在界面的人脸区域绘制矩形框,框上方显示表情类别和置信度。资源包里提供了demo.jpg和一组测试图片,覆盖了开心、生气等不同表情,先拿它们验证结果是否符合预期。
UI 的界面逻辑是从 Qt Designer 里导出的.ui文件,配合资源文件image1.qrc,里面打包了按钮图标、背景图等。如果你在别的机器上打开发现图片区域空白,多半是 qrc 资源没编译进 py 文件,这个属于高频问题,我在第 5 章专门细说。
4. 核心代码拆解:从 OpenCV 采集到 Keras 推理的图像处理链
4.1 人脸检测与图片读取:中文路径的坑
表情识别不是直接把整张图片丢给模型,那样背景干扰太大。正确流程是先用 OpenCV 的 Haar 级联检测器定位人脸区域,裁出人脸图,再送进 CNN。资源包的代码里图片读取部分值得注意,因为它绕开了 OpenCV 一个很经典的问题。
import cv2 import numpy as np def load_image(path): # cv2.imread 不支持中文路径,读中文路径会静默返回 None # 常见做法是先用 np.fromfile 读成二进制再解码 data = np.fromfile(path, dtype=np.uint8) img = cv2.imdecode(data, cv2.IMREAD_COLOR) return img def detect_face(gray): face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ) faces = face_cascade.detectMultiScale( gray, scaleFactor=1.1, minNeighbors=5, minSize=(48, 48), ) return facesload_image这个函数看着不起眼,但如果没有它,你的图片路径里一旦带中文,cv2.imread会直接返回None,而且不报错,后面所有代码都会在莫名其妙的地方崩掉。改成np.fromfile读字节流再cv2.imdecode解码,中文路径就正常了。detect_face里的参数解释一下:scaleFactor=1.1表示每次搜索时窗口缩小 10%,值越小检得越细但越慢;minNeighbors=5是每个候选区域至少被 5 个邻近窗口确认才保留,值越大误检越少但可能漏检;minSize=(48, 48)和模型输入尺寸对齐,小于这个尺寸的人脸直接跳过,避免送入模型时被拉伸变形。
4.2 送入 Keras 前的预处理:灰度、缩放与归一化
fer2013 是灰度图,mini_XCEPTION 的输入是 48x48 的单通道图,所以检测到的人脸必须先转灰度、缩放、归一化,才能交给模型。
def preprocess(face_bgr, target_size=(48, 48)): gray = cv2.cvtColor(face_bgr, cv2.COLOR_BGR2GRAY) gray = cv2.resize(gray, target_size) # 常见做法:转成 float32 并除以 255,归一化到 0-1 # 注意:如果你自己训练的模型按 0-255 训练,就不要除以 255 gray = gray.astype("float32") / 255.0 # 模型输入是 (batch, 48, 48, 1),先补通道维再补 batch 维 gray = np.expand_dims(np.expand_dims(gray, -1), 0) return gray这里最关键的是归一化方式必须和训练时一致。如果模型训练时输入是 0-255,你推理时除以 255,预测概率会变得极其平均,识别结果几乎随机;反过来也一样。判断方法很简单:单张图片多试几次,如果某类表情概率始终很低且分布均匀,基本都是归一化不匹配。expand_dims连续调用两次是因为 Keras 模型要求输入是一个四维张量(batch, height, width, channels),单张图必须补出 batch 维和 channel 维。
4.3 推理与结果可视化
预处理完成后,加载 hdf5 模型做预测,再把结果画到界面上。
from keras.models import load_model model = load_model("models/_mini_XCEPTION.102-0.66.hdf5") # 标签顺序必须和训练时完全一致 label_list = ["angry", "disgust", "fear", "happy", "sad", "surprise", "neutral"] def predict_face(face_bgr): x = preprocess(face_bgr) preds = model.predict(x, verbose=0)[0] idx = int(np.argmax(preds)) return label_list[idx], float(preds[idx])model.predict(x, verbose=0)返回的是长度为 7 的概率数组,总和为 1,取argmax得到概率最大的类别索引,再从label_list里取对应的表情名。显示在界面上的时候,可以把置信度也画出来,比如happy 87.3%,答辩时这个数字比单纯标一个类别更有说服力。整个图像处理链就这么长:读图、检测人脸、裁图、缩放灰度、归一化、预测、画框。搞清楚这条链,后面换模型、换数据集都不会慌。
5. 避坑与排查:从摄像头到中文路径的几个真实记录
资源包里的测试结果和存在的问题.docx是原作者的实测记录,里面提到的部分问题和下面几条高度重合。我按自己跑通时的排错经历整理出五条高频率问题。
5.1 摄像头打不开、图片读不出来
现象:点击“打开摄像头”后画面全黑,cv2.VideoCapture(0)返回False,程序不报错但就是没有画面。
原因:两个来源。一是系统相机隐私权限没开,桌面应用拿不到摄像头;二是摄像头编号不对,笔记本自带的摄像头不一定是 0,也可能是 1。
解决:先打开 Windows 设置里的“相机隐私”,允许桌面应用访问摄像头;再把cv2.VideoCapture(0)里的参数改成 1 或 -1 试试。如果仍然拿不到,优先用图片测试功能跑通整个流程,摄像头的问题单独排查,不要卡在这上面影响进度。
现象:cv2.imread返回值是None,图片显示为空白。
原因:图片路径含中文或特殊字符,OpenCV 的imread内部按 C 风格字符串读取,中文路径直接读不出来且不报错。
解决:用第 4 章写的load_image函数,先np.fromfile读二进制再cv2.imdecode,问题立即消失。这也是为什么源码包里会专门放一个Pic2py.py工具,它可以把图片转成 py 模块,彻底绕开路径问题。
5.2 模型加载与识别结果异常
现象:启动时提示No such file or directory,找不到 hdf5 模型文件。
原因:当前工作目录不是项目根目录。在 PyCharm 或 VS Code 里直接运行runMain.py时,工作目录往往停在工程根目录,而代码里的models/是相对路径,自然找不到。
解决:在入口脚本开头加os.chdir(os.path.dirname(os.path.abspath(__file__))),让工作目录固定到脚本所在目录;或者直接把模型路径改成绝对路径。
现象:预测结果所有类别概率几乎相等,识别跟随机一样。
原因:绝大部分情况是预处理不一致。模型训练时归一化到 0-1,你推理时忘了除以 255,或者反过来了。
解决:对比训练脚本里的预处理方式,保持一致。拿同一张测试图分别走两种归一化,看哪种输出的概率分布有明显的最大值。这个验证只要做一次,以后就固定下来。
现象:PyQt5 界面上图片区域空白,按钮图标也不显示。
原因:.qrc资源文件没编译成 py 模块。Qt Designer 设计的界面引用了 qrc 里的资源,但 Python 运行时读不到。
解决:用pyrcc5 image1.qrc -o image1_rc.py重新生成资源模块,放到项目对应位置;或者学Pic2py.py的做法,把图片批量转成 py 文件,直接 import。
6. 进阶技巧:换模型、验证与打包发布
6.1 换模型时最容易忽略的标签对齐
如果你重新训练了自己的模型,想替换掉原来的_mini_XCEPTION.102-0.66.hdf5,最关键的步骤不是改路径,而是确认标签顺序。fer2013 的标签顺序在 Keras 的flow_from_directory里是按目录名排序的,不同人的训练代码排出来的顺序可能不一样。如果新模型的标签顺序是["angry", "disgust", "fear", "happy", "sad", "surprise", "neutral"],那就和源码一致,直接替换文件即可;如果不是,必须在EmotionRecongnition.py里同步修改label_list,否则界面会把一张生气脸标成高兴。
换模型后我建议强制走一遍单张图片验证:准备一张明显的笑脸、一张明显的生气脸,分别预测,打印出完整的 7 类概率数组,确认最大的那一项是预期类别。这样能在十分钟内确认模型本身没问题、标签顺序没问题、预处理没问题。
6.2 打包发布时 OpenCV 数据的坑
课程设计或答辩有时需要把系统打包成 exe 演示,PyInstaller 打包时最容易漏的是 OpenCV 的 Haar 级联文件。haarcascade_frontalface_default.xml在cv2.data.haarcascades路径下,打包后默认不会自动带上,不处理的话 exe 一到别人机器上就报检测器为空。常见做法是打包命令里显式添加数据文件:
pyinstaller -w -F runMain.py \ --hidden-import=PyQt5.sip \ --add-data "models/_mini_XCEPTION.102-0.66.hdf5;models" \ --add-data "路径/haarcascade_frontalface_default.xml;cv2/data"模型和 xml 文件都通过--add-data打进去,运行时再用sys._MEIPASS拼出正确路径。图片资源如果还走本地文件,建议先用Pic2py.py转成 py 模块打包,这样发布的 exe 不需要额外带上散图目录。
这套资源包里的演示视频和 PPT 可以直接用来答辩展示,但我觉得最有价值的还是那个训练脚本和全套源码。自己动手重新训练一轮,哪怕只用一半数据,也能把整个识别链路理解得更深。有一回我图省事,换模型忘了同步标签顺序,演示时把 sad 识别成 happy,当场翻车,从那以后我每次换模型都强制走一遍标签对齐和单张图片验证,希望帮到你。
本文还有配套的精品资源,点击获取