尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于CNN的人脸表情识别系统:从模型设计到实时演示

基于CNN的人脸表情识别系统:从模型设计到实时演示 简介基于卷积神经网络CNN的人脸表情识别系统是一份Python期末大作业源码面向计算机专业学生与深度学习初学者适合课程设计、实训项目或作品展示也可作为表情识别入门实战参考。zip压缩包共包含23个文件总大小19.17MB以10个Python脚本为主覆盖数据读取、模型定义、训练、测试及摄像头/图片实时识别等环节同时配备TFRecord数据文件、训练好的Keras模型、测试图片、HTML可视化页面以及README说明便于快速复现与二次开发。目前已有51人学习该项目曾获评审98分经过导师认可和本地调试运行可靠性较高。通过这份源代码学习者可以完整了解CNN表情识别的实现路径掌握从数据集预处理、模型训练到界面化调用的具体写法也能够借助附带的测试样例快速验证效果对期末大作业答辩、课程实践或深度学习项目开发都有直接的参考价值。1. 为什么“基于CNN的人脸表情识别系统”适合作为期末大作业期末大作业的评分点和企业项目不一样老师看的是能否复现、有没有分析、演示稳不稳定而不是追求多高的准确率。基于 CNN 的人脸表情识别系统恰好占了三头便宜CNN 原理在答辩时好展开Python 生态里现成库多FER2013 数据集公开可下载。反直觉的一点是这个任务的测试准确率在 70% 左右已经算正常因为表情边界模糊、标签本身有主观噪声人在这个数据集上也未必做得更好。分数差距往往出在数据预处理有没有踩坑、模型参数选型有没有依据、摄像头演示会不会崩。下面按模型设计、数据准备、训练调参、实时演示、答辩验收的顺序给出完整源码路径每步都说清为什么这么做。2. CNN 表情识别模型的网络结构与设计依据2.1 为什么表情识别任务先用 CNN 而不是传统手工特征表情识别的难点不是认出一张脸而是分辨同一张脸上的细微肌肉变化。LBP、HOG 这类手工特征对光照和位置偏移敏感人脸平移几个像素梯度直方图就会明显变化而 CNN 的卷积核在整张图上滑动天然具备平移容忍性卷积层从边缘、纹理到部件逐层组合特征正好对应表情这种“局部变化决定全局语义”的任务。真正让 CNN 成为期末作业首选的理由是参数共享。FER2013 只有约 2.8 万张 48×48 灰度训练图如果第一层就用全连接参数量会直接膨胀到无法在小样本上收敛卷积层用同一个 3×3 核遍历所有位置参数量少两三个数量级配合池化逐步降维整个模型参数量在 30 万到 40 万之间纯 CPU 也能在合理时间内训练完。还要澄清一个常见误解CNN 有平移不变性但并没有旋转不变性。头部轻微歪斜模型可能把同一表情判成不同类因此后面数据增强里必须加小幅旋转这直接影响验证集准确率是 55% 还是 65%。2.2 四层 Conv-BN-ReLU 基线的具体结构from tensorflow.keras import layers, models def build_emotion_cnn(input_shape(48, 48, 1), num_classes7): model models.Sequential([ # 第一卷积块48x48 - 24x24 layers.Conv2D(32, (3, 3), paddingsame, input_shapeinput_shape), layers.BatchNormalization(), layers.Activation(relu), layers.MaxPooling2D((2, 2)), # 第二卷积块24x24 - 12x12 layers.Conv2D(64, (3, 3), paddingsame), layers.BatchNormalization(), layers.Activation(relu), layers.MaxPooling2D((2, 2)), # 第三卷积块12x12 - 6x6 layers.Conv2D(128, (3, 3), paddingsame), layers.BatchNormalization(), layers.Activation(relu), layers.MaxPooling2D((2, 2)), # 第四卷积块6x6 - 3x3 layers.Conv2D(128, (3, 3), paddingsame), layers.BatchNormalization(), layers.Activation(relu), layers.MaxPooling2D((2, 2)), # 分类头展平后接一个隐藏全连接层 layers.Flatten(), layers.Dropout(0.5), layers.Dense(64, activationrelu), layers.Dense(num_classes, activationsoftmax), ]) return model输入是(48, 48, 1)对应 FER2013 的 48×48 灰度图通道数为 1如果换成 RAF-DB 这类彩色数据集这里要改成 3。paddingsame让卷积输出尺寸与输入一致池化层每次精确地把尺寸减半四层之后 48 变成 3空间信息被压缩成 128 个通道的特征图。宽度从 32 递增到 128没有继续翻倍到 256。输入分辨率只有 48网络过宽在 2.8 万样本上容易过拟合。BatchNorm 放在卷积之后、ReLU 之前即 Conv→BatchNorm→ReLU 顺序训练初期损失下降更稳也有人写 Conv→ReLU→BatchNorm在小图上两者差距不大但提交的源码里只能选一种不要混用。分类头用 64 维全连接再接 Softmax隐藏层维度太高是过拟合隐患Dropout(0.5) 在展平之后生效是这套结构里最重要的正则手段。2.3 各层输出尺寸与参数量对照层输出形状参数量约Conv2D_1 BatchNorm(48, 48, 32)320 64MaxPooling_1(24, 24, 32)0Conv2D_2 BatchNorm(24, 24, 64)18,496 128MaxPooling_2(12, 12, 64)0Conv2D_3 BatchNorm(12, 12, 128)73,856 256MaxPooling_3(6, 6, 128)0Conv2D_4 BatchNorm(6, 6, 128)147,584 256MaxPooling_4(3, 3, 128)0Flatten → Dropout11520Dense(64)6473,792Dense(7)7455这个表可以直接画成 CNN 结构图放进期末报告。总参数量约 31 万在 Colab 免费 GPU 上一次 epoch 只需要几十秒CPU 上也能忍受。2.4 输出层与损失函数的选型理由7 类表情互斥输出层用 Softmax 把全连接输出的 logits 转成概率向量训练损失用categorical_crossentropy。不要用 MSESoftmax 配上 MSE 在输出接近 0 或 1 时梯度接近消失收敛明显变慢。如果想让源码显得更专业可以给损失加一点 label smoothingloss tf.keras.losses.CategoricalCrossentropy(label_smoothing0.1)FER2013 的标注本身有噪声label smoothing 让模型不过分相信训练标签通常能带来 1 到 2 个百分点的验证集提升。完整的编译代码在 4.1 节训练脚本里给出。3. FER2013 数据载入与预处理源码是否开箱即跑全看这一章3.1 FER2013 原始格式与两种获取方式FER2013 是 Kaggle 人脸表情识别比赛公开数据集常见的是 CSV 和图片目录两种分发形式。期末大作业用 CSV 最省事因为标签、像素和官方划分都在一个文件里图片目录需要自己写遍历代码下载后还要确认 train/val/test 是否已经分好。CSV 里的坑有两个。第一pixels列是字符串2304 个 0 到 255 的整数用空格分隔直接用pd.read_csv读进来是字符串对象必须解析成数值再 reshape 成 48×48。第二Usage列有 Training、PublicTest、PrivateTest 三种取值。规范做法是 Training 训练PublicTest 当验证集PrivateTest 留作最终测试这套划分可以和其他论文的准确率直接对比。3.2 把 CSV 解析成可训练的 numpy 数组import pandas as pd import numpy as np def load_fer2013(csv_path): df pd.read_csv(csv_path) # 解析每一行空格分隔的 2304 个像素值 images np.array([ np.fromiter(p.split(), dtypenp.uint8, count2304) for p in df[pixels] ], dtypenp.float32) images images.reshape(-1, 48, 48, 1) / 255.0 # 标签转成 one-hot labels pd.get_dummies(df[emotion]).values.astype(np.float32) train_mask (df[Usage] Training).values val_mask (df[Usage] PublicTest).values test_mask (df[Usage] PrivateTest).values return (images[train_mask], labels[train_mask]), \ (images[val_mask], labels[val_mask]), \ (images[test_mask], labels[test_mask])为什么用np.fromiter而不是老教程里的np.fromstring(p, sep )NumPy 1.18 之后np.fromstring对字符串参数会触发弃用警告源码能跑但满屏警告演示现场很掉价。p.split()生成 2304 个元素np.fromiter按uint8读入最后整体转float32并除以 255 归一化到 0 到 1。归一化细节决定预测是否正常。这里除以 255.0迁移预训练模型时通常用(x - 127.5) / 127.5归一化到 -1 到 1。无论选哪种训练和摄像头实时预测必须保持完全一致这是实时演示翻车最常见的根源。3.3 数据增强参数与类别不均衡处理from tensorflow.keras.preprocessing.image import ImageDataGenerator train_aug ImageDataGenerator( rotation_range10, # 小角度旋转模拟头部轻微倾斜 width_shift_range0.1, # 水平平移容忍人脸检测框偏移 height_shift_range0.1, zoom_range0.1, # 缩放适应摄像头距离变化 horizontal_flipTrue, # 镜像翻转 fill_modenearest # 空区域用最近像素填充 ) train_flow train_aug.flow(train_images, train_labels, batch_size64)参数选择依据参数取值理由rotation_range10表情语义对大幅旋转敏感歪太多会把 sad 判成 neutralwidth/height_shift0.1容忍 Haar 人脸检测框的偏移zoom_range0.1模拟人离摄像头远近变化horizontal_flipTrue左右镜像不改变大部分表情语义fill_modenearest48×48 小图用 nearest 不会产生奇怪的填充斑块验证集不能进数据增强只能用原始归一化数据。另外 TensorFlow 2.15 之后ImageDataGenerator会提示将弃用功能目前不受影响如果环境升级到 Keras 3需要改用tf.keras.utils.image_dataset_from_directory或者把环境固定到 TensorFlow 2.14。FER2013 的类别分布很不均衡happy 有近 9000 张disgust 只有几百张不处理的话模型会把 disgust 基本放弃。课程作业里最省事的做法是计算 class weightfrom sklearn.utils.class_weight import compute_class_weight class_weight dict(enumerate( compute_class_weight(balanced, classesnp.arange(7), ytrain_labels.argmax(1)) ))balanced会按样本量反比放大少数类权重但 disgust 只有几百张权重拉满可能让它过拟合。更稳的调法是拿到权重后整体乘以 0.5 到 1.0 的系数既能拉回少数类又不至于让模型在几张大图上死记。4. 训练与评估把 CNN 表情识别准确率从 55% 调到 70% 的调参路线4.1 训练配置与回调函数from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint model.compile( optimizerAdam(learning_rate1e-3), losscategorical_crossentropy, metrics[accuracy] ) callbacks [ EarlyStopping(monitorval_loss, patience8, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience3, min_lr1e-6), ModelCheckpoint(models/best_emotion.h5, monitorval_accuracy, save_best_onlyTrue) ] history model.fit( train_flow, steps_per_epochlen(train_images) // 64, epochs50, validation_data(val_images, val_labels), callbackscallbacks )Adam 初始学习率 1e-3 是卷积网络的稳妥起点。训练开始 loss 不降先检查数据是否归一化振荡就降到 1e-4。EarlyStopping 监控val_loss而不是val_accuracy因为 loss 对概率分布的细微变化更敏感能更早识别验证集变差但 ModelCheckpoint 保存的是val_accuracy最高的权重两个回调监控不同指标是刻意为之最终汇报用准确率更有说服力。ReduceLROnPlateau 连续 3 个 epoch 验证 loss 没进展就把学习率减半最低到 1e-6这比手动改学习率更适合答辩时演示“训练过程不用管”。batch size 取 64 是 48×48 小图上的性价比平衡点太小会让 BatchNorm 的统计量不稳定。4.2 评估分类报告比总准确率更值钱from sklearn.metrics import classification_report, confusion_matrix y_pred model.predict(val_images, verbose0).argmax(1) y_true val_labels.argmax(1) print(classification_report( y_true, y_pred, target_names[angry, disgust, fear, happy, sad, surprise, neutral] ))答辩时老师最常看的不是总准确率而是分类报告里 happy 的 recall 和 fear 的 precision。happy 样本多且特征明显各项指标都会最高fear 大量被分到 surprise 是 FER2013 上公开存在的混淆你能主动说出“这两类在数据上边界模糊”比回避问题强得多。import seaborn as sns import matplotlib.pyplot as plt cm confusion_matrix(y_true, y_pred) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabels[angry,disgust,fear,happy,sad,surprise,neutral], yticklabels[angry,disgust,fear,happy,sad,surprise,neutral]) plt.savefig(outputs/confusion_matrix.png)混淆矩阵图放进报告重点标出 fear/surprise 和 neutral/sad 两对混淆块就能体现你做了 error analysis。4.3 损失曲线里藏着的四种故障模式现象判断处理train_loss 降但 val_loss 不降过拟合增强强度加大、Dropout 提到 0.7、卷积核数量减半train_loss 和 val_loss 都不降学习率过大或容量不足lr 降到 1e-4卷积层数量从 4 层加到 5 层val_acc 卡在 55% 上下归一化或标签错位检查预测时是否漏了/255.0确认 train/val 的 mask 和标签对齐val_acc 一开始超过 80% 然后骤降数据划分泄露检查是否把同一人的相似脸帧同时分进 train 和 valFER2013 官方划分里训练集和验证集不是按人脸身份严格隔离的如果你自定义随机切分很容易把同一张脸的不同帧分到两边让验证集虚高几个点。用官方 Usage 字段划分至少能保证结果的数字有可比性。4.4 65% 是及格线70% 是优秀线课程设计不需要冲击 90%。FER2013 是众包标注数据同一张表情不同标注者可能给不同标签被广泛引用的人类水平评估也在 65% 到 70% 区间。模型跑到验证集 70% 以上已经可以在答辩时作为“达到并超过人类平均水平”的证据。5. 源码组织与摄像头实时识别把训练结果变成现场演示5.1 Python 期末大作业源码的标准目录结构路径职责data/fer2013.csv原始数据README 里注明下载来源src/data_loader.py解析 CSV、归一化、生成增强数据流src/model.pybuild_emotion_cnn模型定义src/train.py编译、回调、训练、保存模型src/evaluate.py分类报告与混淆矩阵src/predict_live.py摄像头实时识别models/best_emotion.h5训练产物requirements.txt依赖清单README.md运行顺序、Python 版本、参数说明这个模型只有 30 多万参数h5 文件只有几 MB可以放心放进源码压缩包。不要复用data_loader.py里的全局变量三个脚本分别调用函数老师打开源码时一眼能看出模块划分。5.2 基于 OpenCV 的实时表情识别完整代码import cv2 import numpy as np from tensorflow.keras.models import load_model MODEL_PATH models/best_emotion.h5 model load_model(MODEL_PATH) labels [angry, disgust, fear, happy, sad, surprise, neutral] face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) cap cv2.VideoCapture(0) while True: ret, frame cap.read() 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: face gray[y:y h, x:x w] face cv2.resize(face, (48, 48)) face face.astype(np.float32) / 255.0 face face.reshape(1, 48, 48, 1) pred model.predict(face, verbose0)[0] label labels[int(np.argmax(pred))] cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) cv2.imshow(FER2013 CNN - emotion recognition, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()detectMultiScale的scaleFactor1.1是速度与召回率的折中值越小检测越慢但漏检越少minNeighbors5控制假阳性教室演示时身后有人走动导致误检频发就把这个值调到 8。切片必须取灰度图gray[y:yh, x:xw]有人直接 resize 彩色帧模型输入是单通道立刻报 shape 错误。model.predict每次调用都有开销纯 CPU 笔记本大约能跑到 10 到 15 FPS用于演示足够。摄像头打不开时先查cap.isOpened()Windows 下可以改成cv2.VideoCapture(0, cv2.CAP_DSHOW)排除驱动兼容问题。5.3 为什么选 Haar 级联而不是更高级的人脸检测器Haar 级联不需要下载额外模型权重OpenCV 安装自带 XMLCPU 速度远高于 MTCNN。MTCNN 对侧脸和遮挡更鲁棒但每个检测框都要再过一遍神经网络帧率明显下降。期末现场演示环境不可控稳定比先进重要README 里写一句“换用 MTCNN 可以提升侧脸召回但会降低帧率”就已经体现你在方案选型上想过了。6. 现场演示的 3 个实战技巧预测平滑、运行环境、答辩追问6.1 连续帧预测平滑一行代码解决表情抖动直接预测视频流时同一个表情连续几帧会判成 happy、neutral、happy演示效果很掉价。常见做法是对预测概率做指数移动平均smooth_probs None def predict_smoothed(face): global smooth_probs probs model.predict(face, verbose0)[0] if smooth_probs is None: smooth_probs probs else: smooth_probs 0.6 * smooth_probs 0.4 * probs return labels[int(np.argmax(smooth_probs))]0.6 是历史权重0.4 是新帧权重既压制跳动又能在 3 到 4 帧内切换表情。想让反应更快就把 0.4 提到 0.5想让显示更稳就降到 0.3。注意切换表情后有约半秒滞后演示时动作做出来之后稍等再指屏幕。6.2 运行环境与依赖版本写进 READMErequirements.txt 只需要六行tensorflow2.8、opencv-python、numpy、pandas、scikit-learn、matplotlib、seaborn。Python 版本选 3.8 到 3.10 最稳3.11 以上装 TensorFlow 偶尔要处理 pip 依赖问题不要在演示前一晚和编译器较劲M 系列 Mac 可以加tensorflow-metal加速但这个量级的网络纯 CPU 训练也就几十分钟。6.3 答辩追问的两个标准答案被问到“为什么准确率只有 70% 而不是 90%”时直接说明这是 FER2013 的主观标注噪声问题同一张表情不同标注者可能给不同标签人在这个数据集上的表现也只有 65% 上下70% 是正常偏好。被问到“如果让你继续提高第一件事做什么”时答“先检查类别不均衡和数据增强再考虑用预训练 ResNet 做骨干并提高输入分辨率”这比回答“换更大模型”更显得有工程判断力。本文还有配套的精品资源点击获取
返回列表