简介:这是一套面向计算机相关专业毕业设计与课程实战的Python人脸表情识别课堂行为检测系统,适合正在准备毕设、期末大作业或需要项目练手的学生。项目经导师指导并通过评审,代码完整可运行,对新手友好,能帮助读者快速理解表情识别与课堂行为分析的实现思路。压缩包共261个文件,约118.46MB,以90个py源码、75个pyc编译文件、24个html与21个css页面资源为主,另含17段mp4演示视频、6个xml配置、6张jpg图片及3个db数据文件,覆盖前端页面、后端逻辑与模型资源。目前已有216人学习下载。读者可获得完整项目源码与模型、可参考的目录结构、页面样式与数据库文件,以及演示视频,便于对照复现、二次开发和撰写论文,是较实用的毕设参考方案。
1. 从一份 99 分毕设说起:这套表情识别课堂行为检测系统到底能跑出什么
大四上学期末,我带的一个学弟把毕设初稿发我,题目就是「人脸表情识别的课堂行为检测系统」。他跑了一遍演示视频,摄像头对着教室后排,屏幕上实时框出每张脸,旁边标着「专注」「走神」「疑惑」几个标签,准确率看着还行。他问我:这东西能不能直接交?我让他先把源码包解压,看看里面到底有什么。结果发现,这套东西的价值不在于算法多新,而在于它把「人脸检测 → 表情分类 → 行为映射 → Web 端展示」整条链路都串起来了,而且代码结构清晰到小白能顺着改。
这套资源适合三类人:正在做计算机相关毕设、需要一份能跑通且能讲清楚原理的项目;课程设计或期末大作业想拿高分的同学;以及想练手 Python 视觉项目但不想从零搭环境的开发者。它解决的核心问题是:把抽象的表情识别算法落到具体的课堂场景里,用行为标签输出结果,而不是只给你一个冷冰冰的准确率数字。源码包里包含前端 CSS、后端逻辑和训练好的模型文件,拿到手就能跑,不用自己从头训模型。接下来我会按「资源拆解 → 环境搭建 → 核心链路 → 避坑 → 进阶调参」的顺序,把这份毕设里里外外讲透,让你不仅能跑起来,还能在答辩时说出每个参数为什么这么设。
2. 拆开源码包:目录结构、模型文件与前后端分工
2.1 从 CSS 文件反推系统架构
很多人拿到源码包第一反应是找app.py或main.py,但这套资源里最先暴露架构信息的其实是那几个 CSS 文件。你看到的admin_login.css、student_index.css、teacher_index.css、course_index.css,分别对应管理员登录页、学生首页、教师首页和课程页。这说明系统不是单页脚本,而是一个带角色权限的 Web 应用。student_insert.css的存在进一步说明有学生信息录入功能,大概率是教师端用来批量导入学生名单的。
从这些样式文件的命名能推断出后端至少有三个角色:管理员、教师、学生。管理员负责账号和基础数据,教师负责发起课堂检测和查看报表,学生端可能只用来查看自己的课堂表现记录。这种设计在毕设里算比较完整的,比那些只有一个上传图片按钮的 demo 强不少。你拿到源码后,先别急着改 Python 代码,把templates或static目录下的 HTML 和 CSS 对应关系理一遍,后面调页面样式时能省很多时间。
2.2 模型文件与依赖清单
源码包里通常会有一个models或weights目录,里面放着.h5或.pth文件。这套系统的人脸检测部分常见做法是用 OpenCV 的 DNN 模块加载预训练模型,表情分类则是一个小型 CNN,输入尺寸一般是 48×48 灰度图,输出 7 类表情:生气、厌恶、恐惧、开心、悲伤、惊讶、中性。课堂行为检测不会直接用这 7 类,而是做一层映射,比如「开心 + 中性」归为「专注」,「悲伤 + 生气」归为「抵触」,「惊讶」单独标为「疑惑」。
依赖方面,requirements.txt里一般会列opencv-python、tensorflow或pytorch、flask、numpy、pandas。这里有个血泪经验:如果模型是 TensorFlow 1.x 训练的,你装 TensorFlow 2.x 会直接报load_model兼容性错误。先看模型文件后缀,.h5且代码里用keras.models.load_model的,大概率是 TF 2.x;如果是.pb加frozen_inference_graph,那是 TF 1.x 的冻结图,得用cv2.dnn.readNetFromTensorflow加载。别上来就pip install tensorflow最新版,先翻一眼源码里的 import 语句。
2.3 前后端数据流与角色权限
系统跑起来后的数据流是这样的:教师端打开摄像头或上传课堂视频 → 后端逐帧抽图 → 人脸检测框出每张脸 → 裁剪对齐后送入表情分类模型 → 得到表情标签 → 按预设规则映射为行为标签 → 写入数据库并推送到前端展示。学生端登录后只能看到自己的记录,教师端能看到全班统计,管理员端管账号和课程表。
这里有个容易翻车的点:如果视频里人脸多、帧率高,后端同步处理会卡死。常见做法是抽帧处理,比如每 5 帧取 1 帧,或者用队列把检测任务异步化。源码里如果没做这个优化,你演示时用一段 30 秒的教室视频就能把内存吃满。我一般会先看app.py里有没有threading或celery相关的代码,没有的话自己加一个简单的生产者-消费者队列,后面第 4 章会讲具体怎么改。
3. 把环境跑起来:Python 版本、依赖安装与首次启动
3.1 Python 版本选择与虚拟环境
这套毕设的代码大概率是在 Python 3.7 或 3.8 下写的,因为 TensorFlow 1.x 和部分 OpenCV 版本对高版本 Python 支持不好。如果你用 Python 3.11,装tensorflow==1.15会直接失败。稳妥做法是装 Python 3.8,然后用 venv 建独立环境。别用 conda 混装,除非你很清楚每个包的来源,否则后面报DLL load failed时排查起来很痛苦。
# 创建虚拟环境,指定 Python 3.8 python3.8 -m venv venv # 激活环境(Windows 用 venv\Scripts\activate) source venv/bin/activate # 升级 pip,避免旧版解析依赖出错 pip install --upgrade pip逻辑说明:虚拟环境把项目依赖和系统 Python 隔开,避免你之前装的包干扰。参数上,python3.8要确保系统里真的装了 3.8,可以用python3.8 --version验证。如果提示找不到命令,去 Python 官网下载 3.8 的安装包,安装时勾选「Add to PATH」。
3.2 依赖安装与常见报错处理
进入项目根目录后,先看requirements.txt里有没有版本号。如果写的是opencv-python不带版本,pip 会装最新版,可能和代码里的cv2.dnn接口不兼容。我一般会手动锁几个关键版本:
# 按顺序安装,避免依赖冲突 pip install numpy==1.19.5 pip install opencv-python==4.5.5.64 pip install tensorflow==2.3.0 pip install flask==1.1.2 pip install pandas==1.1.5逻辑说明:numpy锁 1.19.5 是因为 TensorFlow 2.3 对更高版本支持不稳定;opencv-python选 4.5.5 是因为这个版本同时支持 DNN 模块和 GUI 显示,再高可能缺cv2.imshow的依赖。装完后用pip list检查一遍,如果看到tensorflow和opencv-python版本号对不上,先别跑代码,回去调版本。
3.3 首次启动与数据库初始化
源码里如果有init_db.py或create_table.sql,先跑它建表。没有的话,看app.py里有没有db.create_all(),有的话第一次启动会自动建。启动命令一般是:
# 设置环境变量,避免 Flask 找不到入口 export FLASK_APP=app.py export FLASK_ENV=development # 启动服务 flask run --host=0.0.0.0 --port=5000逻辑说明:FLASK_APP告诉 Flask 入口文件是哪个,FLASK_ENV=development开启调试模式,改代码后自动重载。--host=0.0.0.0让同局域网的其他设备也能访问,方便你用手机测试学生端。启动后浏览器打开http://127.0.0.1:5000,如果看到登录页,说明前端资源加载正常;如果报TemplateNotFound,检查templates目录是否和app.py同级。
提示:首次启动如果卡在「Loading model...」超过 30 秒,大概率是模型文件路径写错了。去代码里搜
load_model或readNetFrom,把路径改成绝对路径试试。
4. 核心链路拆解:人脸检测、表情分类与行为映射
4.1 人脸检测模块的参数调优
这套系统的人脸检测通常用 OpenCV 的 DNN 人脸检测器,加载res10_300x300_ssd_iter_140000.caffemodel和对应的deploy.prototxt。代码里一般会设一个置信度阈值,常见是 0.5。这个值调高会漏检侧脸,调低会误检背景。课堂场景里学生有低头、转头动作,我一般会把阈值降到 0.4,同时把NMS的阈值设成 0.3,避免同一张脸出多个框。
# 人脸检测核心参数 net = cv2.dnn.readNetFromCaffe(prototxt_path, model_path) blob = cv2.dnn.blobFromImage(frame, 1.0, (300, 300), (104.0, 177.0, 123.0)) net.setInput(blob) detections = net.forward() confidence_threshold = 0.4 # 课堂场景建议 0.4,纯正面照可设 0.6 for i in range(detections.shape[2]): confidence = detections[0, 0, i, 2] if confidence > confidence_threshold: box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (x1, y1, x2, y2) = box.astype("int") # 裁剪人脸区域,留 10% 边距 face = frame[max(0, y1-10):min(h, y2+10), max(0, x1-10):min(w, x2+10)]逻辑说明:blobFromImage的1.0是缩放因子,(300, 300)是网络输入尺寸,(104.0, 177.0, 123.0)是均值减去的值,这三个参数必须和训练时一致,改了就检测不准。confidence_threshold是唯一建议你动的参数,课堂视频用 0.4,单人自拍用 0.6。裁剪时留边距是为了让表情分类模型看到完整五官,不留边距容易把下巴或额头切掉,影响分类。
4.2 表情分类模型的输入预处理
表情分类模型一般输入 48×48 灰度图,所以裁剪出人脸后要做灰度转换和尺寸缩放。这里有个细节:很多人直接cv2.resize到 48×48,但训练时如果用的是ImageDataGenerator的rescale=1./255,你推理时也得除以 255。忘了这一步,模型输出全是同一类。
# 表情分类预处理 gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY) gray = cv2.resize(gray, (48, 48)) gray = gray.astype("float32") / 255.0 # 必须和训练时一致 gray = np.expand_dims(gray, axis=-1) # 增加通道维度 gray = np.expand_dims(gray, axis=0) # 增加 batch 维度 # 推理 preds = emotion_model.predict(gray) emotion_label = np.argmax(preds) confidence = np.max(preds)逻辑说明:astype("float32") / 255.0是归一化,如果训练时没做归一化,这行要去掉。expand_dims两次是因为 Keras 模型期望输入形状是(batch, height, width, channels)。np.argmax取最大概率的索引,对应表情标签。如果confidence低于 0.5,建议标为「不确定」,不要硬归到某一类,否则行为映射会出错。
4.3 行为映射规则与可调参数
表情到行为的映射没有标准答案,这套毕设里用的规则大概是:开心和中性 → 专注;悲伤、生气、厌恶 → 抵触;惊讶 → 疑惑;恐惧 → 焦虑。你可以根据答辩场景调整,比如把「中性」拆成「专注」和「无聊」,用连续多帧的中性占比来判断。
# 行为映射规则,可按需修改 behavior_map = { "happy": "专注", "neutral": "专注", "sad": "抵触", "angry": "抵触", "disgust": "抵触", "surprise": "疑惑", "fear": "焦虑" } # 连续 5 帧同一表情才输出行为,避免抖动 from collections import deque history = deque(maxlen=5) history.append(emotion_label) if len(history) == 5 and len(set(history)) == 1: behavior = behavior_map.get(emotion_label, "未知")逻辑说明:deque(maxlen=5)是一个滑动窗口,只有连续 5 帧表情一致才输出行为标签,这样能过滤掉单帧误检导致的标签跳变。behavior_map是字典,键是模型输出的表情英文标签,值是你想展示的中文行为。改映射规则不用动模型,只改这个字典就行,答辩时被问到「为什么这么映射」也有话可说。
5. 避坑与排查:模型加载、路径与性能的五个翻车点
5.1 模型加载报「Unknown layer」或「Bad marshal data」
现象:启动时load_model抛异常,提示未知层类型或数据损坏。原因通常是 TensorFlow 版本和模型保存时的版本不一致,或者模型文件在下载/复制过程中损坏。解决:先确认模型后缀,.h5用keras.models.load_model,.pb用cv2.dnn.readNetFromTensorflow。如果版本对不上,装一个和源码requirements.txt里一致的 TF 版本,别硬扛。文件损坏的话,重新解压源码包,别用浏览器直接下载模型文件,容易断点续传出错。
5.2 摄像头打不开或画面全黑
现象:cv2.VideoCapture(0)返回 False,或者读到的帧全是黑色。原因在 Windows 上常见是摄像头被其他程序占用,在 Linux 上是权限问题。解决:先关掉腾讯会议、钉钉等可能占用摄像头的软件。Linux 下把当前用户加入video组:sudo usermod -aG video $USER,然后重新登录。如果用的是虚拟机,检查 USB 摄像头是否直通到虚拟机里。
5.3 中文标签显示成方块
现象:前端页面上「专注」「疑惑」显示为乱码或方块。原因是 OpenCV 的cv2.putText不支持中文,或者前端字体没设对。解决:如果是在视频画面上写字,改用 PIL 的ImageDraw配合中文字体文件;如果是 Web 页面,在 CSS 里指定font-family: "Microsoft YaHei", "PingFang SC", sans-serif;。源码里的 CSS 文件如果没写中文字体,自己加一行就行。
5.4 多脸场景下帧率骤降
现象:画面里超过 5 个人时,视频卡成幻灯片。原因是每帧都对所有人脸跑一次表情分类,计算量线性增长。解决:抽帧处理,每 3 到 5 帧检测一次人脸,中间帧复用上一次的框;或者把表情分类模型换成轻量版,比如 MobileNet 的变体。源码里如果没做抽帧,在while循环里加一个帧计数器,if frame_count % 3 != 0: continue。
5.5 数据库写入报「Too many connections」
现象:跑一段时间后后端报数据库连接数超限。原因是每次检测都开新连接,没关。解决:用连接池,Flask 里配SQLALCHEMY_ENGINE_OPTIONS的pool_size和max_overflow;或者把检测结果先攒在内存里,每 10 秒批量写一次。源码里如果是 SQLite,并发写会锁库,建议换成 MySQL 或者把写操作放到单独线程里串行执行。
6. 进阶调参与答辩加分技巧:让系统看起来更「聪明」
6.1 用滑动窗口平滑行为标签
前面第 4 章提过用deque做连续帧判断,这里再进一步:把窗口大小从 5 调到 10,并且给不同表情加权。比如「开心」权重 1.0,「中性」权重 0.8,「悲伤」权重 0.6,窗口内加权得分最高的行为作为输出。这样能减少「专注」和「疑惑」之间的频繁跳变,演示时看起来更稳定。
# 加权滑动窗口 from collections import deque window = deque(maxlen=10) weights = {"happy": 1.0, "neutral": 0.8, "sad": 0.6, "angry": 0.6, "surprise": 0.7, "fear": 0.5, "disgust": 0.5} def smooth_behavior(emotion_label): window.append(emotion_label) score = {} for label in window: behavior = behavior_map.get(label, "未知") score[behavior] = score.get(behavior, 0) + weights.get(label, 0.5) return max(score, key=score.get)逻辑说明:window存最近 10 帧的表情标签,weights给每种表情一个可信度权重。score字典累加每个行为的总分,最后取最高分的行为。这样即使中间有一两帧误检,也不会立刻改变输出。参数上,窗口越大越平滑但延迟越高,10 帧在 30fps 下约 0.3 秒延迟,答辩演示够用。
6.2 用混淆矩阵证明模型可靠性
答辩时老师常问「你怎么知道模型准不准」。别只报一个准确率数字,把测试集上的混淆矩阵画出来。源码里如果有test.py或evaluate.py,跑一遍拿到confusion_matrix,用seaborn画热力图。重点看「专注」和「疑惑」之间的误判率,如果这两类互相误判多,说明中性表情和惊讶表情的特征有重叠,可以在答辩时主动提出来,并说明你用了滑动窗口来缓解。
from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt # y_true 和 y_pred 从测试集推理结果里来 cm = confusion_matrix(y_true, y_pred) sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=emotion_labels, yticklabels=emotion_labels) plt.xlabel("Predicted") plt.ylabel("True") plt.title("Emotion Classification Confusion Matrix") plt.show()逻辑说明:confusion_matrix的输入是两个一维数组,分别是真实标签和预测标签。annot=True在格子里显示数字,fmt="d"保证显示整数。xticklabels和yticklabels用表情标签列表,别用数字索引,否则图看不懂。这张图放在论文里比单纯写「准确率 85%」有说服力得多。
6.3 把检测结果导出成 CSV 做课堂报表
教师端如果只能实时看,答辩时展示效果有限。加一个导出按钮,把每节课的行为统计写成 CSV:学生姓名、专注时长占比、疑惑次数、抵触次数。用 pandas 的groupby按学生聚合,两行代码就能出报表。老师看到这个功能,会觉得你的系统有实际落地价值,不是玩具。
import pandas as pd # records 是检测过程中攒的列表,每条包含 student_id, behavior, timestamp df = pd.DataFrame(records) report = df.groupby("student_id")["behavior"].value_counts(normalize=True).unstack().fillna(0) report.to_csv("class_report.csv", encoding="utf-8-sig")逻辑说明:value_counts(normalize=True)算的是每个学生各种行为的占比,unstack把行为变成列,fillna(0)把没出现的行为填 0。encoding="utf-8-sig"是为了 Excel 打开不乱码。这个报表可以直接放进论文的「系统测试」章节,截图展示。
6.4 低显存环境下的模型量化
如果你的笔记本没有独立显卡,跑 TensorFlow 模型会吃满 CPU 内存。常见做法是用 TensorFlow Lite 把模型转成.tflite,体积缩小到原来的四分之一,推理速度提升两三倍。转换命令一行就够,但要注意输入输出节点名称要对上。
# 把 Keras 模型转成 TFLite import tensorflow as tf converter = tf.lite.TFLiteConverter.from_keras_model(emotion_model) converter.optimizations = [tf.lite.Optimize.DEFAULT] tflite_model = converter.convert() open("emotion_model.tflite", "wb").write(tflite_model)逻辑说明:Optimize.DEFAULT开启默认量化,权重从 float32 变成 int8,精度损失通常在 1% 到 2% 之间,课堂行为检测够用。转换后推理代码要改成tf.lite.Interpreter,输入输出张量的索引从get_input_details和get_output_details拿。这一步做完,你的系统在 8GB 内存的轻薄本上也能流畅跑。
从那以后我每次拿到带模型的毕设源码,都先跑一遍pip list和模型加载测试,确认版本对得上再往下做。这套表情识别课堂行为检测系统的价值在于链路完整、代码可读,你顺着第 3 章的环境搭建和第 4 章的链路拆解走一遍,基本能复现出演示效果。希望帮到你。
本文还有配套的精品资源,点击获取